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