1 /* 2 Copyright 2008-2023 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true*/ 33 /*jslint nomen: true, plusplus: true, unparam: true*/ 34 35 import JXG from "../jxg"; 36 import Const from "./constants"; 37 import Coords from "./coords"; 38 import Mat from "../math/math"; 39 import Statistics from "../math/statistics"; 40 import Options from "../options"; 41 import EventEmitter from "../utils/event"; 42 import Color from "../utils/color"; 43 import Type from "../utils/type"; 44 45 /** 46 * Constructs a new GeometryElement object. 47 * @class This is the basic class for geometry elements like points, circles and lines. 48 * @constructor 49 * @param {JXG.Board} board Reference to the board the element is constructed on. 50 * @param {Object} attributes Hash of attributes and their values. 51 * @param {Number} type Element type (a <tt>JXG.OBJECT_TYPE_</tt> value). 52 * @param {Number} oclass The element's class (a <tt>JXG.OBJECT_CLASS_</tt> value). 53 * @borrows JXG.EventEmitter#on as this.on 54 * @borrows JXG.EventEmitter#off as this.off 55 * @borrows JXG.EventEmitter#triggerEventHandlers as this.triggerEventHandlers 56 * @borrows JXG.EventEmitter#eventHandlers as this.eventHandlers 57 */ 58 JXG.GeometryElement = function (board, attributes, type, oclass) { 59 var name, key, attr; 60 61 /** 62 * Controls if updates are necessary 63 * @type Boolean 64 * @default true 65 */ 66 this.needsUpdate = true; 67 68 /** 69 * Controls if this element can be dragged. In GEONExT only 70 * free points and gliders can be dragged. 71 * @type Boolean 72 * @default false 73 */ 74 this.isDraggable = false; 75 76 /** 77 * If element is in two dimensional real space this is true, else false. 78 * @type Boolean 79 * @default true 80 */ 81 this.isReal = true; 82 83 /** 84 * Stores all dependent objects to be updated when this point is moved. 85 * @type Object 86 */ 87 this.childElements = {}; 88 89 /** 90 * If element has a label subelement then this property will be set to true. 91 * @type Boolean 92 * @default false 93 */ 94 this.hasLabel = false; 95 96 /** 97 * True, if the element is currently highlighted. 98 * @type Boolean 99 * @default false 100 */ 101 this.highlighted = false; 102 103 /** 104 * Stores all Intersection Objects which in this moment are not real and 105 * so hide this element. 106 * @type Object 107 */ 108 this.notExistingParents = {}; 109 110 /** 111 * Keeps track of all objects drawn as part of the trace of the element. 112 * @see JXG.GeometryElement#clearTrace 113 * @see JXG.GeometryElement#numTraces 114 * @type Object 115 */ 116 this.traces = {}; 117 118 /** 119 * Counts the number of objects drawn as part of the trace of the element. 120 * @see JXG.GeometryElement#clearTrace 121 * @see JXG.GeometryElement#traces 122 * @type Number 123 */ 124 this.numTraces = 0; 125 126 /** 127 * Stores the transformations which are applied during update in an array 128 * @type Array 129 * @see JXG.Transformation 130 */ 131 this.transformations = []; 132 133 /** 134 * @type JXG.GeometryElement 135 * @default null 136 * @private 137 */ 138 this.baseElement = null; 139 140 /** 141 * Elements depending on this element are stored here. 142 * @type Object 143 */ 144 this.descendants = {}; 145 146 /** 147 * Elements on which this element depends on are stored here. 148 * @type Object 149 */ 150 this.ancestors = {}; 151 152 /** 153 * Ids of elements on which this element depends directly are stored here. 154 * @type Object 155 */ 156 this.parents = []; 157 158 /** 159 * Stores variables for symbolic computations 160 * @type Object 161 */ 162 this.symbolic = {}; 163 164 /** 165 * Stores the SVG (or VML) rendering node for the element. This enables low-level 166 * access to SVG nodes. The properties of such an SVG node can then be changed 167 * by calling setAttribute(). Note that there are a few elements which consist 168 * of more than one SVG nodes: 169 * <ul> 170 * <li> Elements with arrow tail or head: rendNodeTriangleStart, rendNodeTriangleEnd 171 * <li> SVG (or VML) texts: rendNodeText 172 * <li> Button: rendNodeForm, rendNodeButton, rendNodeTag 173 * <li> Checkbox: rendNodeForm, rendNodeCheckbox, rendNodeLabel, rendNodeTag 174 * <li> Input: rendNodeForm, rendNodeInput, rendNodeLabel, rendNodeTag 175 * </ul> 176 * 177 * Here is are two examples: The first example shows how to access the SVG node, 178 * the second example demonstrates how to change SVG attributes. 179 * @example 180 * var p1 = board.create('point', [0, 0]); 181 * console.log(p1.rendNode); 182 * // returns the full SVG node details of the point p1, something like: 183 * // <ellipse id='box_jxgBoard1P6' stroke='#ff0000' stroke-opacity='1' stroke-width='2px' 184 * // fill='#ff0000' fill-opacity='1' cx='250' cy='250' rx='4' ry='4' 185 * // style='position: absolute;'> 186 * // </ellipse> 187 * 188 * @example 189 * var s = board.create('segment', [p1, p2], {strokeWidth: 60}); 190 * s.rendNode.setAttribute('stroke-linecap', 'round'); 191 * 192 * @type Object 193 */ 194 this.rendNode = null; 195 196 /** 197 * The string used with {@link JXG.Board#create} 198 * @type String 199 */ 200 this.elType = ""; 201 202 /** 203 * The element is saved with an explicit entry in the file (<tt>true</tt>) or implicitly 204 * via a composition. 205 * @type Boolean 206 * @default true 207 */ 208 this.dump = true; 209 210 /** 211 * Subs contains the subelements, created during the create method. 212 * @type Object 213 */ 214 this.subs = {}; 215 216 /** 217 * Inherits contains the subelements, which may have an attribute 218 * (in particular the attribute "visible") having value 'inherit'. 219 * @type Object 220 */ 221 this.inherits = []; 222 223 /** 224 * The position of this element inside the {@link JXG.Board#objectsList}. 225 * @type Number 226 * @default -1 227 * @private 228 */ 229 this._pos = -1; 230 231 /** 232 * [c, b0, b1, a, k, r, q0, q1] 233 * 234 * See 235 * A.E. Middleditch, T.W. Stacey, and S.B. Tor: 236 * "Intersection Algorithms for Lines and Circles", 237 * ACM Transactions on Graphics, Vol. 8, 1, 1989, pp 25-40. 238 * 239 * The meaning of the parameters is: 240 * Circle: points p=[p0, p1] on the circle fulfill 241 * a<p, p> + <b, p> + c = 0 242 * For convenience we also store 243 * r: radius 244 * k: discriminant = sqrt(<b,b>-4ac) 245 * q=[q0, q1] center 246 * 247 * Points have radius = 0. 248 * Lines have radius = infinity. 249 * b: normalized vector, representing the direction of the line. 250 * 251 * Should be put into Coords, when all elements possess Coords. 252 * @type Array 253 * @default [1, 0, 0, 0, 1, 1, 0, 0] 254 */ 255 this.stdform = [1, 0, 0, 0, 1, 1, 0, 0]; 256 257 /** 258 * The methodMap determines which methods can be called from within JessieCode and under which name it 259 * can be used. The map is saved in an object, the name of a property is the name of the method used in JessieCode, 260 * the value of a property is the name of the method in JavaScript. 261 * @type Object 262 */ 263 this.methodMap = { 264 setLabel: "setLabel", 265 label: "label", 266 setName: "setName", 267 getName: "getName", 268 Name: "getName", 269 addTransform: "addTransform", 270 setProperty: "setAttribute", 271 setAttribute: "setAttribute", 272 addChild: "addChild", 273 animate: "animate", 274 on: "on", 275 off: "off", 276 trigger: "trigger", 277 addTicks: "addTicks", 278 removeTicks: "removeTicks", 279 removeAllTicks: "removeAllTicks", 280 Bounds: "bounds" 281 }; 282 283 /** 284 * Quadratic form representation of circles (and conics) 285 * @type Array 286 * @default [[1,0,0],[0,1,0],[0,0,1]] 287 */ 288 this.quadraticform = [ 289 [1, 0, 0], 290 [0, 1, 0], 291 [0, 0, 1] 292 ]; 293 294 /** 295 * An associative array containing all visual properties. 296 * @type Object 297 * @default empty object 298 */ 299 this.visProp = {}; 300 301 /** 302 * An associative array containing visual properties which are calculated from 303 * the attribute values (i.e. visProp) and from other constraints. 304 * An example: if an intersection point does not have real coordinates, 305 * visPropCalc.visible is set to false. 306 * Additionally, the user can control visibility with the attribute "visible", 307 * even by supplying a functions as value. 308 * 309 * @type Object 310 * @default empty object 311 */ 312 this.visPropCalc = { 313 visible: false 314 }; 315 316 EventEmitter.eventify(this); 317 318 /** 319 * Is the mouse over this element? 320 * @type Boolean 321 * @default false 322 */ 323 this.mouseover = false; 324 325 /** 326 * Time stamp containing the last time this element has been dragged. 327 * @type Date 328 * @default creation time 329 */ 330 this.lastDragTime = new Date(); 331 332 if (arguments.length > 0) { 333 /** 334 * Reference to the board associated with the element. 335 * @type JXG.Board 336 */ 337 this.board = board; 338 339 /** 340 * Type of the element. 341 * @constant 342 * @type Number 343 */ 344 this.type = type; 345 346 /** 347 * Original type of the element at construction time. Used for removing glider property. 348 * @constant 349 * @type Number 350 */ 351 this._org_type = type; 352 353 /** 354 * The element's class. 355 * @constant 356 * @type Number 357 */ 358 this.elementClass = oclass || Const.OBJECT_CLASS_OTHER; 359 360 /** 361 * Unique identifier for the element. Equivalent to id-attribute of renderer element. 362 * @type String 363 */ 364 this.id = attributes.id; 365 366 name = attributes.name; 367 /* If name is not set or null or even undefined, generate an unique name for this object */ 368 if (!Type.exists(name)) { 369 name = this.board.generateName(this); 370 } 371 372 if (name !== "") { 373 this.board.elementsByName[name] = this; 374 } 375 376 /** 377 * Not necessarily unique name for the element. 378 * @type String 379 * @default Name generated by {@link JXG.Board#generateName}. 380 * @see JXG.Board#generateName 381 */ 382 this.name = name; 383 384 this.needsRegularUpdate = attributes.needsregularupdate; 385 386 // create this.visPropOld and set default values 387 Type.clearVisPropOld(this); 388 389 attr = this.resolveShortcuts(attributes); 390 for (key in attr) { 391 if (attr.hasOwnProperty(key)) { 392 this._set(key, attr[key]); 393 } 394 } 395 396 this.visProp.draft = attr.draft && attr.draft.draft; 397 //this.visProp.gradientangle = '270'; 398 // this.visProp.gradientsecondopacity = Type.evaluate(this.visProp.fillopacity); 399 //this.visProp.gradientpositionx = 0.5; 400 //this.visProp.gradientpositiony = 0.5; 401 } 402 }; 403 404 JXG.extend( 405 JXG.GeometryElement.prototype, 406 /** @lends JXG.GeometryElement.prototype */ { 407 /** 408 * Add an element as a child to the current element. Can be used to model dependencies between geometry elements. 409 * @param {JXG.GeometryElement} obj The dependent object. 410 */ 411 addChild: function (obj) { 412 var el, el2; 413 414 this.childElements[obj.id] = obj; 415 this.addDescendants(obj); 416 obj.ancestors[this.id] = this; 417 418 for (el in this.descendants) { 419 if (this.descendants.hasOwnProperty(el)) { 420 this.descendants[el].ancestors[this.id] = this; 421 422 for (el2 in this.ancestors) { 423 if (this.ancestors.hasOwnProperty(el2)) { 424 this.descendants[el].ancestors[this.ancestors[el2].id] = 425 this.ancestors[el2]; 426 } 427 } 428 } 429 } 430 431 for (el in this.ancestors) { 432 if (this.ancestors.hasOwnProperty(el)) { 433 for (el2 in this.descendants) { 434 if (this.descendants.hasOwnProperty(el2)) { 435 this.ancestors[el].descendants[this.descendants[el2].id] = 436 this.descendants[el2]; 437 } 438 } 439 } 440 } 441 return this; 442 }, 443 444 /** 445 * @param {JXG.GeometryElement} obj The element that is to be added to the descendants list. 446 * @private 447 * @return this 448 */ 449 // Adds the given object to the descendants list of this object and all its child objects. 450 addDescendants: function (obj) { 451 var el; 452 453 this.descendants[obj.id] = obj; 454 for (el in obj.childElements) { 455 if (obj.childElements.hasOwnProperty(el)) { 456 this.addDescendants(obj.childElements[el]); 457 } 458 } 459 return this; 460 }, 461 462 /** 463 * Adds ids of elements to the array this.parents. This method needs to be called if some dependencies 464 * can not be detected automatically by JSXGraph. For example if a function graph is given by a function 465 * which refers to coordinates of a point, calling addParents() is necessary. 466 * 467 * @param {Array} parents Array of elements or ids of elements. 468 * Alternatively, one can give a list of objects as parameters. 469 * @returns {JXG.Object} reference to the object itself. 470 * 471 * @example 472 * // Movable function graph 473 * var A = board.create('point', [1, 0], {name:'A'}), 474 * B = board.create('point', [3, 1], {name:'B'}), 475 * f = board.create('functiongraph', function(x) { 476 * var ax = A.X(), 477 * ay = A.Y(), 478 * bx = B.X(), 479 * by = B.Y(), 480 * a = (by - ay) / ( (bx - ax) * (bx - ax) ); 481 * return a * (x - ax) * (x - ax) + ay; 482 * }, {fixed: false}); 483 * f.addParents([A, B]); 484 * </pre><div class="jxgbox" id="JXG7c91d4d2-986c-4378-8135-24505027f251" style="width: 400px; height: 400px;"></div> 485 * <script type="text/javascript"> 486 * (function() { 487 * var board = JXG.JSXGraph.initBoard('JXG7c91d4d2-986c-4378-8135-24505027f251', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 488 * var A = board.create('point', [1, 0], {name:'A'}), 489 * B = board.create('point', [3, 1], {name:'B'}), 490 * f = board.create('functiongraph', function(x) { 491 * var ax = A.X(), 492 * ay = A.Y(), 493 * bx = B.X(), 494 * by = B.Y(), 495 * a = (by - ay) / ( (bx - ax) * (bx - ax) ); 496 * return a * (x - ax) * (x - ax) + ay; 497 * }, {fixed: false}); 498 * f.addParents([A, B]); 499 * })(); 500 * </script><pre> 501 * 502 **/ 503 addParents: function (parents) { 504 var i, len, par; 505 506 if (Type.isArray(parents)) { 507 par = parents; 508 } else { 509 par = arguments; 510 } 511 512 len = par.length; 513 for (i = 0; i < len; ++i) { 514 if (!Type.exists(par[i])) { 515 continue; 516 } 517 if (Type.isId(this.board, par[i])) { 518 this.parents.push(par[i]); 519 } else if (Type.exists(par[i].id)) { 520 this.parents.push(par[i].id); 521 } 522 } 523 this.parents = Type.uniqueArray(this.parents); 524 }, 525 526 /** 527 * Sets ids of elements to the array this.parents. 528 * First, this.parents is cleared. See {@link JXG.GeometryElement#addParents}. 529 * @param {Array} parents Array of elements or ids of elements. 530 * Alternatively, one can give a list of objects as parameters. 531 * @returns {JXG.Object} reference to the object itself. 532 **/ 533 setParents: function (parents) { 534 this.parents = []; 535 this.addParents(parents); 536 }, 537 538 /** 539 * Add dependence on elements in JessieCode functions. 540 * @param {Array} function_array Array of functions containing potential properties "deps" with 541 * elements the function depends on. 542 * @returns {JXG.Object} reference to the object itself 543 * @private 544 */ 545 addParentsFromJCFunctions: function (function_array) { 546 var i, e, obj; 547 for (i = 0; i < function_array.length; i++) { 548 for (e in function_array[i].deps) { 549 obj = function_array[i].deps[e]; 550 this.addParents(obj); 551 obj.addChild(this); 552 } 553 } 554 return this; 555 }, 556 557 /** 558 * Remove an element as a child from the current element. 559 * @param {JXG.GeometryElement} obj The dependent object. 560 * @returns {JXG.Object} reference to the object itself 561 */ 562 removeChild: function (obj) { 563 //var el, el2; 564 565 delete this.childElements[obj.id]; 566 this.removeDescendants(obj); 567 delete obj.ancestors[this.id]; 568 569 /* 570 // I do not know if these addDescendants stuff has to be adapted to removeChild. A.W. 571 for (el in this.descendants) { 572 if (this.descendants.hasOwnProperty(el)) { 573 delete this.descendants[el].ancestors[this.id]; 574 575 for (el2 in this.ancestors) { 576 if (this.ancestors.hasOwnProperty(el2)) { 577 this.descendants[el].ancestors[this.ancestors[el2].id] = this.ancestors[el2]; 578 } 579 } 580 } 581 } 582 583 for (el in this.ancestors) { 584 if (this.ancestors.hasOwnProperty(el)) { 585 for (el2 in this.descendants) { 586 if (this.descendants.hasOwnProperty(el2)) { 587 this.ancestors[el].descendants[this.descendants[el2].id] = this.descendants[el2]; 588 } 589 } 590 } 591 } 592 */ 593 return this; 594 }, 595 596 /** 597 * Removes the given object from the descendants list of this object and all its child objects. 598 * @param {JXG.GeometryElement} obj The element that is to be removed from the descendants list. 599 * @private 600 * @returns {JXG.Object} reference to the object itself 601 */ 602 removeDescendants: function (obj) { 603 var el; 604 605 delete this.descendants[obj.id]; 606 for (el in obj.childElements) { 607 if (obj.childElements.hasOwnProperty(el)) { 608 this.removeDescendants(obj.childElements[el]); 609 } 610 } 611 return this; 612 }, 613 614 /** 615 * Counts the direct children of an object without counting labels. 616 * @private 617 * @returns {number} Number of children 618 */ 619 countChildren: function () { 620 var prop, 621 d, 622 s = 0; 623 624 d = this.childElements; 625 for (prop in d) { 626 if (d.hasOwnProperty(prop) && prop.indexOf("Label") < 0) { 627 s++; 628 } 629 } 630 return s; 631 }, 632 633 /** 634 * Returns the elements name. Used in JessieCode. 635 * @returns {String} 636 */ 637 getName: function () { 638 return this.name; 639 }, 640 641 /** 642 * Add transformations to this element. 643 * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} 644 * or an array of {@link JXG.Transformation}s. 645 * @returns {JXG.GeometryElement} Reference to the element. 646 */ 647 addTransform: function (transform) { 648 return this; 649 }, 650 651 /** 652 * Decides whether an element can be dragged. This is used in 653 * {@link JXG.GeometryElement#setPositionDirectly} methods 654 * where all parent elements are checked if they may be dragged, too. 655 * @private 656 * @returns {boolean} 657 */ 658 draggable: function () { 659 return ( 660 this.isDraggable && 661 !Type.evaluate(this.visProp.fixed) && 662 // !this.visProp.frozen && 663 this.type !== Const.OBJECT_TYPE_GLIDER 664 ); 665 }, 666 667 /** 668 * Translates the object by <tt>(x, y)</tt>. In case the element is defined by points, the defining points are 669 * translated, e.g. a circle constructed by a center point and a point on the circle line. 670 * @param {Number} method The type of coordinates used here. 671 * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 672 * @param {Array} coords array of translation vector. 673 * @returns {JXG.GeometryElement} Reference to the element object. 674 */ 675 setPosition: function (method, coords) { 676 var parents = [], 677 el, 678 i, 679 len, 680 t; 681 682 if (!Type.exists(this.parents)) { 683 return this; 684 } 685 686 len = this.parents.length; 687 for (i = 0; i < len; ++i) { 688 el = this.board.select(this.parents[i]); 689 if (Type.isPoint(el)) { 690 if (!el.draggable()) { 691 return this; 692 } 693 parents.push(el); 694 } 695 } 696 697 if (coords.length === 3) { 698 coords = coords.slice(1); 699 } 700 701 t = this.board.create("transform", coords, { type: "translate" }); 702 703 // We distinguish two cases: 704 // 1) elements which depend on free elements, i.e. arcs and sectors 705 // 2) other elements 706 // 707 // In the first case we simply transform the parents elements 708 // In the second case we add a transform to the element. 709 // 710 len = parents.length; 711 if (len > 0) { 712 t.applyOnce(parents); 713 } else { 714 if ( 715 this.transformations.length > 0 && 716 this.transformations[this.transformations.length - 1].isNumericMatrix 717 ) { 718 this.transformations[this.transformations.length - 1].melt(t); 719 } else { 720 this.addTransform(t); 721 } 722 } 723 724 /* 725 * If - against the default configuration - defining gliders are marked as 726 * draggable, then their position has to be updated now. 727 */ 728 for (i = 0; i < len; ++i) { 729 if (parents[i].type === Const.OBJECT_TYPE_GLIDER) { 730 parents[i].updateGlider(); 731 } 732 } 733 734 return this; 735 }, 736 737 /** 738 * Moves an element by the difference of two coordinates. 739 * @param {Number} method The type of coordinates used here. 740 * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 741 * @param {Array} coords coordinates in screen/user units 742 * @param {Array} oldcoords previous coordinates in screen/user units 743 * @returns {JXG.GeometryElement} this element 744 */ 745 setPositionDirectly: function (method, coords, oldcoords) { 746 var c = new Coords(method, coords, this.board, false), 747 oldc = new Coords(method, oldcoords, this.board, false), 748 dc = Statistics.subtract(c.usrCoords, oldc.usrCoords); 749 750 this.setPosition(Const.COORDS_BY_USER, dc); 751 752 return this; 753 }, 754 755 /** 756 * Array of strings containing the polynomials defining the element. 757 * Used for determining geometric loci the groebner way. 758 * @returns {Array} An array containing polynomials describing the locus of the current object. 759 * @public 760 */ 761 generatePolynomial: function () { 762 return []; 763 }, 764 765 /** 766 * Animates properties for that object like stroke or fill color, opacity and maybe 767 * even more later. 768 * @param {Object} hash Object containing properties with target values for the animation. 769 * @param {number} time Number of milliseconds to complete the animation. 770 * @param {Object} [options] Optional settings for the animation:<ul><li>callback: A function that is called as soon as the animation is finished.</li></ul> 771 * @returns {JXG.GeometryElement} A reference to the object 772 */ 773 animate: function (hash, time, options) { 774 options = options || {}; 775 var r, 776 p, 777 i, 778 delay = this.board.attr.animationdelay, 779 steps = Math.ceil(time / delay), 780 self = this, 781 animateColor = function (startRGB, endRGB, property) { 782 var hsv1, hsv2, sh, ss, sv; 783 hsv1 = Color.rgb2hsv(startRGB); 784 hsv2 = Color.rgb2hsv(endRGB); 785 786 sh = (hsv2[0] - hsv1[0]) / steps; 787 ss = (hsv2[1] - hsv1[1]) / steps; 788 sv = (hsv2[2] - hsv1[2]) / steps; 789 self.animationData[property] = []; 790 791 for (i = 0; i < steps; i++) { 792 self.animationData[property][steps - i - 1] = Color.hsv2rgb( 793 hsv1[0] + (i + 1) * sh, 794 hsv1[1] + (i + 1) * ss, 795 hsv1[2] + (i + 1) * sv 796 ); 797 } 798 }, 799 animateFloat = function (start, end, property, round) { 800 var tmp, s; 801 802 start = parseFloat(start); 803 end = parseFloat(end); 804 805 // we can't animate without having valid numbers. 806 // And parseFloat returns NaN if the given string doesn't contain 807 // a valid float number. 808 if (isNaN(start) || isNaN(end)) { 809 return; 810 } 811 812 s = (end - start) / steps; 813 self.animationData[property] = []; 814 815 for (i = 0; i < steps; i++) { 816 tmp = start + (i + 1) * s; 817 self.animationData[property][steps - i - 1] = round 818 ? Math.floor(tmp) 819 : tmp; 820 } 821 }; 822 823 this.animationData = {}; 824 825 for (r in hash) { 826 if (hash.hasOwnProperty(r)) { 827 p = r.toLowerCase(); 828 829 switch (p) { 830 case "strokecolor": 831 case "fillcolor": 832 animateColor(this.visProp[p], hash[r], p); 833 break; 834 case "size": 835 if (!Type.isPoint(this)) { 836 break; 837 } 838 animateFloat(this.visProp[p], hash[r], p, true); 839 break; 840 case "strokeopacity": 841 case "strokewidth": 842 case "fillopacity": 843 animateFloat(this.visProp[p], hash[r], p, false); 844 break; 845 } 846 } 847 } 848 849 this.animationCallback = options.callback; 850 this.board.addAnimation(this); 851 return this; 852 }, 853 854 /** 855 * General update method. Should be overwritten by the element itself. 856 * Can be used sometimes to commit changes to the object. 857 * @return {JXG.GeometryElement} Reference to the element 858 */ 859 update: function () { 860 if (Type.evaluate(this.visProp.trace)) { 861 this.cloneToBackground(); 862 } 863 return this; 864 }, 865 866 /** 867 * Provide updateRenderer method. 868 * @return {JXG.GeometryElement} Reference to the element 869 * @private 870 */ 871 updateRenderer: function () { 872 return this; 873 }, 874 875 /** 876 * Run through the full update chain of an element. 877 * @param {Boolean} visible Set visibility in case the elements attribute value is 'inherit'. null is allowed. 878 * @return {JXG.GeometryElement} Reference to the element 879 * @private 880 */ 881 fullUpdate: function (visible) { 882 return this.prepareUpdate().update().updateVisibility(visible).updateRenderer(); 883 }, 884 885 /** 886 * Show the element or hide it. If hidden, it will still exist but not be 887 * visible on the board. 888 * <p> 889 * Sets also the display of the inherits elements. These can be 890 * JSXGraph elements or arrays of JSXGraph elements. 891 * However, deeper nesting than this is not supported. 892 * 893 * @param {Boolean} val true: show the element, false: hide the element 894 * @return {JXG.GeometryElement} Reference to the element 895 * @private 896 */ 897 setDisplayRendNode: function (val) { 898 var i, len, s, len_s, obj; 899 900 if (val === undefined) { 901 val = this.visPropCalc.visible; 902 } 903 904 if (val === this.visPropOld.visible) { 905 return this; 906 } 907 908 // Set display of the element itself 909 this.board.renderer.display(this, val); 910 911 // Set the visibility of elements which inherit the attribute 'visible' 912 len = this.inherits.length; 913 for (s = 0; s < len; s++) { 914 obj = this.inherits[s]; 915 if (Type.isArray(obj)) { 916 len_s = obj.length; 917 for (i = 0; i < len_s; i++) { 918 if ( 919 Type.exists(obj[i]) && 920 Type.exists(obj[i].rendNode) && 921 Type.evaluate(obj[i].visProp.visible) === 'inherit' 922 ) { 923 obj[i].setDisplayRendNode(val); 924 } 925 } 926 } else { 927 if ( 928 Type.exists(obj) && 929 Type.exists(obj.rendNode) && 930 Type.evaluate(obj.visProp.visible) === 'inherit' 931 ) { 932 obj.setDisplayRendNode(val); 933 } 934 } 935 } 936 937 // Set the visibility of the label if it inherits the attribute 'visible' 938 if (this.hasLabel && Type.exists(this.label) && Type.exists(this.label.rendNode)) { 939 if (Type.evaluate(this.label.visProp.visible) === "inherit") { 940 this.label.setDisplayRendNode(val); 941 } 942 } 943 944 return this; 945 }, 946 947 /** 948 * Hide the element. It will still exist but not be visible on the board. 949 * Alias for "element.setAttribute({visible: false});" 950 * @return {JXG.GeometryElement} Reference to the element 951 */ 952 hide: function () { 953 this.setAttribute({ visible: false }); 954 return this; 955 }, 956 957 /** 958 * Hide the element. It will still exist but not be visible on the board. 959 * Alias for {@link JXG.GeometryElement#hide} 960 * @returns {JXG.GeometryElement} Reference to the element 961 */ 962 hideElement: function () { 963 this.hide(); 964 return this; 965 }, 966 967 /** 968 * Make the element visible. 969 * Alias for "element.setAttribute({visible: true});" 970 * @return {JXG.GeometryElement} Reference to the element 971 */ 972 show: function () { 973 this.setAttribute({ visible: true }); 974 return this; 975 }, 976 977 /** 978 * Make the element visible. 979 * Alias for {@link JXG.GeometryElement#show} 980 * @returns {JXG.GeometryElement} Reference to the element 981 */ 982 showElement: function () { 983 this.show(); 984 return this; 985 }, 986 987 /** 988 * Set the visibility of an element. The visibility is influenced by 989 * (listed in ascending priority): 990 * <ol> 991 * <li> The value of the element's attribute 'visible' 992 * <li> The visibility of a parent element. (Example: label) 993 * This overrules the value of the element's attribute value only if 994 * this attribute value of the element is 'inherit'. 995 * <li> being inside of the canvas 996 * </ol> 997 * <p> 998 * This method is called three times for most elements: 999 * <ol> 1000 * <li> between {@link JXG.GeometryElement#update} 1001 * and {@link JXG.GeometryElement#updateRenderer}. In case the value is 'inherit', nothing is done. 1002 * <li> Recursively, called by itself for child elements. Here, 'inherit' is overruled by the parent's value. 1003 * <li> In {@link JXG.GeometryElement#updateRenderer}, if the element is outside of the canvas. 1004 * </ol> 1005 * 1006 * @param {Boolean} parent_val Visibility of the parent element. 1007 * @return {JXG.GeometryElement} Reference to the element. 1008 * @private 1009 */ 1010 updateVisibility: function (parent_val) { 1011 var i, len, s, len_s, obj, val; 1012 1013 if (this.needsUpdate) { 1014 // Handle the element 1015 if (parent_val !== undefined) { 1016 this.visPropCalc.visible = parent_val; 1017 } else { 1018 val = Type.evaluate(this.visProp.visible); 1019 1020 // infobox uses hiddenByParent 1021 if (Type.exists(this.hiddenByParent) && this.hiddenByParent) { 1022 val = false; 1023 } 1024 if (val !== "inherit") { 1025 this.visPropCalc.visible = val; 1026 } 1027 } 1028 1029 // Handle elements which inherit the visibility 1030 len = this.inherits.length; 1031 for (s = 0; s < len; s++) { 1032 obj = this.inherits[s]; 1033 if (Type.isArray(obj)) { 1034 len_s = obj.length; 1035 for (i = 0; i < len_s; i++) { 1036 if ( 1037 Type.exists(obj[i]) /*&& Type.exists(obj[i].rendNode)*/ && 1038 Type.evaluate(obj[i].visProp.visible) === "inherit" 1039 ) { 1040 obj[i] 1041 .prepareUpdate() 1042 .updateVisibility(this.visPropCalc.visible); 1043 } 1044 } 1045 } else { 1046 if ( 1047 Type.exists(obj) /*&& Type.exists(obj.rendNode)*/ && 1048 Type.evaluate(obj.visProp.visible) === "inherit" 1049 ) { 1050 obj.prepareUpdate().updateVisibility(this.visPropCalc.visible); 1051 } 1052 } 1053 } 1054 1055 // Handle the label if it inherits the visibility 1056 if ( 1057 Type.exists(this.label) && 1058 Type.exists(this.label.visProp) && 1059 Type.evaluate(this.label.visProp.visible) 1060 ) { 1061 this.label.prepareUpdate().updateVisibility(this.visPropCalc.visible); 1062 } 1063 } 1064 return this; 1065 }, 1066 1067 /** 1068 * Sets the value of attribute <tt>key</tt> to <tt>value</tt>. 1069 * @param {String} key The attribute's name. 1070 * @param value The new value 1071 * @private 1072 */ 1073 _set: function (key, value) { 1074 var el; 1075 1076 key = key.toLocaleLowerCase(); 1077 1078 // Search for entries in visProp with "color" as part of the key name 1079 // and containing a RGBA string 1080 if ( 1081 this.visProp.hasOwnProperty(key) && 1082 key.indexOf("color") >= 0 && 1083 Type.isString(value) && 1084 value.length === 9 && 1085 value.charAt(0) === "#" 1086 ) { 1087 value = Color.rgba2rgbo(value); 1088 this.visProp[key] = value[0]; 1089 // Previously: *=. But then, we can only decrease opacity. 1090 this.visProp[key.replace("color", "opacity")] = value[1]; 1091 } else { 1092 if ( 1093 value !== null && 1094 Type.isObject(value) && 1095 !Type.exists(value.id) && 1096 !Type.exists(value.name) 1097 ) { 1098 // value is of type {prop: val, prop: val,...} 1099 // Convert these attributes to lowercase, too 1100 this.visProp[key] = {}; 1101 for (el in value) { 1102 if (value.hasOwnProperty(el)) { 1103 this.visProp[key][el.toLocaleLowerCase()] = value[el]; 1104 } 1105 } 1106 } else { 1107 this.visProp[key] = value; 1108 } 1109 } 1110 }, 1111 1112 /** 1113 * Resolves attribute shortcuts like <tt>color</tt> and expands them, e.g. <tt>strokeColor</tt> and <tt>fillColor</tt>. 1114 * Writes the expanded attributes back to the given <tt>attributes</tt>. 1115 * @param {Object} attributes object 1116 * @returns {Object} The given attributes object with shortcuts expanded. 1117 * @private 1118 */ 1119 resolveShortcuts: function (attributes) { 1120 var key, 1121 i, 1122 j, 1123 subattr = ["traceattributes", "traceAttributes"]; 1124 1125 for (key in Options.shortcuts) { 1126 if (Options.shortcuts.hasOwnProperty(key)) { 1127 if (Type.exists(attributes[key])) { 1128 for (i = 0; i < Options.shortcuts[key].length; i++) { 1129 if (!Type.exists(attributes[Options.shortcuts[key][i]])) { 1130 attributes[Options.shortcuts[key][i]] = attributes[key]; 1131 } 1132 } 1133 } 1134 for (j = 0; j < subattr.length; j++) { 1135 if (Type.isObject(attributes[subattr[j]])) { 1136 attributes[subattr[j]] = this.resolveShortcuts( 1137 attributes[subattr[j]] 1138 ); 1139 } 1140 } 1141 } 1142 } 1143 return attributes; 1144 }, 1145 1146 /** 1147 * Sets a label and its text 1148 * If label doesn't exist, it creates one 1149 * @param {String} str 1150 */ 1151 setLabel: function (str) { 1152 if (!this.hasLabel) { 1153 this.setAttribute({ withlabel: true }); 1154 } 1155 this.setLabelText(str); 1156 }, 1157 1158 /** 1159 * Updates the element's label text, strips all html. 1160 * @param {String} str 1161 */ 1162 setLabelText: function (str) { 1163 if (Type.exists(this.label)) { 1164 str = str.replace(/</g, "<").replace(/>/g, ">"); 1165 this.label.setText(str); 1166 } 1167 1168 return this; 1169 }, 1170 1171 /** 1172 * Updates the element's label text and the element's attribute "name", strips all html. 1173 * @param {String} str 1174 */ 1175 setName: function (str) { 1176 str = str.replace(/</g, "<").replace(/>/g, ">"); 1177 if (this.elType !== "slider") { 1178 this.setLabelText(str); 1179 } 1180 this.setAttribute({ name: str }); 1181 }, 1182 1183 /** 1184 * Deprecated alias for {@link JXG.GeometryElement#setAttribute}. 1185 * @deprecated Use {@link JXG.GeometryElement#setAttribute}. 1186 */ 1187 setProperty: function () { 1188 JXG.deprecated("setProperty()", "setAttribute()"); 1189 this.setAttribute.apply(this, arguments); 1190 }, 1191 1192 /** 1193 * Sets an arbitrary number of attributes. This method has one or more 1194 * parameters of the following types: 1195 * <ul> 1196 * <li> object: {key1:value1,key2:value2,...} 1197 * <li> string: 'key:value' 1198 * <li> array: ['key', value] 1199 * </ul> 1200 * @param {Object} attributes An object with attributes. 1201 * @returns {JXG.GeometryElement} A reference to the element. 1202 * 1203 * @function 1204 * @example 1205 * // Set attribute directly on creation of an element using the attributes object parameter 1206 * var board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-1, 5, 5, 1]}; 1207 * var p = board.create('point', [2, 2], {visible: false}); 1208 * 1209 * // Now make this point visible and fixed: 1210 * p.setAttribute({ 1211 * fixed: true, 1212 * visible: true 1213 * }); 1214 */ 1215 setAttribute: function (attr) { 1216 var i, j, le, key, value, arg, 1217 opacity, pair, oldvalue, 1218 attributes = {}; 1219 1220 // Normalize the user input 1221 for (i = 0; i < arguments.length; i++) { 1222 arg = arguments[i]; 1223 if (Type.isString(arg)) { 1224 // pairRaw is string of the form 'key:value' 1225 pair = arg.split(":"); 1226 attributes[Type.trim(pair[0])] = Type.trim(pair[1]); 1227 } else if (!Type.isArray(arg)) { 1228 // pairRaw consists of objects of the form {key1:value1,key2:value2,...} 1229 JXG.extend(attributes, arg); 1230 } else { 1231 // pairRaw consists of array [key,value] 1232 attributes[arg[0]] = arg[1]; 1233 } 1234 } 1235 1236 // Handle shortcuts 1237 attributes = this.resolveShortcuts(attributes); 1238 1239 for (i in attributes) { 1240 if (attributes.hasOwnProperty(i)) { 1241 key = i.replace(/\s+/g, "").toLowerCase(); 1242 value = attributes[i]; 1243 1244 // This handles the subobjects, if the key:value pairs are contained in an object. 1245 // Example: 1246 // ticks.setAttribute({ 1247 // strokeColor: 'blue', 1248 // label: { 1249 // visible: false 1250 // } 1251 // }) 1252 // Now, only the supplied label attributes are overwritten. 1253 // Otherwise, the value of label would be {visible:false} only. 1254 if (Type.isObject(value) && Type.exists(this.visProp[key])) { 1255 this.visProp[key] = Type.merge(this.visProp[key], value); 1256 1257 // First, handle the special case 1258 // ticks.setAttribute({label: {anchorX: "right", ..., visible: true}); 1259 if (this.type === Const.OBJECT_TYPE_TICKS && Type.exists(this.labels)) { 1260 le = this.labels.length; 1261 for (j = 0; j < le; j++) { 1262 this.labels[j].setAttribute(value); 1263 } 1264 } else if (Type.exists(this[key])) { 1265 if (Type.isArray(this[key])) { 1266 for (j = 0; j < this[key].length; j++) { 1267 this[key][j].setAttribute(value); 1268 } 1269 } else { 1270 this[key].setAttribute(value); 1271 } 1272 } 1273 continue; 1274 } 1275 1276 oldvalue = this.visProp[key]; 1277 switch (key) { 1278 case "checked": 1279 // checkbox Is not available on initial call. 1280 if (Type.exists(this.rendNodeTag)) { 1281 this.rendNodeCheckbox.checked = !!value; 1282 } 1283 break; 1284 case "disabled": 1285 // button, checkbox, input. Is not available on initial call. 1286 if (Type.exists(this.rendNodeTag)) { 1287 this.rendNodeTag.disabled = !!value; 1288 } 1289 break; 1290 case "face": 1291 if (Type.isPoint(this)) { 1292 this.visProp.face = value; 1293 this.board.renderer.changePointStyle(this); 1294 } 1295 break; 1296 case "generatelabelvalue": 1297 if ( 1298 this.type === Const.OBJECT_TYPE_TICKS && 1299 Type.isFunction(value) 1300 ) { 1301 this.generateLabelValue = value; 1302 } 1303 break; 1304 case "gradient": 1305 this.visProp.gradient = value; 1306 this.board.renderer.setGradient(this); 1307 break; 1308 case "gradientsecondcolor": 1309 value = Color.rgba2rgbo(value); 1310 this.visProp.gradientsecondcolor = value[0]; 1311 this.visProp.gradientsecondopacity = value[1]; 1312 this.board.renderer.updateGradient(this); 1313 break; 1314 case "gradientsecondopacity": 1315 this.visProp.gradientsecondopacity = value; 1316 this.board.renderer.updateGradient(this); 1317 break; 1318 case "infoboxtext": 1319 if (Type.isString(value)) { 1320 this.infoboxText = value; 1321 } else { 1322 this.infoboxText = false; 1323 } 1324 break; 1325 case "labelcolor": 1326 value = Color.rgba2rgbo(value); 1327 opacity = value[1]; 1328 value = value[0]; 1329 if (opacity === 0) { 1330 if (Type.exists(this.label) && this.hasLabel) { 1331 this.label.hideElement(); 1332 } 1333 } 1334 if (Type.exists(this.label) && this.hasLabel) { 1335 this.label.visProp.strokecolor = value; 1336 this.board.renderer.setObjectStrokeColor( 1337 this.label, 1338 value, 1339 opacity 1340 ); 1341 } 1342 if (this.elementClass === Const.OBJECT_CLASS_TEXT) { 1343 this.visProp.strokecolor = value; 1344 this.visProp.strokeopacity = opacity; 1345 this.board.renderer.setObjectStrokeColor(this, value, opacity); 1346 } 1347 break; 1348 case "layer": 1349 this.board.renderer.setLayer(this, Type.evaluate(value)); 1350 this._set(key, value); 1351 break; 1352 case "maxlength": 1353 // input. Is not available on initial call. 1354 if (Type.exists(this.rendNodeTag)) { 1355 this.rendNodeTag.maxlength = !!value; 1356 } 1357 break; 1358 case "name": 1359 oldvalue = this.name; 1360 delete this.board.elementsByName[this.name]; 1361 this.name = value; 1362 this.board.elementsByName[this.name] = this; 1363 break; 1364 case "needsregularupdate": 1365 this.needsRegularUpdate = !(value === "false" || value === false); 1366 this.board.renderer.setBuffering( 1367 this, 1368 this.needsRegularUpdate ? "auto" : "static" 1369 ); 1370 break; 1371 case "onpolygon": 1372 if (this.type === Const.OBJECT_TYPE_GLIDER) { 1373 this.onPolygon = !!value; 1374 } 1375 break; 1376 case "radius": 1377 if ( 1378 this.type === Const.OBJECT_TYPE_ANGLE || 1379 this.type === Const.OBJECT_TYPE_SECTOR 1380 ) { 1381 this.setRadius(value); 1382 } 1383 break; 1384 case "rotate": 1385 if ( 1386 (this.elementClass === Const.OBJECT_CLASS_TEXT && 1387 Type.evaluate(this.visProp.display) === "internal") || 1388 this.type === Const.OBJECT_TYPE_IMAGE 1389 ) { 1390 this.addRotation(value); 1391 } 1392 break; 1393 case "tabindex": 1394 if (Type.exists(this.rendNode)) { 1395 this.rendNode.setAttribute("tabindex", value); 1396 this._set(key, value); 1397 } 1398 break; 1399 // case "ticksdistance": 1400 // if (this.type === Const.OBJECT_TYPE_TICKS && Type.isNumber(value)) { 1401 // this.ticksFunction = this.makeTicksFunction(value); 1402 // } 1403 // break; 1404 case "trace": 1405 if (value === "false" || value === false) { 1406 this.clearTrace(); 1407 this.visProp.trace = false; 1408 } else if (value === "pause") { 1409 this.visProp.trace = false; 1410 } else { 1411 this.visProp.trace = true; 1412 } 1413 break; 1414 case "visible": 1415 if (value === "false") { 1416 this.visProp.visible = false; 1417 } else if (value === "true") { 1418 this.visProp.visible = true; 1419 } else { 1420 this.visProp.visible = value; 1421 } 1422 1423 this.setDisplayRendNode(Type.evaluate(this.visProp.visible)); 1424 if ( 1425 Type.evaluate(this.visProp.visible) && 1426 Type.exists(this.updateSize) 1427 ) { 1428 this.updateSize(); 1429 } 1430 1431 break; 1432 case "withlabel": 1433 this.visProp.withlabel = value; 1434 if (!Type.evaluate(value)) { 1435 if (this.label && this.hasLabel) { 1436 //this.label.hideElement(); 1437 this.label.setAttribute({ visible: false }); 1438 } 1439 } else { 1440 if (!this.label) { 1441 this.createLabel(); 1442 } 1443 //this.label.showElement(); 1444 this.label.setAttribute({ visible: "inherit" }); 1445 //this.label.setDisplayRendNode(Type.evaluate(this.visProp.visible)); 1446 } 1447 this.hasLabel = value; 1448 break; 1449 default: 1450 if ( 1451 Type.exists(this.visProp[key]) && 1452 (!JXG.Validator[key] || 1453 (JXG.Validator[key] && JXG.Validator[key](value)) || 1454 (JXG.Validator[key] && 1455 Type.isFunction(value) && 1456 JXG.Validator[key](value()))) 1457 ) { 1458 value = 1459 (value.toLowerCase && value.toLowerCase() === "false") 1460 ? false 1461 : value; 1462 this._set(key, value); 1463 } 1464 break; 1465 } 1466 this.triggerEventHandlers(["attribute:" + key], [oldvalue, value, this]); 1467 } 1468 } 1469 1470 this.triggerEventHandlers(["attribute"], [attributes, this]); 1471 1472 if (!Type.evaluate(this.visProp.needsregularupdate)) { 1473 this.board.fullUpdate(); 1474 } else { 1475 this.board.update(this); 1476 } 1477 if (this.elementClass === Const.OBJECT_CLASS_TEXT) { 1478 this.updateSize(); 1479 } 1480 1481 return this; 1482 }, 1483 1484 /** 1485 * Deprecated alias for {@link JXG.GeometryElement#getAttribute}. 1486 * @deprecated Use {@link JXG.GeometryElement#getAttribute}. 1487 */ 1488 getProperty: function () { 1489 JXG.deprecated("getProperty()", "getAttribute()"); 1490 this.getProperty.apply(this, arguments); 1491 }, 1492 1493 /** 1494 * Get the value of the property <tt>key</tt>. 1495 * @param {String} key The name of the property you are looking for 1496 * @returns The value of the property 1497 */ 1498 getAttribute: function (key) { 1499 var result; 1500 key = key.toLowerCase(); 1501 1502 switch (key) { 1503 case "needsregularupdate": 1504 result = this.needsRegularUpdate; 1505 break; 1506 case "labelcolor": 1507 result = this.label.visProp.strokecolor; 1508 break; 1509 case "infoboxtext": 1510 result = this.infoboxText; 1511 break; 1512 case "withlabel": 1513 result = this.hasLabel; 1514 break; 1515 default: 1516 result = this.visProp[key]; 1517 break; 1518 } 1519 1520 return result; 1521 }, 1522 1523 /** 1524 * Set the dash style of an object. See {@link JXG.GeometryElement#dash} 1525 * for a list of available dash styles. 1526 * You should use {@link JXG.GeometryElement#setAttribute} instead of this method. 1527 * 1528 * @param {number} dash Indicates the new dash style 1529 * @private 1530 */ 1531 setDash: function (dash) { 1532 this.setAttribute({ dash: dash }); 1533 return this; 1534 }, 1535 1536 /** 1537 * Notify all child elements for updates. 1538 * @private 1539 */ 1540 prepareUpdate: function () { 1541 this.needsUpdate = true; 1542 return this; 1543 }, 1544 1545 /** 1546 * Removes the element from the construction. This only removes the SVG or VML node of the element and its label (if available) from 1547 * the renderer, to remove the element completely you should use {@link JXG.Board#removeObject}. 1548 */ 1549 remove: function () { 1550 // this.board.renderer.remove(this.board.renderer.getElementById(this.id)); 1551 this.board.renderer.remove(this.rendNode); 1552 1553 if (this.hasLabel) { 1554 this.board.renderer.remove(this.board.renderer.getElementById(this.label.id)); 1555 } 1556 return this; 1557 }, 1558 1559 /** 1560 * Returns the coords object where a text that is bound to the element shall be drawn. 1561 * Differs in some cases from the values that getLabelAnchor returns. 1562 * @returns {JXG.Coords} JXG.Coords Place where the text shall be drawn. 1563 * @see JXG.GeometryElement#getLabelAnchor 1564 */ 1565 getTextAnchor: function () { 1566 return new Coords(Const.COORDS_BY_USER, [0, 0], this.board); 1567 }, 1568 1569 /** 1570 * Returns the coords object where the label of the element shall be drawn. 1571 * Differs in some cases from the values that getTextAnchor returns. 1572 * @returns {JXG.Coords} JXG.Coords Place where the text shall be drawn. 1573 * @see JXG.GeometryElement#getTextAnchor 1574 */ 1575 getLabelAnchor: function () { 1576 return new Coords(Const.COORDS_BY_USER, [0, 0], this.board); 1577 }, 1578 1579 /** 1580 * Determines whether the element has arrows at start or end of the arc. 1581 * If it is set to be a "typical" vector, ie lastArrow == true, 1582 * then the element.type is set to VECTOR. 1583 * @param {Boolean} firstArrow True if there is an arrow at the start of the arc, false otherwise. 1584 * @param {Boolean} lastArrow True if there is an arrow at the end of the arc, false otherwise. 1585 */ 1586 setArrow: function (firstArrow, lastArrow) { 1587 this.visProp.firstarrow = firstArrow; 1588 this.visProp.lastarrow = lastArrow; 1589 if (lastArrow) { 1590 this.type = Const.OBJECT_TYPE_VECTOR; 1591 this.elType = "arrow"; 1592 } 1593 1594 this.prepareUpdate().update().updateVisibility().updateRenderer(); 1595 return this; 1596 }, 1597 1598 /** 1599 * Creates a gradient nodes in the renderer. 1600 * @see JXG.SVGRenderer#setGradient 1601 * @private 1602 */ 1603 createGradient: function () { 1604 var ev_g = Type.evaluate(this.visProp.gradient); 1605 if (ev_g === "linear" || ev_g === "radial") { 1606 this.board.renderer.setGradient(this); 1607 } 1608 }, 1609 1610 /** 1611 * Creates a label element for this geometry element. 1612 * @see #addLabelToElement 1613 */ 1614 createLabel: function () { 1615 var attr, 1616 that = this; 1617 1618 // this is a dirty hack to resolve the text-dependency. If there is no text element available, 1619 // just don't create a label. This method is usually not called by a user, so we won't throw 1620 // an exception here and simply output a warning via JXG.debug. 1621 if (JXG.elements.text) { 1622 attr = Type.deepCopy(this.visProp.label, null); 1623 attr.id = this.id + "Label"; 1624 attr.isLabel = true; 1625 attr.anchor = this; 1626 attr.priv = this.visProp.priv; 1627 1628 if (this.visProp.withlabel) { 1629 this.label = JXG.elements.text( 1630 this.board, 1631 [ 1632 0, 1633 0, 1634 function () { 1635 if (Type.isFunction(that.name)) { 1636 return that.name(); 1637 } 1638 return that.name; 1639 } 1640 ], 1641 attr 1642 ); 1643 this.label.needsUpdate = true; 1644 this.label.dump = false; 1645 this.label.fullUpdate(); 1646 1647 this.hasLabel = true; 1648 } 1649 } else { 1650 JXG.debug( 1651 "JSXGraph: Can't create label: text element is not available. Make sure you include base/text" 1652 ); 1653 } 1654 1655 return this; 1656 }, 1657 1658 /** 1659 * Highlights the element. 1660 * @private 1661 * @param {Boolean} [force=false] Force the highlighting 1662 * @returns {JXG.Board} 1663 */ 1664 highlight: function (force) { 1665 force = Type.def(force, false); 1666 // I know, we have the JXG.Board.highlightedObjects AND JXG.GeometryElement.highlighted and YES we need both. 1667 // Board.highlightedObjects is for the internal highlighting and GeometryElement.highlighted is for user highlighting 1668 // initiated by the user, e.g. through custom DOM events. We can't just pick one because this would break user 1669 // defined highlighting in many ways: 1670 // * if overriding the highlight() methods the user had to handle the highlightedObjects stuff, otherwise he'd break 1671 // everything (e.g. the pie chart example https://jsxgraph.org/wiki/index.php/Pie_chart (not exactly 1672 // user defined but for this type of chart the highlight method was overridden and not adjusted to the changes in here) 1673 // where it just kept highlighting until the radius of the pie was far beyond infinity... 1674 // * user defined highlighting would get pointless, everytime the user highlights something using .highlight(), it would get 1675 // dehighlighted immediately, because highlight puts the element into highlightedObjects and from there it gets dehighlighted 1676 // through dehighlightAll. 1677 1678 // highlight only if not highlighted 1679 if (Type.evaluate(this.visProp.highlight) && (!this.highlighted || force)) { 1680 this.highlighted = true; 1681 this.board.highlightedObjects[this.id] = this; 1682 this.board.renderer.highlight(this); 1683 } 1684 return this; 1685 }, 1686 1687 /** 1688 * Uses the "normal" properties of the element. 1689 * @returns {JXG.Board} 1690 */ 1691 noHighlight: function () { 1692 // see comment in JXG.GeometryElement.highlight() 1693 1694 // dehighlight only if not highlighted 1695 if (this.highlighted) { 1696 this.highlighted = false; 1697 delete this.board.highlightedObjects[this.id]; 1698 this.board.renderer.noHighlight(this); 1699 } 1700 return this; 1701 }, 1702 1703 /** 1704 * Removes all objects generated by the trace function. 1705 */ 1706 clearTrace: function () { 1707 var obj; 1708 1709 for (obj in this.traces) { 1710 if (this.traces.hasOwnProperty(obj)) { 1711 this.board.renderer.remove(this.traces[obj]); 1712 } 1713 } 1714 1715 this.numTraces = 0; 1716 return this; 1717 }, 1718 1719 /** 1720 * Copy the element to background. This is used for tracing elements. 1721 * @returns {JXG.GeometryElement} A reference to the element 1722 */ 1723 cloneToBackground: function () { 1724 return this; 1725 }, 1726 1727 /** 1728 * Dimensions of the smallest rectangle enclosing the element. 1729 * @returns {Array} The coordinates of the enclosing rectangle in a format 1730 * like the bounding box in {@link JXG.Board#setBoundingBox}. 1731 * 1732 * @returns {Array} similar to {@link JXG.Board#setBoundingBox}. 1733 */ 1734 bounds: function () { 1735 return [0, 0, 0, 0]; 1736 }, 1737 1738 /** 1739 * Normalize the element's standard form. 1740 * @private 1741 */ 1742 normalize: function () { 1743 this.stdform = Mat.normalize(this.stdform); 1744 return this; 1745 }, 1746 1747 /** 1748 * EXPERIMENTAL. Generate JSON object code of visProp and other properties. 1749 * @type String 1750 * @private 1751 * @ignore 1752 * @returns JSON string containing element's properties. 1753 */ 1754 toJSON: function () { 1755 var vis, 1756 key, 1757 json = ['{"name":', this.name]; 1758 1759 json.push(", " + '"id":' + this.id); 1760 1761 vis = []; 1762 for (key in this.visProp) { 1763 if (this.visProp.hasOwnProperty(key)) { 1764 if (Type.exists(this.visProp[key])) { 1765 vis.push('"' + key + '":' + this.visProp[key]); 1766 } 1767 } 1768 } 1769 json.push(', "visProp":{' + vis.toString() + "}"); 1770 json.push("}"); 1771 1772 return json.join(""); 1773 }, 1774 1775 /** 1776 * Rotate texts or images by a given degree. 1777 * @param {number} angle The degree of the rotation (90 means vertical text). 1778 * @see JXG.GeometryElement#rotate 1779 */ 1780 addRotation: function (angle) { 1781 var tOffInv, 1782 tOff, 1783 tS, 1784 tSInv, 1785 tRot, 1786 that = this; 1787 1788 if ( 1789 (this.elementClass === Const.OBJECT_CLASS_TEXT || 1790 this.type === Const.OBJECT_TYPE_IMAGE) && 1791 angle !== 0 1792 ) { 1793 tOffInv = this.board.create( 1794 "transform", 1795 [ 1796 function () { 1797 return -that.X(); 1798 }, 1799 function () { 1800 return -that.Y(); 1801 } 1802 ], 1803 { type: "translate" } 1804 ); 1805 1806 tOff = this.board.create( 1807 "transform", 1808 [ 1809 function () { 1810 return that.X(); 1811 }, 1812 function () { 1813 return that.Y(); 1814 } 1815 ], 1816 { type: "translate" } 1817 ); 1818 1819 tS = this.board.create( 1820 "transform", 1821 [ 1822 function () { 1823 return that.board.unitX / that.board.unitY; 1824 }, 1825 function () { 1826 return 1; 1827 } 1828 ], 1829 { type: "scale" } 1830 ); 1831 1832 tSInv = this.board.create( 1833 "transform", 1834 [ 1835 function () { 1836 return that.board.unitY / that.board.unitX; 1837 }, 1838 function () { 1839 return 1; 1840 } 1841 ], 1842 { type: "scale" } 1843 ); 1844 1845 tRot = this.board.create( 1846 "transform", 1847 [ 1848 function () { 1849 return (Type.evaluate(angle) * Math.PI) / 180; 1850 } 1851 ], 1852 { type: "rotate" } 1853 ); 1854 1855 tOffInv.bindTo(this); 1856 tS.bindTo(this); 1857 tRot.bindTo(this); 1858 tSInv.bindTo(this); 1859 tOff.bindTo(this); 1860 } 1861 1862 return this; 1863 }, 1864 1865 /** 1866 * Set the highlightStrokeColor of an element 1867 * @ignore 1868 * @name JXG.GeometryElement#highlightStrokeColorMethod 1869 * @param {String} sColor String which determines the stroke color of an object when its highlighted. 1870 * @see JXG.GeometryElement#highlightStrokeColor 1871 * @deprecated Use {@link JXG.GeometryElement#setAttribute} 1872 */ 1873 highlightStrokeColor: function (sColor) { 1874 JXG.deprecated("highlightStrokeColor()", "setAttribute()"); 1875 this.setAttribute({ highlightStrokeColor: sColor }); 1876 return this; 1877 }, 1878 1879 /** 1880 * Set the strokeColor of an element 1881 * @ignore 1882 * @name JXG.GeometryElement#strokeColorMethod 1883 * @param {String} sColor String which determines the stroke color of an object. 1884 * @see JXG.GeometryElement#strokeColor 1885 * @deprecated Use {@link JXG.GeometryElement#setAttribute} 1886 */ 1887 strokeColor: function (sColor) { 1888 JXG.deprecated("strokeColor()", "setAttribute()"); 1889 this.setAttribute({ strokeColor: sColor }); 1890 return this; 1891 }, 1892 1893 /** 1894 * Set the strokeWidth of an element 1895 * @ignore 1896 * @name JXG.GeometryElement#strokeWidthMethod 1897 * @param {Number} width Integer which determines the stroke width of an outline. 1898 * @see JXG.GeometryElement#strokeWidth 1899 * @deprecated Use {@link JXG.GeometryElement#setAttribute} 1900 */ 1901 strokeWidth: function (width) { 1902 JXG.deprecated("strokeWidth()", "setAttribute()"); 1903 this.setAttribute({ strokeWidth: width }); 1904 return this; 1905 }, 1906 1907 /** 1908 * Set the fillColor of an element 1909 * @ignore 1910 * @name JXG.GeometryElement#fillColorMethod 1911 * @param {String} fColor String which determines the fill color of an object. 1912 * @see JXG.GeometryElement#fillColor 1913 * @deprecated Use {@link JXG.GeometryElement#setAttribute} 1914 */ 1915 fillColor: function (fColor) { 1916 JXG.deprecated("fillColor()", "setAttribute()"); 1917 this.setAttribute({ fillColor: fColor }); 1918 return this; 1919 }, 1920 1921 /** 1922 * Set the highlightFillColor of an element 1923 * @ignore 1924 * @name JXG.GeometryElement#highlightFillColorMethod 1925 * @param {String} fColor String which determines the fill color of an object when its highlighted. 1926 * @see JXG.GeometryElement#highlightFillColor 1927 * @deprecated Use {@link JXG.GeometryElement#setAttribute} 1928 */ 1929 highlightFillColor: function (fColor) { 1930 JXG.deprecated("highlightFillColor()", "setAttribute()"); 1931 this.setAttribute({ highlightFillColor: fColor }); 1932 return this; 1933 }, 1934 1935 /** 1936 * Set the labelColor of an element 1937 * @ignore 1938 * @param {String} lColor String which determines the text color of an object's label. 1939 * @see JXG.GeometryElement#labelColor 1940 * @deprecated Use {@link JXG.GeometryElement#setAttribute} 1941 */ 1942 labelColor: function (lColor) { 1943 JXG.deprecated("labelColor()", "setAttribute()"); 1944 this.setAttribute({ labelColor: lColor }); 1945 return this; 1946 }, 1947 1948 /** 1949 * Set the dash type of an element 1950 * @ignore 1951 * @name JXG.GeometryElement#dashMethod 1952 * @param {Number} d Integer which determines the way of dashing an element's outline. 1953 * @see JXG.GeometryElement#dash 1954 * @deprecated Use {@link JXG.GeometryElement#setAttribute} 1955 */ 1956 dash: function (d) { 1957 JXG.deprecated("dash()", "setAttribute()"); 1958 this.setAttribute({ dash: d }); 1959 return this; 1960 }, 1961 1962 /** 1963 * Set the visibility of an element 1964 * @ignore 1965 * @name JXG.GeometryElement#visibleMethod 1966 * @param {Boolean} v Boolean which determines whether the element is drawn. 1967 * @see JXG.GeometryElement#visible 1968 * @deprecated Use {@link JXG.GeometryElement#setAttribute} 1969 */ 1970 visible: function (v) { 1971 JXG.deprecated("visible()", "setAttribute()"); 1972 this.setAttribute({ visible: v }); 1973 return this; 1974 }, 1975 1976 /** 1977 * Set the shadow of an element 1978 * @ignore 1979 * @name JXG.GeometryElement#shadowMethod 1980 * @param {Boolean} s Boolean which determines whether the element has a shadow or not. 1981 * @see JXG.GeometryElement#shadow 1982 * @deprecated Use {@link JXG.GeometryElement#setAttribute} 1983 */ 1984 shadow: function (s) { 1985 JXG.deprecated("shadow()", "setAttribute()"); 1986 this.setAttribute({ shadow: s }); 1987 return this; 1988 }, 1989 1990 /** 1991 * The type of the element as used in {@link JXG.Board#create}. 1992 * @returns {String} 1993 */ 1994 getType: function () { 1995 return this.elType; 1996 }, 1997 1998 /** 1999 * List of the element ids resp. values used as parents in {@link JXG.Board#create}. 2000 * @returns {Array} 2001 */ 2002 getParents: function () { 2003 return Type.isArray(this.parents) ? this.parents : []; 2004 }, 2005 2006 /** 2007 * @ignore 2008 * @private 2009 * Snaps the element to the grid. Only works for points, lines and circles. Points will snap to the grid 2010 * as defined in their properties {@link JXG.Point#snapSizeX} and {@link JXG.Point#snapSizeY}. Lines and circles 2011 * will snap their parent points to the grid, if they have {@link JXG.Point#snapToGrid} set to true. 2012 * @returns {JXG.GeometryElement} Reference to the element. 2013 */ 2014 snapToGrid: function () { 2015 return this; 2016 }, 2017 2018 /** 2019 * Snaps the element to points. Only works for points. Points will snap to the next point 2020 * as defined in their properties {@link JXG.Point#attractorDistance} and {@link JXG.Point#attractorUnit}. 2021 * Lines and circles 2022 * will snap their parent points to points. 2023 * @private 2024 * @returns {JXG.GeometryElement} Reference to the element. 2025 */ 2026 snapToPoints: function () { 2027 return this; 2028 }, 2029 2030 /** 2031 * Retrieve a copy of the current visProp. 2032 * @returns {Object} 2033 */ 2034 getAttributes: function () { 2035 var attributes = Type.deepCopy(this.visProp), 2036 /* 2037 cleanThis = ['attractors', 'snatchdistance', 'traceattributes', 'frozen', 2038 'shadow', 'gradientangle', 'gradientsecondopacity', 'gradientpositionx', 'gradientpositiony', 2039 'needsregularupdate', 'zoom', 'layer', 'offset'], 2040 */ 2041 cleanThis = [], 2042 i, 2043 len = cleanThis.length; 2044 2045 attributes.id = this.id; 2046 attributes.name = this.name; 2047 2048 for (i = 0; i < len; i++) { 2049 delete attributes[cleanThis[i]]; 2050 } 2051 2052 return attributes; 2053 }, 2054 2055 /** 2056 * Checks whether (x,y) is near the element. 2057 * @param {Number} x Coordinate in x direction, screen coordinates. 2058 * @param {Number} y Coordinate in y direction, screen coordinates. 2059 * @returns {Boolean} True if (x,y) is near the element, False otherwise. 2060 */ 2061 hasPoint: function (x, y) { 2062 return false; 2063 }, 2064 2065 /** 2066 * Adds ticks to this line or curve. Ticks can be added to a curve or any kind of line: line, arrow, and axis. 2067 * @param {JXG.Ticks} ticks Reference to a ticks object which is describing the ticks (color, distance, how many, etc.). 2068 * @returns {String} Id of the ticks object. 2069 */ 2070 addTicks: function (ticks) { 2071 if (ticks.id === "" || !Type.exists(ticks.id)) { 2072 ticks.id = this.id + "_ticks_" + (this.ticks.length + 1); 2073 } 2074 2075 this.board.renderer.drawTicks(ticks); 2076 this.ticks.push(ticks); 2077 2078 return ticks.id; 2079 }, 2080 2081 /** 2082 * Removes all ticks from a line or curve. 2083 */ 2084 removeAllTicks: function () { 2085 var t; 2086 if (Type.exists(this.ticks)) { 2087 for (t = this.ticks.length - 1; t >= 0; t--) { 2088 this.removeTicks(this.ticks[t]); 2089 } 2090 this.ticks = []; 2091 this.board.update(); 2092 } 2093 }, 2094 2095 /** 2096 * Removes ticks identified by parameter named tick from this line or curve. 2097 * @param {JXG.Ticks} tick Reference to tick object to remove. 2098 */ 2099 removeTicks: function (tick) { 2100 var t, j; 2101 2102 if (Type.exists(this.defaultTicks) && this.defaultTicks === tick) { 2103 this.defaultTicks = null; 2104 } 2105 2106 if (Type.exists(this.ticks)) { 2107 for (t = this.ticks.length - 1; t >= 0; t--) { 2108 if (this.ticks[t] === tick) { 2109 this.board.removeObject(this.ticks[t]); 2110 2111 if (this.ticks[t].ticks) { 2112 for (j = 0; j < this.ticks[t].ticks.length; j++) { 2113 if (Type.exists(this.ticks[t].labels[j])) { 2114 this.board.removeObject(this.ticks[t].labels[j]); 2115 } 2116 } 2117 } 2118 2119 delete this.ticks[t]; 2120 break; 2121 } 2122 } 2123 } 2124 }, 2125 2126 /** 2127 * Determine values of snapSizeX and snapSizeY. If the attributes 2128 * snapSizex and snapSizeY are greater than zero, these values are taken. 2129 * Otherwise, determine the distance between major ticks of the 2130 * default axes. 2131 * @returns {Array} containing the snap sizes for x and y direction. 2132 * @private 2133 */ 2134 getSnapSizes: function () { 2135 var sX, sY, ticks; 2136 2137 sX = Type.evaluate(this.visProp.snapsizex); 2138 sY = Type.evaluate(this.visProp.snapsizey); 2139 2140 if (sX <= 0 && this.board.defaultAxes && this.board.defaultAxes.x.defaultTicks) { 2141 ticks = this.board.defaultAxes.x.defaultTicks; 2142 sX = ticks.ticksDelta * (Type.evaluate(ticks.visProp.minorticks) + 1); 2143 } 2144 2145 if (sY <= 0 && this.board.defaultAxes && this.board.defaultAxes.y.defaultTicks) { 2146 ticks = this.board.defaultAxes.y.defaultTicks; 2147 sY = ticks.ticksDelta * (Type.evaluate(ticks.visProp.minorticks) + 1); 2148 } 2149 2150 return [sX, sY]; 2151 }, 2152 2153 /** 2154 * Move an element to its nearest grid point. 2155 * The function uses the coords object of the element as 2156 * its actual position. If there is no coords object or if the object is fixed, nothing is done. 2157 * @param {Boolean} force force snapping independent from what the snaptogrid attribute says 2158 * @param {Boolean} fromParent True if the drag comes from a child element. This is the case if a line 2159 * through two points is dragged. In this case we do not try to force the points to stay inside of 2160 * the visible board, but the distance between the two points stays constant. 2161 * @returns {JXG.GeometryElement} Reference to this element 2162 */ 2163 handleSnapToGrid: function (force, fromParent) { 2164 var x, y, rx, ry, rcoords, 2165 mi, ma, 2166 boardBB, res, sX, sY, 2167 needsSnapToGrid = false, 2168 attractToGrid = Type.evaluate(this.visProp.attracttogrid), 2169 ev_au = Type.evaluate(this.visProp.attractorunit), 2170 ev_ad = Type.evaluate(this.visProp.attractordistance); 2171 2172 if (!Type.exists(this.coords) || Type.evaluate(this.visProp.fixed)) { 2173 return this; 2174 } 2175 2176 needsSnapToGrid = 2177 Type.evaluate(this.visProp.snaptogrid) || attractToGrid || force === true; 2178 2179 if (needsSnapToGrid) { 2180 x = this.coords.usrCoords[1]; 2181 y = this.coords.usrCoords[2]; 2182 res = this.getSnapSizes(); 2183 sX = res[0]; 2184 sY = res[1]; 2185 2186 // If no valid snap sizes are available, don't change the coords. 2187 if (sX > 0 && sY > 0) { 2188 boardBB = this.board.getBoundingBox(); 2189 rx = Math.round(x / sX) * sX; 2190 ry = Math.round(y / sY) * sY; 2191 2192 rcoords = new JXG.Coords(Const.COORDS_BY_USER, [rx, ry], this.board); 2193 if ( 2194 !attractToGrid || 2195 rcoords.distance( 2196 ev_au === "screen" ? Const.COORDS_BY_SCREEN : Const.COORDS_BY_USER, 2197 this.coords 2198 ) < ev_ad 2199 ) { 2200 x = rx; 2201 y = ry; 2202 // Checking whether x and y are still within boundingBox. 2203 // If not, adjust them to remain within the board. 2204 // Otherwise a point may become invisible. 2205 if (!fromParent) { 2206 mi = Math.min(boardBB[0], boardBB[2]); 2207 ma = Math.max(boardBB[0], boardBB[2]); 2208 if (x < mi && x > mi - sX) { 2209 x += sX; 2210 } else if (x > ma && x < ma + sX) { 2211 x -= sX; 2212 } 2213 2214 mi = Math.min(boardBB[1], boardBB[3]); 2215 ma = Math.max(boardBB[1], boardBB[3]); 2216 if (y < mi && y > mi - sY) { 2217 y += sY; 2218 } else if (y > ma && y < ma + sY) { 2219 y -= sY; 2220 } 2221 } 2222 this.coords.setCoordinates(Const.COORDS_BY_USER, [x, y]); 2223 } 2224 } 2225 } 2226 return this; 2227 }, 2228 2229 getBoundingBox: function () { 2230 var i, 2231 le, 2232 v, 2233 x, 2234 y, 2235 bb = [Infinity, Infinity, -Infinity, -Infinity]; 2236 2237 if (this.type === Const.OBJECT_TYPE_POLYGON) { 2238 le = this.vertices.length - 1; 2239 if (le <= 0) { 2240 return bb; 2241 } 2242 for (i = 0; i < le; i++) { 2243 v = this.vertices[i].X(); 2244 bb[0] = v < bb[0] ? v : bb[0]; 2245 bb[2] = v > bb[2] ? v : bb[2]; 2246 v = this.vertices[i].Y(); 2247 bb[1] = v < bb[1] ? v : bb[1]; 2248 bb[3] = v > bb[3] ? v : bb[3]; 2249 } 2250 } else if (this.elementClass === Const.OBJECT_CLASS_CIRCLE) { 2251 x = this.center.X(); 2252 y = this.center.Y(); 2253 bb = [x - this.radius, y + this.radius, x + this.radius, y - this.radius]; 2254 } else if (this.elementClass === Const.OBJECT_CLASS_CURVE) { 2255 le = this.vertices.length; 2256 if (le === 0) { 2257 return bb; 2258 } 2259 for (i = 0; i < le; i++) { 2260 v = this.points[i].coords.usrCoords[1]; 2261 bb[0] = v < bb[0] ? v : bb[0]; 2262 bb[2] = v > bb[2] ? v : bb[2]; 2263 v = this.points[i].coords.usrCoords[1]; 2264 bb[1] = v < bb[1] ? v : bb[1]; 2265 bb[3] = v > bb[3] ? v : bb[3]; 2266 } 2267 } 2268 2269 return bb; 2270 }, 2271 2272 /** 2273 * Alias of {@link JXG.EventEmitter.on}. 2274 * 2275 * @name addEvent 2276 * @memberof JXG.GeometryElement 2277 * @function 2278 */ 2279 addEvent: JXG.shortcut(JXG.GeometryElement.prototype, 'on'), 2280 2281 /** 2282 * Alias of {@link JXG.EventEmitter.off}. 2283 * 2284 * @name removeEvent 2285 * @memberof JXG.GeometryElement 2286 * @function 2287 */ 2288 removeEvent: JXG.shortcut(JXG.GeometryElement.prototype, 'off'), 2289 2290 /** 2291 * Format a number according to the locale set in the attribute "intl". 2292 * If in the options of the intl-attribute "maximumFractionDigits" is not set, 2293 * the optional parameter digits is used instead. 2294 * See <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat">https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat</a> 2295 * for more information about internationalization. 2296 * 2297 * @param {Number} value Number to be formatted 2298 * @param {Number} [digits=undefined] Optional number of digits 2299 * @returns {String|Number} string containing the formatted number according to the locale 2300 * or the number itself of the formatting is not possible. 2301 */ 2302 formatNumberLocale: function (value, digits) { 2303 var loc, opt, key, 2304 optCalc = {}, 2305 // These options are case sensitive: 2306 translate = { 2307 maximumfractiondigits: 'maximumFractionDigits', 2308 minimumfractiondigits: 'minimumFractionDigits', 2309 compactdisplay: 'compactDisplay', 2310 currencydisplay: 'currencyDisplay', 2311 currencysign: 'currencySign', 2312 localematcher: 'localeMatcher', 2313 numberingsystem: 'numberingSystem', 2314 signdisplay: 'signDisplay', 2315 unitdisplay: 'unitDisplay', 2316 usegrouping: 'useGrouping', 2317 roundingmode: 'roundingMode', 2318 roundingpriority: 'roundingPriority', 2319 roundingincrement: 'roundingIncrement', 2320 trailingzerodisplay: 'trailingZeroDisplay', 2321 minimumintegerdigits: 'minimumIntegerDigits', 2322 minimumsignificantdigits: 'minimumSignificantDigits', 2323 maximumsignificantdigits: 'maximumSignificantDigits' 2324 }; 2325 2326 if (Type.exists(Intl) && 2327 this.useLocale()) { 2328 2329 loc = Type.evaluate(this.visProp.intl.locale) || 2330 Type.evaluate(this.board.attr.intl.locale); 2331 opt = Type.evaluate(this.visProp.intl.options) || {}; 2332 2333 // Transfer back to camel case if necessary 2334 // and evaluate 2335 for (key in opt) { 2336 if (opt.hasOwnProperty(key)) { 2337 if (translate.hasOwnProperty(key)) { 2338 optCalc[translate[key]] = Type.evaluate(opt[key]); 2339 } else { 2340 optCalc[key] = Type.evaluate(opt[key]); 2341 } 2342 } 2343 } 2344 2345 // If maximumfractiondigits is not set, 2346 // the value of the attribute "digits" is taken instead. 2347 key = 'maximumfractiondigits'; 2348 if (!Type.exists(opt[key])) { 2349 optCalc[translate[key]] = digits; 2350 2351 // key = 'minimumfractiondigits'; 2352 // if (!Type.exists(opt[key]) || Type.evaluate(opt[key]) > digits) { 2353 // optCalc[translate[key]] = digits; 2354 // } 2355 } 2356 2357 return Intl.NumberFormat(loc, optCalc).format(value); 2358 } 2359 2360 return value; 2361 }, 2362 2363 /** 2364 * Checks if locale is enabled in the attribute. This may be in the attributes of the board, 2365 * or in the attributes of the text. The latter has higher priority. The board attribute is taken if 2366 * attribute "intl.enabled" of the text element is set to 'inherit'. 2367 * 2368 * @returns {Boolean} if locale can be used for number formatting. 2369 */ 2370 useLocale: function () { 2371 var val; 2372 2373 // Check if element supports intl 2374 if (!Type.exists(this.visProp.intl) || 2375 !Type.exists(this.visProp.intl.enabled)) { 2376 return false; 2377 } 2378 2379 // Check if intl is supported explicitly enabled for this element 2380 val = Type.evaluate(this.visProp.intl.enabled); 2381 2382 if (val === true) { 2383 return true; 2384 } 2385 2386 // Check intl attribute of the board 2387 if (val === 'inherit') { 2388 if (Type.evaluate(this.board.attr.intl.enabled) === true) { 2389 return true; 2390 } 2391 } 2392 2393 return false; 2394 }, 2395 2396 /* ************************** 2397 * EVENT DEFINITION 2398 * for documentation purposes 2399 * ************************** */ 2400 2401 //region Event handler documentation 2402 /** 2403 * @event 2404 * @description This event is fired whenever the user is hovering over an element. 2405 * @name JXG.GeometryElement#over 2406 * @param {Event} e The browser's event object. 2407 */ 2408 __evt__over: function (e) { }, 2409 2410 /** 2411 * @event 2412 * @description This event is fired whenever the user puts the mouse over an element. 2413 * @name JXG.GeometryElement#mouseover 2414 * @param {Event} e The browser's event object. 2415 */ 2416 __evt__mouseover: function (e) { }, 2417 2418 /** 2419 * @event 2420 * @description This event is fired whenever the user is leaving an element. 2421 * @name JXG.GeometryElement#out 2422 * @param {Event} e The browser's event object. 2423 */ 2424 __evt__out: function (e) { }, 2425 2426 /** 2427 * @event 2428 * @description This event is fired whenever the user puts the mouse away from an element. 2429 * @name JXG.GeometryElement#mouseout 2430 * @param {Event} e The browser's event object. 2431 */ 2432 __evt__mouseout: function (e) { }, 2433 2434 /** 2435 * @event 2436 * @description This event is fired whenever the user is moving over an element. 2437 * @name JXG.GeometryElement#move 2438 * @param {Event} e The browser's event object. 2439 */ 2440 __evt__move: function (e) { }, 2441 2442 /** 2443 * @event 2444 * @description This event is fired whenever the user is moving the mouse over an element. 2445 * @name JXG.GeometryElement#mousemove 2446 * @param {Event} e The browser's event object. 2447 */ 2448 __evt__mousemove: function (e) { }, 2449 2450 /** 2451 * @event 2452 * @description This event is fired whenever the user drags an element. 2453 * @name JXG.GeometryElement#drag 2454 * @param {Event} e The browser's event object. 2455 */ 2456 __evt__drag: function (e) { }, 2457 2458 /** 2459 * @event 2460 * @description This event is fired whenever the user drags the element with a mouse. 2461 * @name JXG.GeometryElement#mousedrag 2462 * @param {Event} e The browser's event object. 2463 */ 2464 __evt__mousedrag: function (e) { }, 2465 2466 /** 2467 * @event 2468 * @description This event is fired whenever the user drags the element with a pen. 2469 * @name JXG.GeometryElement#pendrag 2470 * @param {Event} e The browser's event object. 2471 */ 2472 __evt__pendrag: function (e) { }, 2473 2474 /** 2475 * @event 2476 * @description This event is fired whenever the user drags the element on a touch device. 2477 * @name JXG.GeometryElement#touchdrag 2478 * @param {Event} e The browser's event object. 2479 */ 2480 __evt__touchdrag: function (e) { }, 2481 2482 /** 2483 * @event 2484 * @description This event is fired whenever the user drags the element by pressing arrow keys 2485 * on the keyboard. 2486 * @name JXG.GeometryElement#keydrag 2487 * @param {Event} e The browser's event object. 2488 */ 2489 __evt__keydrag: function (e) { }, 2490 2491 /** 2492 * @event 2493 * @description Whenever the user starts to touch or click an element. 2494 * @name JXG.GeometryElement#down 2495 * @param {Event} e The browser's event object. 2496 */ 2497 __evt__down: function (e) { }, 2498 2499 /** 2500 * @event 2501 * @description Whenever the user starts to click an element. 2502 * @name JXG.GeometryElement#mousedown 2503 * @param {Event} e The browser's event object. 2504 */ 2505 __evt__mousedown: function (e) { }, 2506 2507 /** 2508 * @event 2509 * @description Whenever the user taps an element with the pen. 2510 * @name JXG.GeometryElement#pendown 2511 * @param {Event} e The browser's event object. 2512 */ 2513 __evt__pendown: function (e) { }, 2514 2515 /** 2516 * @event 2517 * @description Whenever the user starts to touch an element. 2518 * @name JXG.GeometryElement#touchdown 2519 * @param {Event} e The browser's event object. 2520 */ 2521 __evt__touchdown: function (e) { }, 2522 2523 /** 2524 * @event 2525 * @description Whenever the user clicks on an element. 2526 * @name JXG.Board#click 2527 * @param {Event} e The browser's event object. 2528 */ 2529 __evt__click: function (e) { }, 2530 2531 /** 2532 * @event 2533 * @description Whenever the user double clicks on an element. 2534 * This event works on desktop browser, but is undefined 2535 * on mobile browsers. 2536 * @name JXG.Board#dblclick 2537 * @param {Event} e The browser's event object. 2538 */ 2539 __evt__dblclick: function (e) { }, 2540 2541 /** 2542 * @event 2543 * @description Whenever the user clicks on an element with a mouse device. 2544 * @name JXG.Board#mouseclick 2545 * @param {Event} e The browser's event object. 2546 */ 2547 __evt__mouseclick: function (e) { }, 2548 2549 /** 2550 * @event 2551 * @description Whenever the user double clicks on an element with a mouse device. 2552 * @name JXG.Board#mousedblclick 2553 * @param {Event} e The browser's event object. 2554 */ 2555 __evt__mousedblclick: function (e) { }, 2556 2557 /** 2558 * @event 2559 * @description Whenever the user clicks on an element with a pointer device. 2560 * @name JXG.Board#pointerclick 2561 * @param {Event} e The browser's event object. 2562 */ 2563 __evt__pointerclick: function (e) { }, 2564 2565 /** 2566 * @event 2567 * @description Whenever the user double clicks on an element with a pointer device. 2568 * This event works on desktop browser, but is undefined 2569 * on mobile browsers. 2570 * @name JXG.Board#pointerdblclick 2571 * @param {Event} e The browser's event object. 2572 */ 2573 __evt__pointerdblclick: function (e) { }, 2574 2575 /** 2576 * @event 2577 * @description Whenever the user stops to touch or click an element. 2578 * @name JXG.GeometryElement#up 2579 * @param {Event} e The browser's event object. 2580 */ 2581 __evt__up: function (e) { }, 2582 2583 /** 2584 * @event 2585 * @description Whenever the user releases the mousebutton over an element. 2586 * @name JXG.GeometryElement#mouseup 2587 * @param {Event} e The browser's event object. 2588 */ 2589 __evt__mouseup: function (e) { }, 2590 2591 /** 2592 * @event 2593 * @description Whenever the user lifts the pen over an element. 2594 * @name JXG.GeometryElement#penup 2595 * @param {Event} e The browser's event object. 2596 */ 2597 __evt__penup: function (e) { }, 2598 2599 /** 2600 * @event 2601 * @description Whenever the user stops touching an element. 2602 * @name JXG.GeometryElement#touchup 2603 * @param {Event} e The browser's event object. 2604 */ 2605 __evt__touchup: function (e) { }, 2606 2607 /** 2608 * @event 2609 * @description Notify every time an attribute is changed. 2610 * @name JXG.GeometryElement#attribute 2611 * @param {Object} o A list of changed attributes and their new value. 2612 * @param {Object} el Reference to the element 2613 */ 2614 __evt__attribute: function (o, el) { }, 2615 2616 /** 2617 * @event 2618 * @description This is a generic event handler. It exists for every possible attribute that can be set for 2619 * any element, e.g. if you want to be notified everytime an element's strokecolor is changed, is the event 2620 * <tt>attribute:strokecolor</tt>. 2621 * @name JXG.GeometryElement#attribute:key 2622 * @param val The old value. 2623 * @param nval The new value 2624 * @param {Object} el Reference to the element 2625 */ 2626 __evt__attribute_: function (val, nval, el) { }, 2627 2628 /** 2629 * @ignore 2630 */ 2631 __evt: function () { } 2632 //endregion 2633 } 2634 ); 2635 2636 export default JXG.GeometryElement; 2637 // const GeometryElement = JXG.GeometryElement; 2638 // export { GeometryElement as default, GeometryElement }; 2639