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