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