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