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