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