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