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