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