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