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