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