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