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