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