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