      1 /*
      2     Copyright 2008-2022
      3         Matthias Ehmann,
      4         Michael Gerhaeuser,
      5         Carsten Miller,
      6         Alfred Wassermann
      7
      8     This file is part of JSXGraph.
      9
     10     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
     11
     12     You can redistribute it and/or modify it under the terms of the
     13
     14       * GNU Lesser General Public License as published by
     15         the Free Software Foundation, either version 3 of the License, or
     16         (at your option) any later version
     17       OR
     18       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
     19
     20     JSXGraph is distributed in the hope that it will be useful,
     21     but WITHOUT ANY WARRANTY; without even the implied warranty of
     22     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     23     GNU Lesser General Public License for more details.
     24
     25     You should have received a copy of the GNU Lesser General Public License and
     26     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
     27     and <http://opensource.org/licenses/MIT/>.
     28  */
     29
     30
     31 /*global JXG: true, define: true, console: true, window: true*/
     32 /*jslint nomen: true, plusplus: true*/
     33
     34 /* depends:
     35  jxg
     36  options
     37  math/math
     38  math/geometry
     39  math/numerics
     40  base/coords
     41  base/constants
     42  base/element
     43  parser/geonext
     44  utils/type
     45   elements:
     46    transform
     47  */
     48
     49 /**
     50  * @fileoverview The geometry object CoordsElement is defined in this file.
     51  * This object provides the coordinate handling of points, images and texts.
     52  */
     53
     54 define([
     55     'jxg', 'math/math', 'math/geometry', 'math/numerics', 'math/statistics', 'base/coords', 'base/constants', 'utils/type',
     56 ], function (JXG, Mat, Geometry, Numerics, Statistics, Coords, Const, Type) {
     57
     58     "use strict";
     59
     60     /**
     61      * An element containing coords is the basic geometric element. Based on points lines and circles can be constructed which can be intersected
     62      * which in turn are points again which can be used to construct new lines, circles, polygons, etc. This class holds methods for
     63      * all kind of coordinate elements like points, texts and images.
     64      * @class Creates a new coords element object. Do not use this constructor to create an element.
     65      *
     66      * @private
     67      * @augments JXG.GeometryElement
     68      * @param {Array} coordinates An array with the affine user coordinates of the point.
     69      * {@link JXG.Options#elements}, and - optionally - a name and an id.
     70      */
     71     JXG.CoordsElement = function (coordinates, isLabel) {
     72         var i;
     73
     74         if (!Type.exists(coordinates)) {
     75             coordinates = [1, 0, 0];
     76         }
     77
     78         for (i = 0; i < coordinates.length; ++i) {
     79             coordinates[i] = parseFloat(coordinates[i]);
     80         }
     81
     82         /**
     83          * Coordinates of the element.
     84          * @type JXG.Coords
     85          * @private
     86          */
     87         this.coords = new Coords(Const.COORDS_BY_USER, coordinates, this.board);
     88         this.initialCoords = new Coords(Const.COORDS_BY_USER, coordinates, this.board);
     89
     90         /**
     91          * Relative position on a slide element (line, circle, curve) if element is a glider on this element.
     92          * @type Number
     93          * @private
     94          */
     95         this.position = null;
     96
     97         /**
     98          * True if there the method this.updateConstraint() has been set. It is
     99          * probably different from the prototype function() {return this;}.
    100          * Used in updateCoords fo glider elements.
    101          *
    102          * @see JXG.CoordsElement#updateCoords
    103          * @type Boolean
    104          * @private
    105          */
    106         this.isConstrained = false;
    107
    108         /**
    109          * Determines whether the element slides on a polygon if point is a glider.
    110          * @type Boolean
    111          * @default false
    112          * @private
    113          */
    114         this.onPolygon = false;
    115
    116         /**
    117          * When used as a glider this member stores the object, where to glide on.
    118          * To set the object to glide on use the method
    119          * {@link JXG.Point#makeGlider} and DO NOT set this property directly
    120          * as it will break the dependency tree.
    121          * @type JXG.GeometryElement
    122          */
    123         this.slideObject = null;
    124
    125         /**
    126          * List of elements the element is bound to, i.e. the element glides on.
    127          * Only the last entry is active.
    128          * Use {@link JXG.Point#popSlideObject} to remove the currently active slideObject.
    129          */
    130         this.slideObjects = [];
    131
    132         /**
    133          * A {@link JXG.CoordsElement#updateGlider} call is usually followed
    134          * by a general {@link JXG.Board#update} which calls
    135          * {@link JXG.CoordsElement#updateGliderFromParent}.
    136          * To prevent double updates, {@link JXG.CoordsElement#needsUpdateFromParent}
    137          * is set to false in updateGlider() and reset to true in the following call to
    138          * {@link JXG.CoordsElement#updateGliderFromParent}
    139          * @type Boolean
    140          */
    141         this.needsUpdateFromParent = true;
    142
    143         /**
    144          * Stores the groups of this element in an array of Group.
    145          * @type Array
    146          * @see JXG.Group
    147          * @private
    148          */
    149         this.groups = [];
    150
    151         /*
    152          * Do we need this?
    153          */
    154         this.Xjc = null;
    155         this.Yjc = null;
    156
    157         // documented in GeometryElement
    158         this.methodMap = Type.deepCopy(this.methodMap, {
    159             move: 'moveTo',
    160             moveTo: 'moveTo',
    161             moveAlong: 'moveAlong',
    162             visit: 'visit',
    163             glide: 'makeGlider',
    164             makeGlider: 'makeGlider',
    165             intersect: 'makeIntersection',
    166             makeIntersection: 'makeIntersection',
    167             X: 'X',
    168             Y: 'Y',
    169             free: 'free',
    170             setPosition: 'setGliderPosition',
    171             setGliderPosition: 'setGliderPosition',
    172             addConstraint: 'addConstraint',
    173             dist: 'Dist',
    174             onPolygon: 'onPolygon'
    175         });
    176
    177         /*
    178          * this.element may have been set by the object constructor.
    179          */
    180         if (Type.exists(this.element)) {
    181             this.addAnchor(coordinates, isLabel);
    182         }
    183         this.isDraggable = true;
    184
    185     };
    186
    187     JXG.extend(JXG.CoordsElement.prototype, /** @lends JXG.CoordsElement.prototype */ {
    188         /**
    189          * Dummy function for unconstrained points or gliders.
    190          * @private
    191          */
    192         updateConstraint: function () {
    193             return this;
    194         },
    195
    196         /**
    197          * Updates the coordinates of the element.
    198          * @private
    199          */
    200         updateCoords: function (fromParent) {
    201             var i;
    202
    203             if (!this.needsUpdate) {
    204                 return this;
    205             }
    206
    207             if (!Type.exists(fromParent)) {
    208                 fromParent = false;
    209             }
    210
    211             if (!Type.evaluate(this.visProp.frozen)) {
    212                 this.updateConstraint();
    213             }
    214
    215             /*
    216              * We need to calculate the new coordinates no matter of the elements visibility because
    217              * a child could be visible and depend on the coordinates of the element/point (e.g. perpendicular).
    218              *
    219              * Check if the element is a glider and calculate new coords in dependency of this.slideObject.
    220              * This function is called with fromParent==true in case it is a glider element for example if
    221              * the defining elements of the line or circle have been changed.
    222              */
    223             if (this.type === Const.OBJECT_TYPE_GLIDER) {
    224                 if (this.isConstrained) {
    225                     fromParent = false;
    226                 }
    227
    228                 if (fromParent) {
    229                     this.updateGliderFromParent();
    230                 } else {
    231                     this.updateGlider();
    232                 }
    233             }
    234
    235             this.updateTransform(fromParent);
    236
    237             return this;
    238         },
    239
    240         /**
    241          * Update of glider in case of dragging the glider or setting the postion of the glider.
    242          * The relative position of the glider has to be updated.
    243          *
    244          * In case of a glider on a line:
    245          * If the second point is an ideal point, then -1 < this.position < 1,
    246          * this.position==+/-1 equals point2, this.position==0 equals point1
    247          *
    248          * If the first point is an ideal point, then 0 < this.position < 2
    249          * this.position==0  or 2 equals point1, this.position==1 equals point2
    250          *
    251          * @private
    252          */
    253         updateGlider: function () {
    254             var i, p1c, p2c, d, v, poly, cc, pos, sgn,
    255                 alpha, beta,
    256                 delta = 2.0 * Math.PI,
    257                 angle,
    258                 cp, c, invMat, newCoords, newPos,
    259                 doRound = false,
    260                 ev_sw,
    261                 slide = this.slideObject,
    262                 res, cu,
    263                 slides = [],
    264                 isTransformed;
    265
    266             this.needsUpdateFromParent = false;
    267             if (slide.elementClass === Const.OBJECT_CLASS_CIRCLE) {
    268                 if (Type.evaluate(this.visProp.isgeonext)) {
    269                     delta = 1.0;
    270                 }
    271                 newCoords = Geometry.projectPointToCircle(this, slide, this.board);
    272                 newPos = Geometry.rad([slide.center.X() + 1.0, slide.center.Y()], slide.center, this) / delta;
    273             } else if (slide.elementClass === Const.OBJECT_CLASS_LINE) {
    274                 /*
    275                  * onPolygon==true: the point is a slider on a segment and this segment is one of the
    276                  * "borders" of a polygon.
    277                  * This is a GEONExT feature.
    278                  */
    279                 if (this.onPolygon) {
    280                     p1c = slide.point1.coords.usrCoords;
    281                     p2c = slide.point2.coords.usrCoords;
    282                     i = 1;
    283                     d = p2c[i] - p1c[i];
    284
    285                     if (Math.abs(d) < Mat.eps) {
    286                         i = 2;
    287                         d = p2c[i] - p1c[i];
    288                     }
    289
    290                     cc = Geometry.projectPointToLine(this, slide, this.board);
    291                     pos = (cc.usrCoords[i] - p1c[i]) / d;
    292                     poly = slide.parentPolygon;
    293
    294                     if (pos < 0) {
    295                         for (i = 0; i < poly.borders.length; i++) {
    296                             if (slide === poly.borders[i]) {
    297                                 slide = poly.borders[(i - 1 + poly.borders.length) % poly.borders.length];
    298                                 break;
    299                             }
    300                         }
    301                     } else if (pos > 1.0) {
    302                         for (i = 0; i < poly.borders.length; i++) {
    303                             if (slide === poly.borders[i]) {
    304                                 slide = poly.borders[(i + 1 + poly.borders.length) % poly.borders.length];
    305                                 break;
    306                             }
    307                         }
    308                     }
    309
    310                     // If the slide object has changed, save the change to the glider.
    311                     if (slide.id !== this.slideObject.id) {
    312                         this.slideObject = slide;
    313                     }
    314                 }
    315
    316                 p1c = slide.point1.coords;
    317                 p2c = slide.point2.coords;
    318
    319                 // Distance between the two defining points
    320                 d = p1c.distance(Const.COORDS_BY_USER, p2c);
    321
    322                 // The defining points are identical
    323                 if (d < Mat.eps) {
    324                     //this.coords.setCoordinates(Const.COORDS_BY_USER, p1c);
    325                     newCoords = p1c;
    326                     doRound = true;
    327                     newPos = 0.0;
    328                 } else {
    329                     //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToLine(this, slide, this.board).usrCoords, false);
    330                     newCoords = Geometry.projectPointToLine(this, slide, this.board);
    331                     p1c = p1c.usrCoords.slice(0);
    332                     p2c = p2c.usrCoords.slice(0);
    333
    334                     // The second point is an ideal point
    335                     if (Math.abs(p2c[0]) < Mat.eps) {
    336                         i = 1;
    337                         d = p2c[i];
    338
    339                         if (Math.abs(d) < Mat.eps) {
    340                             i = 2;
    341                             d = p2c[i];
    342                         }
    343
    344                         d = (newCoords.usrCoords[i] - p1c[i]) / d;
    345                         sgn = (d >= 0) ? 1 : -1;
    346                         d = Math.abs(d);
    347                         newPos = sgn * d / (d + 1);
    348
    349                     // The first point is an ideal point
    350                     } else if (Math.abs(p1c[0]) < Mat.eps) {
    351                         i = 1;
    352                         d = p1c[i];
    353
    354                         if (Math.abs(d) < Mat.eps) {
    355                             i = 2;
    356                             d = p1c[i];
    357                         }
    358
    359                         d = (newCoords.usrCoords[i] - p2c[i]) / d;
    360
    361                         // 1.0 - d/(1-d);
    362                         if (d < 0.0) {
    363                             newPos = (1 - 2.0 * d) / (1.0 - d);
    364                         } else {
    365                             newPos = 1 / (d + 1);
    366                         }
    367                     } else {
    368                         i = 1;
    369                         d = p2c[i] - p1c[i];
    370
    371                         if (Math.abs(d) < Mat.eps) {
    372                             i = 2;
    373                             d = p2c[i] - p1c[i];
    374                         }
    375                         newPos = (newCoords.usrCoords[i] - p1c[i]) / d;
    376                     }
    377                 }
    378
    379                 // Snap the glider point of the slider into its appropiate position
    380                 // First, recalculate the new value of this.position
    381                 // Second, call update(fromParent==true) to make the positioning snappier.
    382                 ev_sw = Type.evaluate(this.visProp.snapwidth);
    383                 if (Type.evaluate(ev_sw) > 0.0 &&
    384                     Math.abs(this._smax - this._smin) >= Mat.eps) {
    385                     newPos = Math.max(Math.min(newPos, 1), 0);
    386
    387                     v = newPos * (this._smax - this._smin) + this._smin;
    388                     v = Math.round(v / ev_sw) * ev_sw;
    389                     newPos = (v - this._smin) / (this._smax - this._smin);
    390                     this.update(true);
    391                 }
    392
    393                 p1c = slide.point1.coords;
    394                 if (!Type.evaluate(slide.visProp.straightfirst) &&
    395                     Math.abs(p1c.usrCoords[0]) > Mat.eps && newPos < 0) {
    396                     newCoords = p1c;
    397                     doRound = true;
    398                     newPos = 0;
    399                 }
    400
    401                 p2c = slide.point2.coords;
    402                 if (!Type.evaluate(slide.visProp.straightlast) &&
    403                     Math.abs(p2c.usrCoords[0]) > Mat.eps && newPos > 1) {
    404                     newCoords = p2c;
    405                     doRound = true;
    406                     newPos = 1;
    407                 }
    408             } else if (slide.type === Const.OBJECT_TYPE_TURTLE) {
    409                 // In case, the point is a constrained glider.
    410                 this.updateConstraint();
    411                 res = Geometry.projectPointToTurtle(this, slide, this.board);
    412                 newCoords = res[0];
    413                 newPos = res[1];     // save position for the overwriting below
    414             } else if (slide.elementClass === Const.OBJECT_CLASS_CURVE) {
    415                 if ((slide.type === Const.OBJECT_TYPE_ARC ||
    416                      slide.type === Const.OBJECT_TYPE_SECTOR)) {
    417                     newCoords = Geometry.projectPointToCircle(this, slide, this.board);
    418
    419                     angle = Geometry.rad(slide.radiuspoint, slide.center, this);
    420                     alpha = 0.0;
    421                     beta = Geometry.rad(slide.radiuspoint, slide.center, slide.anglepoint);
    422                     newPos = angle;
    423
    424                     ev_sw = Type.evaluate(slide.visProp.selection);
    425                     if ((ev_sw === 'minor' && beta > Math.PI) ||
    426                         (ev_sw === 'major' && beta < Math.PI)) {
    427                         alpha = beta;
    428                         beta = 2 * Math.PI;
    429                     }
    430
    431                     // Correct the position if we are outside of the sector/arc
    432                     if (angle < alpha || angle > beta) {
    433                         newPos = beta;
    434
    435                         if ((angle < alpha && angle > alpha * 0.5) || (angle > beta && angle > beta * 0.5 + Math.PI)) {
    436                             newPos = alpha;
    437                         }
    438
    439                         this.needsUpdateFromParent = true;
    440                         this.updateGliderFromParent();
    441                     }
    442
    443                     delta = beta - alpha;
    444                     if (this.visProp.isgeonext) {
    445                         delta = 1.0;
    446                     }
    447                     if (Math.abs(delta) > Mat.eps) {
    448                         newPos /= delta;
    449                     }
    450                 } else {
    451                     // In case, the point is a constrained glider.
    452                     this.updateConstraint();
    453
    454                     // Handle the case if the curve comes from a transformation of a continous curve.
    455                     if (slide.transformations.length > 0) {
    456                         isTransformed = false;
    457                         res = slide.getTransformationSource();
    458                         if (res[0]) {
    459                             isTransformed = res[0];
    460                             slides.push(slide);
    461                             slides.push(res[1]);
    462                         }
    463                         // Recurse
    464                         while (res[0] && Type.exists(res[1]._transformationSource)) {
    465                             res = res[1].getTransformationSource();
    466                             slides.push(res[1]);
    467                         }
    468
    469                         cu = this.coords.usrCoords;
    470                         if (isTransformed) {
    471                             for (i = 0; i < slides.length; i++) {
    472                                 slides[i].updateTransformMatrix();
    473                                 invMat = Mat.inverse(slides[i].transformMat);
    474                                 cu = Mat.matVecMult(invMat, cu);
    475                             }
    476                             cp = (new Coords(Const.COORDS_BY_USER, cu, this.board)).usrCoords;
    477                             c = Geometry.projectCoordsToCurve(cp[1], cp[2],
    478                                         this.position || 0,
    479                                         slides[slides.length - 1],
    480                                         this.board);
    481                             // projectPointCurve() already would apply the transformation.
    482                             // Since we are projecting on the original curve, we have to do
    483                             // the transformations "by hand".
    484                             cu = c[0].usrCoords;
    485                             for (i = slides.length - 2; i >= 0; i--) {
    486                                 cu = Mat.matVecMult(slides[i].transformMat, cu);
    487                             }
    488                             c[0] = new Coords(Const.COORDS_BY_USER, cu, this.board);
    489                         } else {
    490                             slide.updateTransformMatrix();
    491                             invMat = Mat.inverse(slide.transformMat);
    492                             cu = Mat.matVecMult(invMat, cu);
    493                             cp = (new Coords(Const.COORDS_BY_USER, cu, this.board)).usrCoords;
    494                             c = Geometry.projectCoordsToCurve(cp[1], cp[2], this.position || 0, slide, this.board);
    495                         }
    496
    497                         newCoords = c[0];
    498                         newPos = c[1];
    499                     } else {
    500                         res = Geometry.projectPointToCurve(this, slide, this.board);
    501                         newCoords = res[0];
    502                         newPos = res[1]; // save position for the overwriting below
    503                     }
    504                 }
    505             } else if (Type.isPoint(slide)) {
    506                 //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToPoint(this, slide, this.board).usrCoords, false);
    507                 newCoords = Geometry.projectPointToPoint(this, slide, this.board);
    508                 newPos = this.position; // save position for the overwriting below
    509             }
    510
    511             this.coords.setCoordinates(Const.COORDS_BY_USER, newCoords.usrCoords, doRound);
    512             this.position = newPos;
    513         },
    514
    515         /**
    516          * Update of a glider in case a parent element has been updated. That means the
    517          * relative position of the glider stays the same.
    518          * @private
    519          */
    520         updateGliderFromParent: function () {
    521             var p1c, p2c, r, lbda, c,
    522                 slide = this.slideObject,
    523                 slides = [],
    524                 res, i,
    525                 isTransformed,
    526                 baseangle, alpha, angle, beta,
    527                 delta = 2.0 * Math.PI;
    528
    529             if (!this.needsUpdateFromParent) {
    530                 this.needsUpdateFromParent = true;
    531                 return;
    532             }
    533
    534             if (slide.elementClass === Const.OBJECT_CLASS_CIRCLE) {
    535                 r = slide.Radius();
    536                 if (Type.evaluate(this.visProp.isgeonext)) {
    537                     delta = 1.0;
    538                 }
    539                 c = [
    540                     slide.center.X() + r * Math.cos(this.position * delta),
    541                     slide.center.Y() + r * Math.sin(this.position * delta)
    542                 ];
    543             } else if (slide.elementClass === Const.OBJECT_CLASS_LINE) {
    544                 p1c = slide.point1.coords.usrCoords;
    545                 p2c = slide.point2.coords.usrCoords;
    546
    547                 // If one of the defining points of the line does not exist,
    548                 // the glider should disappear
    549                 if ((p1c[0] === 0 && p1c[1] === 0 && p1c[2] === 0) ||
    550                     (p2c[0] === 0 && p2c[1] === 0 && p2c[2] === 0)) {
    551                     c = [0, 0, 0];
    552                 // The second point is an ideal point
    553                 } else if (Math.abs(p2c[0]) < Mat.eps) {
    554                     lbda = Math.min(Math.abs(this.position), 1 - Mat.eps);
    555                     lbda /= (1.0 - lbda);
    556
    557                     if (this.position < 0) {
    558                         lbda = -lbda;
    559                     }
    560
    561                     c = [
    562                         p1c[0] + lbda * p2c[0],
    563                         p1c[1] + lbda * p2c[1],
    564                         p1c[2] + lbda * p2c[2]
    565                     ];
    566                 // The first point is an ideal point
    567                 } else if (Math.abs(p1c[0]) < Mat.eps) {
    568                     lbda = Math.max(this.position, Mat.eps);
    569                     lbda = Math.min(lbda, 2 - Mat.eps);
    570
    571                     if (lbda > 1) {
    572                         lbda = (lbda - 1) / (lbda - 2);
    573                     } else {
    574                         lbda = (1 - lbda) / lbda;
    575                     }
    576
    577                     c = [
    578                         p2c[0] + lbda * p1c[0],
    579                         p2c[1] + lbda * p1c[1],
    580                         p2c[2] + lbda * p1c[2]
    581                     ];
    582                 } else {
    583                     lbda = this.position;
    584                     c = [
    585                         p1c[0] + lbda * (p2c[0] - p1c[0]),
    586                         p1c[1] + lbda * (p2c[1] - p1c[1]),
    587                         p1c[2] + lbda * (p2c[2] - p1c[2])
    588                     ];
    589                 }
    590             } else if (slide.type === Const.OBJECT_TYPE_TURTLE) {
    591                 this.coords.setCoordinates(Const.COORDS_BY_USER, [slide.Z(this.position), slide.X(this.position), slide.Y(this.position)]);
    592                 // In case, the point is a constrained glider.
    593                 this.updateConstraint();
    594                 c  = Geometry.projectPointToTurtle(this, slide, this.board)[0].usrCoords;
    595             } else if (slide.elementClass === Const.OBJECT_CLASS_CURVE) {
    596                 // Handle the case if the curve comes from a transformation of a continuous curve.
    597                 isTransformed = false;
    598                 res = slide.getTransformationSource();
    599                 if (res[0]) {
    600                     isTransformed = res[0];
    601                     slides.push(slide);
    602                     slides.push(res[1]);
    603                 }
    604                 // Recurse
    605                 while (res[0] && Type.exists(res[1]._transformationSource)) {
    606                     res = res[1].getTransformationSource();
    607                     slides.push(res[1]);
    608                 }
    609                 if (isTransformed) {
    610                     this.coords.setCoordinates(Const.COORDS_BY_USER, [
    611                         slides[slides.length - 1].Z(this.position),
    612                         slides[slides.length - 1].X(this.position),
    613                         slides[slides.length - 1].Y(this.position)]);
    614                 } else {
    615                     this.coords.setCoordinates(Const.COORDS_BY_USER, [
    616                         slide.Z(this.position),
    617                         slide.X(this.position),
    618                         slide.Y(this.position)]);
    619                 }
    620
    621                 if (slide.type === Const.OBJECT_TYPE_ARC || slide.type === Const.OBJECT_TYPE_SECTOR) {
    622                     baseangle = Geometry.rad([slide.center.X() + 1, slide.center.Y()], slide.center, slide.radiuspoint);
    623
    624                     alpha = 0.0;
    625                     beta = Geometry.rad(slide.radiuspoint, slide.center, slide.anglepoint);
    626
    627                     if ((slide.visProp.selection === 'minor' && beta > Math.PI) ||
    628                             (slide.visProp.selection === 'major' && beta < Math.PI)) {
    629                         alpha = beta;
    630                         beta = 2 * Math.PI;
    631                     }
    632
    633                     delta = beta - alpha;
    634                     if (Type.evaluate(this.visProp.isgeonext)) {
    635                         delta = 1.0;
    636                     }
    637                     angle = this.position * delta;
    638
    639                     // Correct the position if we are outside of the sector/arc
    640                     if (angle < alpha || angle > beta) {
    641                         angle = beta;
    642
    643                         if ((angle < alpha && angle > alpha * 0.5) ||
    644                                 (angle > beta && angle > beta * 0.5 + Math.PI)) {
    645                             angle = alpha;
    646                         }
    647
    648                         this.position = angle;
    649                         if (Math.abs(delta) > Mat.eps) {
    650                             this.position /= delta;
    651                         }
    652                     }
    653
    654                     r = slide.Radius();
    655                     c = [
    656                         slide.center.X() + r * Math.cos(this.position * delta + baseangle),
    657                         slide.center.Y() + r * Math.sin(this.position * delta + baseangle)
    658                     ];
    659                 } else {
    660                     // In case, the point is a constrained glider.
    661                     this.updateConstraint();
    662
    663                     if (isTransformed) {
    664                         c = Geometry.projectPointToCurve(this, slides[slides.length - 1], this.board)[0].usrCoords;
    665                         // projectPointCurve() already would do the transformation.
    666                         // But since we are projecting on the original curve, we have to do
    667                         // the transformation "by hand".
    668                         for (i = slides.length - 2; i >= 0; i--) {
    669                             c = (new Coords(Const.COORDS_BY_USER,
    670                                 Mat.matVecMult(slides[i].transformMat, c), this.board)).usrCoords;
    671                         }
    672
    673                     } else {
    674                         c = Geometry.projectPointToCurve(this, slide, this.board)[0].usrCoords;
    675                     }
    676                 }
    677
    678             } else if (Type.isPoint(slide)) {
    679                 c = Geometry.projectPointToPoint(this, slide, this.board).usrCoords;
    680             }
    681
    682             this.coords.setCoordinates(Const.COORDS_BY_USER, c, false);
    683         },
    684
    685         updateRendererGeneric: function (rendererMethod) {
    686             //var wasReal;
    687
    688             if (!this.needsUpdate) {
    689                 return this;
    690             }
    691
    692             if (this.visPropCalc.visible) {
    693                 //wasReal = this.isReal;
    694                 this.isReal = (!isNaN(this.coords.usrCoords[1] + this.coords.usrCoords[2]));
    695                 //Homogeneous coords: ideal point
    696                 this.isReal = (Math.abs(this.coords.usrCoords[0]) > Mat.eps) ? this.isReal : false;
    697
    698                 if (// wasReal &&
    699                     !this.isReal) {
    700                     this.updateVisibility(false);
    701                 }
    702             }
    703
    704             // Call the renderer only if element is visible.
    705             // Update the position
    706             if (this.visPropCalc.visible) {
    707                 this.board.renderer[rendererMethod](this);
    708             }
    709
    710             // Update the label if visible.
    711             if (this.hasLabel && this.visPropCalc.visible && this.label &&
    712                 this.label.visPropCalc.visible && this.isReal) {
    713                 this.label.update();
    714                 this.board.renderer.updateText(this.label);
    715             }
    716
    717             // Update rendNode display
    718             this.setDisplayRendNode();
    719             // if (this.visPropCalc.visible !== this.visPropOld.visible) {
    720             //     this.board.renderer.display(this, this.visPropCalc.visible);
    721             //     this.visPropOld.visible = this.visPropCalc.visible;
    722             //
    723             //     if (this.hasLabel) {
    724             //         this.board.renderer.display(this.label, this.label.visPropCalc.visible);
    725             //     }
    726             // }
    727
    728             this.needsUpdate = false;
    729             return this;
    730         },
    731
    732         /**
    733          * Getter method for x, this is used by for CAS-points to access point coordinates.
    734          * @returns {Number} User coordinate of point in x direction.
    735          */
    736         X: function () {
    737             return this.coords.usrCoords[1];
    738         },
    739
    740         /**
    741          * Getter method for y, this is used by CAS-points to access point coordinates.
    742          * @returns {Number} User coordinate of point in y direction.
    743          */
    744         Y: function () {
    745             return this.coords.usrCoords[2];
    746         },
    747
    748         /**
    749          * Getter method for z, this is used by CAS-points to access point coordinates.
    750          * @returns {Number} User coordinate of point in z direction.
    751          */
    752         Z: function () {
    753             return this.coords.usrCoords[0];
    754         },
    755
    756         /**
    757          * New evaluation of the function term.
    758          * This is required for CAS-points: Their XTerm() method is
    759          * overwritten in {@link JXG.CoordsElement#addConstraint}.
    760          *
    761          * @returns {Number} User coordinate of point in x direction.
    762          * @private
    763          */
    764         XEval: function () {
    765             return this.coords.usrCoords[1];
    766         },
    767
    768         /**
    769          * New evaluation of the function term.
    770          * This is required for CAS-points: Their YTerm() method is overwritten
    771          * in {@link JXG.CoordsElement#addConstraint}.
    772          *
    773          * @returns {Number} User coordinate of point in y direction.
    774          * @private
    775          */
    776         YEval: function () {
    777             return this.coords.usrCoords[2];
    778         },
    779
    780         /**
    781          * New evaluation of the function term.
    782          * This is required for CAS-points: Their ZTerm() method is overwritten in
    783          * {@link JXG.CoordsElement#addConstraint}.
    784          *
    785          * @returns {Number} User coordinate of point in z direction.
    786          * @private
    787          */
    788         ZEval: function () {
    789             return this.coords.usrCoords[0];
    790         },
    791
    792         /**
    793          * Getter method for the distance to a second point, this is required for CAS-elements.
    794          * Here, function inlining seems to be worthwile  (for plotting).
    795          * @param {JXG.Point} point2 The point to which the distance shall be calculated.
    796          * @returns {Number} Distance in user coordinate to the given point
    797          */
    798         Dist: function (point2) {
    799             if (this.isReal && point2.isReal) {
    800                 return this.coords.distance(Const.COORDS_BY_USER, point2.coords);
    801             }
    802             return NaN;
    803         },
    804
    805         /**
    806          * Alias for {@link JXG.Element#handleSnapToGrid}
    807          * @param {Boolean} force force snapping independent from what the snaptogrid attribute says
    808          * @returns {JXG.CoordsElement} Reference to this element
    809          */
    810         snapToGrid: function (force) {
    811             return this.handleSnapToGrid(force);
    812         },
    813
    814         /**
    815          * Let a point snap to the nearest point in distance of
    816          * {@link JXG.Point#attractorDistance}.
    817          * The function uses the coords object of the point as
    818          * its actual position.
    819          * @param {Boolean} force force snapping independent from what the snaptogrid attribute says
    820          * @returns {JXG.Point} Reference to this element
    821          */
    822         handleSnapToPoints: function (force) {
    823             var i, pEl, pCoords,
    824                 d = 0,
    825                 len,
    826                 dMax = Infinity,
    827                 c = null,
    828                 ev_au, ev_ad,
    829                 ev_is2p = Type.evaluate(this.visProp.ignoredsnaptopoints),
    830                 len2, j, ignore = false;
    831
    832             len = this.board.objectsList.length;
    833
    834             if (ev_is2p) {
    835                 len2 = ev_is2p.length;
    836             }
    837
    838             if (Type.evaluate(this.visProp.snaptopoints) || force) {
    839                 ev_au = Type.evaluate(this.visProp.attractorunit);
    840                 ev_ad = Type.evaluate(this.visProp.attractordistance);
    841
    842                 for (i = 0; i < len; i++) {
    843                     pEl = this.board.objectsList[i];
    844
    845                     if (ev_is2p) {
    846                         ignore = false;
    847                         for (j = 0; j < len2; j++) {
    848                             if (pEl === this.board.select(ev_is2p[j])) {
    849                                 ignore = true;
    850                                 break;
    851                             }
    852                         }
    853                         if (ignore) {
    854                             continue;
    855                         }
    856                     }
    857
    858                     if (Type.isPoint(pEl) && pEl !== this && pEl.visPropCalc.visible) {
    859                         pCoords = Geometry.projectPointToPoint(this, pEl, this.board);
    860                         if (ev_au === 'screen') {
    861                             d = pCoords.distance(Const.COORDS_BY_SCREEN, this.coords);
    862                         } else {
    863                             d = pCoords.distance(Const.COORDS_BY_USER, this.coords);
    864                         }
    865
    866                         if (d < ev_ad && d < dMax) {
    867                             dMax = d;
    868                             c = pCoords;
    869                         }
    870                     }
    871                 }
    872
    873                 if (c !== null) {
    874                     this.coords.setCoordinates(Const.COORDS_BY_USER, c.usrCoords);
    875                 }
    876             }
    877
    878             return this;
    879         },
    880
    881         /**
    882          * Alias for {@link JXG.CoordsElement#handleSnapToPoints}.
    883          *
    884          * @param {Boolean} force force snapping independent from what the snaptogrid attribute says
    885          * @returns {JXG.Point} Reference to this element
    886          */
    887         snapToPoints: function (force) {
    888             return this.handleSnapToPoints(force);
    889         },
    890
    891         /**
    892          * A point can change its type from free point to glider
    893          * and vice versa. If it is given an array of attractor elements
    894          * (attribute attractors) and the attribute attractorDistance
    895          * then the point will be made a glider if it less than attractorDistance
    896          * apart from one of its attractor elements.
    897          * If attractorDistance is equal to zero, the point stays in its
    898          * current form.
    899          * @returns {JXG.Point} Reference to this element
    900          */
    901         handleAttractors: function () {
    902             var i, el, projCoords,
    903                 d = 0.0,
    904                 projection,
    905                 ev_au = Type.evaluate(this.visProp.attractorunit),
    906                 ev_ad = Type.evaluate(this.visProp.attractordistance),
    907                 ev_sd = Type.evaluate(this.visProp.snatchdistance),
    908                 ev_a = Type.evaluate(this.visProp.attractors),
    909                 len = ev_a.length;
    910
    911             if (ev_ad === 0.0) {
    912                 return;
    913             }
    914
    915             for (i = 0; i < len; i++) {
    916                 el = this.board.select(ev_a[i]);
    917
    918                 if (Type.exists(el) && el !== this) {
    919                     if (Type.isPoint(el)) {
    920                         projCoords = Geometry.projectPointToPoint(this, el, this.board);
    921                     } else if (el.elementClass === Const.OBJECT_CLASS_LINE) {
    922                         projection = Geometry.projectCoordsToSegment(
    923                                     this.coords.usrCoords,
    924                                     el.point1.coords.usrCoords,
    925                                     el.point2.coords.usrCoords);
    926                         if (!Type.evaluate(el.visProp.straightfirst) && projection[1] < 0.0) {
    927                             projCoords = el.point1.coords;
    928                         } else if (!Type.evaluate(el.visProp.straightlast) && projection[1] > 1.0) {
    929                             projCoords = el.point2.coords;
    930                         } else {
    931                             projCoords = new Coords(Const.COORDS_BY_USER, projection[0], this.board);
    932                         }
    933                     } else if (el.elementClass === Const.OBJECT_CLASS_CIRCLE) {
    934                         projCoords = Geometry.projectPointToCircle(this, el, this.board);
    935                     } else if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
    936                         projCoords = Geometry.projectPointToCurve(this, el, this.board)[0];
    937                     } else if (el.type === Const.OBJECT_TYPE_TURTLE) {
    938                         projCoords = Geometry.projectPointToTurtle(this, el, this.board)[0];
    939                     } else if (el.type === Const.OBJECT_TYPE_POLYGON) {
    940                         projCoords = new Coords(Const.COORDS_BY_USER,
    941                             Geometry.projectCoordsToPolygon(this.coords.usrCoords, el),
    942                             this.board);
    943                     }
    944
    945                     if (ev_au === 'screen') {
    946                         d = projCoords.distance(Const.COORDS_BY_SCREEN, this.coords);
    947                     } else {
    948                         d = projCoords.distance(Const.COORDS_BY_USER, this.coords);
    949                     }
    950
    951                     if (d < ev_ad) {
    952                         if (!(this.type === Const.OBJECT_TYPE_GLIDER &&
    953                               (el === this.slideObject || this.slideObject && this.onPolygon && this.slideObject.parentPolygon === el)
    954                              )
    955                            ) {
    956                             this.makeGlider(el);
    957                         }
    958                         break;       // bind the point to the first attractor in its list.
    959                     }
    960                     if (d >= ev_sd &&
    961                         (el === this.slideObject || this.slideObject && this.onPolygon && this.slideObject.parentPolygon === el)
    962                        ) {
    963                         this.popSlideObject();
    964                     }
    965                 }
    966             }
    967
    968             return this;
    969         },
    970
    971         /**
    972          * Sets coordinates and calls the point's update() method.
    973          * @param {Number} method The type of coordinates used here.
    974          * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
    975          * @param {Array} coords coordinates <tt>([z], x, y)</tt> in screen/user units
    976          * @returns {JXG.Point} this element
    977          */
    978         setPositionDirectly: function (method, coords) {
    979             var i, c, dc,
    980                 oldCoords = this.coords,
    981                 newCoords;
    982
    983             if (this.relativeCoords) {
    984                 c = new Coords(method, coords, this.board);
    985                 if (Type.evaluate(this.visProp.islabel)) {
    986                     dc = Statistics.subtract(c.scrCoords, oldCoords.scrCoords);
    987                     this.relativeCoords.scrCoords[1] += dc[1];
    988                     this.relativeCoords.scrCoords[2] += dc[2];
    989                 } else {
    990                     dc = Statistics.subtract(c.usrCoords, oldCoords.usrCoords);
    991                     this.relativeCoords.usrCoords[1] += dc[1];
    992                     this.relativeCoords.usrCoords[2] += dc[2];
    993                 }
    994
    995                 return this;
    996             }
    997
    998             this.coords.setCoordinates(method, coords);
    999             this.handleSnapToGrid();
    1000             this.handleSnapToPoints();
    1001             this.handleAttractors();
    1002
    1003             // Update the initial coordinates. This is needed for free points
    1004             // that have a transformation bound to it.
    1005             for (i = this.transformations.length - 1; i >= 0; i--) {
    1006                 if (method === Const.COORDS_BY_SCREEN) {
    1007                     newCoords = (new Coords(method, coords, this.board)).usrCoords;
    1008                 } else {
    1009                     if (coords.length === 2) {
    1010                         coords = [1].concat(coords);
    1011                     }
    1012                     newCoords = coords;
    1013                 }
    1014                 this.initialCoords.setCoordinates(Const.COORDS_BY_USER, Mat.matVecMult(Mat.inverse(this.transformations[i].matrix), newCoords));
    1015             }
    1016             this.prepareUpdate().update();
    1017
    1018             // If the user suspends the board updates we need to recalculate the relative position of
    1019             // the point on the slide object. This is done in updateGlider() which is NOT called during the
    1020             // update process triggered by unsuspendUpdate.
    1021             if (this.board.isSuspendedUpdate && this.type === Const.OBJECT_TYPE_GLIDER) {
    1022                 this.updateGlider();
    1023             }
    1024
    1025             return this;
    1026         },
    1027
    1028         /**
    1029          * Translates the point by <tt>tv = (x, y)</tt>.
    1030          * @param {Number} method The type of coordinates used here.
    1031          * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
    1032          * @param {Array} tv (x, y)
    1033          * @returns {JXG.Point}
    1034          */
    1035         setPositionByTransform: function (method, tv) {
    1036             var t;
    1037
    1038             tv = new Coords(method, tv, this.board);
    1039             t = this.board.create('transform', tv.usrCoords.slice(1), {type: 'translate'});
    1040
    1041             if (this.transformations.length > 0 &&
    1042                     this.transformations[this.transformations.length - 1].isNumericMatrix) {
    1043                 this.transformations[this.transformations.length - 1].melt(t);
    1044             } else {
    1045                 this.addTransform(this, t);
    1046             }
    1047
    1048             this.prepareUpdate().update();
    1049
    1050             return this;
    1051         },
    1052
    1053         /**
    1054          * Sets coordinates and calls the point's update() method.
    1055          * @param {Number} method The type of coordinates used here.
    1056          * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
    1057          * @param {Array} coords coordinates in screen/user units
    1058          * @returns {JXG.Point}
    1059          */
    1060         setPosition: function (method, coords) {
    1061             return this.setPositionDirectly(method, coords);
    1062         },
    1063
    1064         /**
    1065          * Sets the position of a glider relative to the defining elements
    1066          * of the {@link JXG.Point#slideObject}.
    1067          * @param {Number} x
    1068          * @returns {JXG.Point} Reference to the point element.
    1069          */
    1070         setGliderPosition: function (x) {
    1071             if (this.type === Const.OBJECT_TYPE_GLIDER) {
    1072                 this.position = x;
    1073                 this.board.update();
    1074             }
    1075
    1076             return this;
    1077         },
    1078
    1079         /**
    1080          * Convert the point to glider and update the construction.
    1081          * To move the point visual onto the glider, a call of board update is necessary.
    1082          * @param {String|Object} slide The object the point will be bound to.
    1083          */
    1084         makeGlider: function (slide) {
    1085             var slideobj = this.board.select(slide),
    1086                 onPolygon = false,
    1087                 min,
    1088                 i,
    1089                 dist;
    1090
    1091             if (slideobj.type === Const.OBJECT_TYPE_POLYGON){
    1092                 // Search for the closest edge of the polygon.
    1093                 min = Number.MAX_VALUE;
    1094                 for (i = 0; i < slideobj.borders.length; i++){
    1095                     dist = JXG.Math.Geometry.distPointLine(this.coords.usrCoords, slideobj.borders[i].stdform);
    1096                     if (dist < min){
    1097                         min = dist;
    1098                         slide = slideobj.borders[i];
    1099                     }
    1100                 }
    1101                 slideobj = this.board.select(slide);
    1102                 onPolygon = true;
    1103             }
    1104
    1105             /* Gliders on Ticks are forbidden */
    1106             if (!Type.exists(slideobj)) {
    1107                 throw new Error("JSXGraph: slide object undefined.");
    1108             } else if (slideobj.type === Const.OBJECT_TYPE_TICKS) {
    1109                 throw new Error("JSXGraph: gliders on ticks are not possible.");
    1110             }
    1111
    1112             this.slideObject = this.board.select(slide);
    1113             this.slideObjects.push(this.slideObject);
    1114             this.addParents(slide);
    1115
    1116             this.type = Const.OBJECT_TYPE_GLIDER;
    1117             this.elType = 'glider';
    1118             this.visProp.snapwidth = -1;          // By default, deactivate snapWidth
    1119             this.slideObject.addChild(this);
    1120             this.isDraggable = true;
    1121             this.onPolygon = onPolygon;
    1122
    1123             this.generatePolynomial = function () {
    1124                 return this.slideObject.generatePolynomial(this);
    1125             };
    1126
    1127             // Determine the initial value of this.position
    1128             this.updateGlider();
    1129             this.needsUpdateFromParent = true;
    1130             this.updateGliderFromParent();
    1131
    1132             return this;
    1133         },
    1134
    1135         /**
    1136          * Remove the last slideObject. If there are more than one elements the point is bound to,
    1137          * the second last element is the new active slideObject.
    1138          */
    1139         popSlideObject: function () {
    1140             if (this.slideObjects.length > 0) {
    1141                 this.slideObjects.pop();
    1142
    1143                 // It may not be sufficient to remove the point from
    1144                 // the list of childElement. For complex dependencies
    1145                 // one may have to go to the list of ancestor and descendants.  A.W.
    1146                 // Yes indeed, see #51 on github bugtracker
    1147                 //  delete this.slideObject.childElements[this.id];
    1148                 this.slideObject.removeChild(this);
    1149
    1150                 if (this.slideObjects.length === 0) {
    1151                     this.type = this._org_type;
    1152                     if (this.type === Const.OBJECT_TYPE_POINT) {
    1153                         this.elType = 'point';
    1154                     } else if (this.elementClass === Const.OBJECT_CLASS_TEXT) {
    1155                         this.elType = 'text';
    1156                     } else if (this.type === Const.OBJECT_TYPE_IMAGE) {
    1157                         this.elType = 'image';
    1158                     } else if (this.type === Const.OBJECT_TYPE_FOREIGNOBJECT) {
    1159                         this.elType = 'foreignobject';
    1160                     }
    1161
    1162                     this.slideObject = null;
    1163                 } else {
    1164                     this.slideObject = this.slideObjects[this.slideObjects.length - 1];
    1165                 }
    1166             }
    1167         },
    1168
    1169         /**
    1170          * Converts a calculated element into a free element,
    1171          * i.e. it will delete all ancestors and transformations and,
    1172          * if the element is currently a glider, will remove the slideObject reference.
    1173          */
    1174         free: function () {
    1175             var ancestorId, ancestor;
    1176                 // child;
    1177
    1178             if (this.type !== Const.OBJECT_TYPE_GLIDER) {
    1179                 // remove all transformations
    1180                 this.transformations.length = 0;
    1181
    1182                 delete this.updateConstraint;
    1183                 this.isConstrained = false;
    1184                 // this.updateConstraint = function () {
    1185                 //     return this;
    1186                 // };
    1187
    1188                 if (!this.isDraggable) {
    1189                     this.isDraggable = true;
    1190
    1191                     if (this.elementClass === Const.OBJECT_CLASS_POINT) {
    1192                         this.type = Const.OBJECT_TYPE_POINT;
    1193                         this.elType = 'point';
    1194                     }
    1195
    1196                     this.XEval = function () {
    1197                         return this.coords.usrCoords[1];
    1198                     };
    1199
    1200                     this.YEval = function () {
    1201                         return this.coords.usrCoords[2];
    1202                     };
    1203
    1204                     this.ZEval = function () {
    1205                         return this.coords.usrCoords[0];
    1206                     };
    1207
    1208                     this.Xjc = null;
    1209                     this.Yjc = null;
    1210                 } else {
    1211                     return;
    1212                 }
    1213             }
    1214
    1215             // a free point does not depend on anything. And instead of running through tons of descendants and ancestor
    1216             // structures, where we eventually are going to visit a lot of objects twice or thrice with hard to read and
    1217             // comprehend code, just run once through all objects and delete all references to this point and its label.
    1218             for (ancestorId in this.board.objects) {
    1219                 if (this.board.objects.hasOwnProperty(ancestorId)) {
    1220                     ancestor = this.board.objects[ancestorId];
    1221
    1222                     if (ancestor.descendants) {
    1223                         delete ancestor.descendants[this.id];
    1224                         delete ancestor.childElements[this.id];
    1225
    1226                         if (this.hasLabel) {
    1227                             delete ancestor.descendants[this.label.id];
    1228                             delete ancestor.childElements[this.label.id];
    1229                         }
    1230                     }
    1231                 }
    1232             }
    1233
    1234             // A free point does not depend on anything. Remove all ancestors.
    1235             this.ancestors = {}; // only remove the reference
    1236
    1237             // Completely remove all slideObjects of the element
    1238             this.slideObject = null;
    1239             this.slideObjects = [];
    1240             if (this.elementClass === Const.OBJECT_CLASS_POINT) {
    1241                 this.type = Const.OBJECT_TYPE_POINT;
    1242                 this.elType = 'point';
    1243             } else if (this.elementClass === Const.OBJECT_CLASS_TEXT) {
    1244                 this.type = this._org_type;
    1245                 this.elType = 'text';
    1246             } else if (this.elementClass === Const.OBJECT_CLASS_OTHER) {
    1247                 this.type = this._org_type;
    1248                 this.elType = 'image';
    1249             }
    1250         },
    1251
    1252         /**
    1253          * Convert the point to CAS point and call update().
    1254          * @param {Array} terms [[zterm], xterm, yterm] defining terms for the z, x and y coordinate.
    1255          * The z-coordinate is optional and it is used for homogeneous coordinates.
    1256          * The coordinates may be either <ul>
    1257          *   <li>a JavaScript function,</li>
    1258          *   <li>a string containing GEONExT syntax. This string will be converted into a JavaScript
    1259          *     function here,</li>
    1260          *   <li>a Number</li>
    1261          *   <li>a pointer to a slider object. This will be converted into a call of the Value()-method
    1262          *     of this slider.</li>
    1263          *   </ul>
    1264          * @see JXG.GeonextParser#geonext2JS
    1265          */
    1266         addConstraint: function (terms) {
    1267             var i, v,
    1268                 newfuncs = [],
    1269                 what = ['X', 'Y'],
    1270
    1271                 makeConstFunction = function (z) {
    1272                     return function () {
    1273                         return z;
    1274                     };
    1275                 },
    1276
    1277                 makeSliderFunction = function (a) {
    1278                     return function () {
    1279                         return a.Value();
    1280                     };
    1281                 };
    1282
    1283             if (this.elementClass === Const.OBJECT_CLASS_POINT) {
    1284                 this.type = Const.OBJECT_TYPE_CAS;
    1285             }
    1286
    1287             this.isDraggable = false;
    1288
    1289             for (i = 0; i < terms.length; i++) {
    1290                 v = terms[i];
    1291
    1292                 if (Type.isString(v)) {
    1293                     // Convert GEONExT syntax into JavaScript syntax
    1294                     //t  = JXG.GeonextParser.geonext2JS(v, this.board);
    1295                     //newfuncs[i] = new Function('','return ' + t + ';');
    1296                     //v = GeonextParser.replaceNameById(v, this.board);
    1297                     newfuncs[i] = this.board.jc.snippet(v, true, null, true);
    1298
    1299                     if (terms.length === 2) {
    1300                         this[what[i] + 'jc'] = terms[i];
    1301                     }
    1302                 } else if (Type.isFunction(v)) {
    1303                     newfuncs[i] = v;
    1304                 } else if (Type.isNumber(v)) {
    1305                     newfuncs[i] = makeConstFunction(v);
    1306                 // Slider
    1307             } else if (Type.isObject(v) && Type.isFunction(v.Value)) {
    1308                     newfuncs[i] = makeSliderFunction(v);
    1309                 }
    1310
    1311                 newfuncs[i].origin = v;
    1312             }
    1313
    1314             // Intersection function
    1315             if (terms.length === 1) {
    1316                 this.updateConstraint = function () {
    1317                     var c = newfuncs[0]();
    1318
    1319                     // Array
    1320                     if (Type.isArray(c)) {
    1321                         this.coords.setCoordinates(Const.COORDS_BY_USER, c);
    1322                     // Coords object
    1323                     } else {
    1324                         this.coords = c;
    1325                     }
    1326                     return this;
    1327                 };
    1328             // Euclidean coordinates
    1329             } else if (terms.length === 2) {
    1330                 this.XEval = newfuncs[0];
    1331                 this.YEval = newfuncs[1];
    1332
    1333                 this.setParents([newfuncs[0].origin, newfuncs[1].origin]);
    1334
    1335                 this.updateConstraint = function () {
    1336                     this.coords.setCoordinates(Const.COORDS_BY_USER, [this.XEval(), this.YEval()]);
    1337                     return this;
    1338                 };
    1339             // Homogeneous coordinates
    1340             } else {
    1341                 this.ZEval = newfuncs[0];
    1342                 this.XEval = newfuncs[1];
    1343                 this.YEval = newfuncs[2];
    1344
    1345                 this.setParents([newfuncs[0].origin, newfuncs[1].origin, newfuncs[2].origin]);
    1346
    1347                 this.updateConstraint = function () {
    1348                     this.coords.setCoordinates(Const.COORDS_BY_USER, [this.ZEval(), this.XEval(), this.YEval()]);
    1349                     return this;
    1350                 };
    1351             }
    1352             this.isConstrained = true;
    1353
    1354             /**
    1355             * We have to do an update. Otherwise, elements relying on this point will receive NaN.
    1356             */
    1357             this.prepareUpdate().update();
    1358             if (!this.board.isSuspendedUpdate) {
    1359                 this.updateVisibility().updateRenderer();
    1360                 if (this.hasLabel) {
    1361                     this.label.fullUpdate();
    1362                 }
    1363             }
    1364
    1365             return this;
    1366         },
    1367
    1368         /**
    1369          * In case there is an attribute "anchor", the element is bound to
    1370          * this anchor element.
    1371          * This is handled with this.relativeCoords. If the element is a label
    1372          * relativeCoords are given in scrCoords, otherwise in usrCoords.
    1373          * @param{Array} coordinates Offset from th anchor element. These are the values for this.relativeCoords.
    1374          * In case of a label, coordinates are screen coordinates. Otherwise, coordinates are user coordinates.
    1375          * @param{Boolean} isLabel Yes/no
    1376          * @private
    1377          */
    1378         addAnchor: function (coordinates, isLabel) {
    1379             if (isLabel) {
    1380                 this.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, coordinates.slice(0, 2), this.board);
    1381             } else {
    1382                 this.relativeCoords = new Coords(Const.COORDS_BY_USER, coordinates, this.board);
    1383             }
    1384             this.element.addChild(this);
    1385             if (isLabel) {
    1386                 this.addParents(this.element);
    1387             }
    1388
    1389             this.XEval = function () {
    1390                 var sx, coords, anchor, ev_o;
    1391
    1392                 if (Type.evaluate(this.visProp.islabel)) {
    1393                     ev_o = Type.evaluate(this.visProp.offset);
    1394                     sx =  parseFloat(ev_o[0]);
    1395                     anchor = this.element.getLabelAnchor();
    1396                     coords = new Coords(Const.COORDS_BY_SCREEN,
    1397                         [sx + this.relativeCoords.scrCoords[1] + anchor.scrCoords[1], 0], this.board);
    1398
    1399                     return coords.usrCoords[1];
    1400                 }
    1401
    1402                 anchor = this.element.getTextAnchor();
    1403                 return this.relativeCoords.usrCoords[1] + anchor.usrCoords[1];
    1404             };
    1405
    1406             this.YEval = function () {
    1407                 var sy, coords, anchor, ev_o;
    1408
    1409                 if (Type.evaluate(this.visProp.islabel)) {
    1410                     ev_o = Type.evaluate(this.visProp.offset);
    1411                     sy = -parseFloat(ev_o[1]);
    1412                     anchor = this.element.getLabelAnchor();
    1413                     coords = new Coords(Const.COORDS_BY_SCREEN,
    1414                         [0, sy + this.relativeCoords.scrCoords[2] + anchor.scrCoords[2]], this.board);
    1415
    1416                     return coords.usrCoords[2];
    1417                 }
    1418
    1419                 anchor = this.element.getTextAnchor();
    1420                 return this.relativeCoords.usrCoords[2] + anchor.usrCoords[2];
    1421             };
    1422
    1423             this.ZEval = Type.createFunction(1, this.board, '');
    1424
    1425             this.updateConstraint = function () {
    1426                 this.coords.setCoordinates(Const.COORDS_BY_USER, [this.ZEval(), this.XEval(), this.YEval()]);
    1427             };
    1428             this.isConstrained = true;
    1429
    1430             this.updateConstraint();
    1431             //this.coords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board);
    1432         },
    1433
    1434         /**
    1435          * Applies the transformations of the element.
    1436          * This method applies to text and images. Point transformations are handled differently.
    1437          * @param {Boolean} fromParent True if the drag comes from a child element. Unused.
    1438          * @returns {JXG.CoordsElement} Reference to itself.
    1439          */
    1440         updateTransform: function (fromParent) {
    1441             var i;
    1442
    1443             if (this.transformations.length === 0) {
    1444                 return this;
    1445             }
    1446
    1447             for (i = 0; i < this.transformations.length; i++) {
    1448                 this.transformations[i].update();
    1449             }
    1450
    1451             return this;
    1452         },
    1453
    1454         /**
    1455          * Add transformations to this element.
    1456          * @param {JXG.GeometryElement} el
    1457          * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation}
    1458          * or an array of {@link JXG.Transformation}s.
    1459          * @returns {JXG.CoordsElement} Reference to itself.
    1460          */
    1461         addTransform: function (el, transform) {
    1462             var i,
    1463                 list = Type.isArray(transform) ? transform : [transform],
    1464                 len = list.length;
    1465
    1466             // There is only one baseElement possible
    1467             if (this.transformations.length === 0) {
    1468                 this.baseElement = el;
    1469             }
    1470
    1471             for (i = 0; i < len; i++) {
    1472                 this.transformations.push(list[i]);
    1473             }
    1474
    1475             return this;
    1476         },
    1477
    1478         /**
    1479          * Animate the point.
    1480          * @param {Number} direction The direction the glider is animated. Can be +1 or -1.
    1481          * @param {Number} stepCount The number of steps in which the parent element is divided.
    1482          * Must be at least 1.
    1483          * @param {Number} delay Time in msec between two animation steps. Default is 250.
    1484          * @returns {JXG.CoordsElement} Reference to iself.
    1485          *
    1486          * @name Glider#startAnimation
    1487          * @see Glider#stopAnimation
    1488          * @function
    1489          * @example
    1490          * // Divide the circle line into 6 steps and
    1491          * // visit every step 330 msec counterclockwise.
    1492          * var ci = board.create('circle', [[-1,2], [2,1]]);
    1493          * var gl = board.create('glider', [0,2, ci]);
    1494          * gl.startAnimation(-1, 6, 330);
    1495          *
    1496          * </pre><div id="JXG0f35a50e-e99d-11e8-a1ca-04d3b0c2aad3" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1497          * <script type="text/javascript">
    1498          *     (function() {
    1499          *         var board = JXG.JSXGraph.initBoard('JXG0f35a50e-e99d-11e8-a1ca-04d3b0c2aad3',
    1500          *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    1501          *     // Divide the circle line into 6 steps and
    1502          *     // visit every step 330 msec counterclockwise.
    1503          *     var ci = board.create('circle', [[-1,2], [2,1]]);
    1504          *     var gl = board.create('glider', [0,2, ci]);
    1505          *     gl.startAnimation(-1, 6, 330);
    1506          *
    1507          *     })();
    1508          *
    1509          * </script><pre>
    1510          *
    1511          * @example
    1512          * // Divide the slider area into 20 steps and
    1513          * // visit every step 30 msec.
    1514          * var n = board.create('slider',[[-2,4],[2,4],[1,5,100]],{name:'n'});
    1515          * n.startAnimation(1, 20, 30);
    1516          *
    1517          * </pre><div id="JXG40ce04b8-e99c-11e8-a1ca-04d3b0c2aad3" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1518          * <script type="text/javascript">
    1519          *     (function() {
    1520          *         var board = JXG.JSXGraph.initBoard('JXG40ce04b8-e99c-11e8-a1ca-04d3b0c2aad3',
    1521          *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    1522          *     // Divide the slider area into 20 steps and
    1523          *     // visit every step 30 msec.
    1524          *     var n = board.create('slider',[[-2,4],[2,4],[1,5,100]],{name:'n'});
    1525          *     n.startAnimation(1, 20, 30);
    1526          *
    1527          *     })();
    1528          * </script><pre>
    1529          *
    1530          */
    1531         startAnimation: function (direction, stepCount, delay) {
    1532             var that = this;
    1533
    1534             delay = delay || 250;
    1535
    1536             if ((this.type === Const.OBJECT_TYPE_GLIDER) && !Type.exists(this.intervalCode)) {
    1537                 this.intervalCode = window.setInterval(function () {
    1538                     that._anim(direction, stepCount);
    1539                 }, delay);
    1540
    1541                 if (!Type.exists(this.intervalCount)) {
    1542                     this.intervalCount = 0;
    1543                 }
    1544             }
    1545             return this;
    1546         },
    1547
    1548         /**
    1549          * Stop animation.
    1550          * @name Glider#stopAnimation
    1551          * @see Glider#startAnimation
    1552          * @function
    1553          * @returns {JXG.CoordsElement} Reference to itself.
    1554          */
    1555         stopAnimation: function () {
    1556             if (Type.exists(this.intervalCode)) {
    1557                 window.clearInterval(this.intervalCode);
    1558                 delete this.intervalCode;
    1559             }
    1560
    1561             return this;
    1562         },
    1563
    1564         /**
    1565          * Starts an animation which moves the point along a given path in given time.
    1566          * @param {Array|function} path The path the point is moved on.
    1567          * This can be either an array of arrays or containing x and y values of the points of
    1568          * the path, or an array of points, or a function taking the amount of elapsed time since the animation
    1569          * has started and returns an array containing a x and a y value or NaN.
    1570          * In case of NaN the animation stops.
    1571          * @param {Number} time The time in milliseconds in which to finish the animation
    1572          * @param {Object} [options] Optional settings for the animation.
    1573          * @param {function} [options.callback] A function that is called as soon as the animation is finished.
    1574          * @param {Boolean} [options.interpolate=true] If <tt>path</tt> is an array moveAlong()
    1575          * will interpolate the path
    1576          * using {@link JXG.Math.Numerics.Neville}. Set this flag to false if you don't want to use interpolation.
    1577          * @returns {JXG.CoordsElement} Reference to itself.
    1578          * @see JXG.CoordsElement#moveAlong
    1579          * @see JXG.CoordsElement#moveTo
    1580          * @see JXG.GeometryElement#animate
    1581          */
    1582         moveAlong: function (path, time, options) {
    1583             options = options || {};
    1584
    1585             var i, neville,
    1586                 interpath = [],
    1587                 p = [],
    1588                 delay = this.board.attr.animationdelay,
    1589                 steps = time / delay,
    1590                 len, pos, part,
    1591
    1592                 makeFakeFunction = function (i, j) {
    1593                     return function () {
    1594                         return path[i][j];
    1595                     };
    1596                 };
    1597
    1598             if (Type.isArray(path)) {
    1599                 len = path.length;
    1600                 for (i = 0; i < len; i++) {
    1601                     if (Type.isPoint(path[i])) {
    1602                         p[i] = path[i];
    1603                     } else {
    1604                         p[i] = {
    1605                             elementClass: Const.OBJECT_CLASS_POINT,
    1606                             X: makeFakeFunction(i, 0),
    1607                             Y: makeFakeFunction(i, 1)
    1608                         };
    1609                     }
    1610                 }
    1611
    1612                 time = time || 0;
    1613                 if (time === 0) {
    1614                     this.setPosition(Const.COORDS_BY_USER, [p[p.length - 1].X(), p[p.length - 1].Y()]);
    1615                     return this.board.update(this);
    1616                 }
    1617
    1618                 if (!Type.exists(options.interpolate) || options.interpolate) {
    1619                     neville = Numerics.Neville(p);
    1620                     for (i = 0; i < steps; i++) {
    1621                         interpath[i] = [];
    1622                         interpath[i][0] = neville[0]((steps - i) / steps * neville[3]());
    1623                         interpath[i][1] = neville[1]((steps - i) / steps * neville[3]());
    1624                     }
    1625                 } else {
    1626                     len = path.length - 1;
    1627                     for (i = 0; i < steps; ++i) {
    1628                         pos = Math.floor(i / steps * len);
    1629                         part = i / steps * len - pos;
    1630
    1631                         interpath[i] = [];
    1632                         interpath[i][0] = (1.0 - part) * p[pos].X() + part * p[pos + 1].X();
    1633                         interpath[i][1] = (1.0 - part) * p[pos].Y() + part * p[pos + 1].Y();
    1634                     }
    1635                     interpath.push([p[len].X(), p[len].Y()]);
    1636                     interpath.reverse();
    1637                     /*
    1638                     for (i = 0; i < steps; i++) {
    1639                         interpath[i] = [];
    1640                         interpath[i][0] = path[Math.floor((steps - i) / steps * (path.length - 1))][0];
    1641                         interpath[i][1] = path[Math.floor((steps - i) / steps * (path.length - 1))][1];
    1642                     }
    1643                     */
    1644                 }
    1645
    1646                 this.animationPath = interpath;
    1647             } else if (Type.isFunction(path)) {
    1648                 this.animationPath = path;
    1649                 this.animationStart = new Date().getTime();
    1650             }
    1651
    1652             this.animationCallback = options.callback;
    1653             this.board.addAnimation(this);
    1654
    1655             return this;
    1656         },
    1657
    1658         /**
    1659          * Starts an animated point movement towards the given coordinates <tt>where</tt>.
    1660          * The animation is done after <tt>time</tt> milliseconds.
    1661          * If the second parameter is not given or is equal to 0, setPosition() is called, see #setPosition,
    1662          * i.e. the coordinates are changed without animation.
    1663          * @param {Array} where Array containing the x and y coordinate of the target location.
    1664          * @param {Number} [time] Number of milliseconds the animation should last.
    1665          * @param {Object} [options] Optional settings for the animation
    1666          * @param {function} [options.callback] A function that is called as soon as the animation is finished.
    1667          * @param {String} [options.effect='<>'] animation effects like speed fade in and out. possible values are
    1668          * '<>' for speed increase on start and slow down at the end (default) and '--' for constant speed during
    1669          * the whole animation.
    1670          * @returns {JXG.CoordsElement} Reference to itself.
    1671          * @see JXG.CoordsElement#moveAlong
    1672          * @see JXG.CoordsElement#visit
    1673          * @see JXG.GeometryElement#animate
    1674          */
    1675         moveTo: function (where, time, options) {
    1676             options = options || {};
    1677             where = new Coords(Const.COORDS_BY_USER, where, this.board);
    1678
    1679             var i,
    1680                 delay = this.board.attr.animationdelay,
    1681                 steps = Math.ceil(time / delay),
    1682                 coords = [],
    1683                 X = this.coords.usrCoords[1],
    1684                 Y = this.coords.usrCoords[2],
    1685                 dX = (where.usrCoords[1] - X),
    1686                 dY = (where.usrCoords[2] - Y),
    1687
    1688                 /** @ignore */
    1689                 stepFun = function (i) {
    1690                     if (options.effect && options.effect === '<>') {
    1691                         return Math.pow(Math.sin((i / steps) * Math.PI / 2), 2);
    1692                     }
    1693                     return i / steps;
    1694                 };
    1695
    1696             if (!Type.exists(time) || time === 0 ||
    1697                 (Math.abs(where.usrCoords[0] - this.coords.usrCoords[0]) > Mat.eps)) {
    1698                 this.setPosition(Const.COORDS_BY_USER, where.usrCoords);
    1699                 return this.board.update(this);
    1700             }
    1701
    1702             // In case there is no callback and we are already at the endpoint we can stop here
    1703             if (!Type.exists(options.callback) && Math.abs(dX) < Mat.eps && Math.abs(dY) < Mat.eps) {
    1704                 return this;
    1705             }
    1706
    1707             for (i = steps; i >= 0; i--) {
    1708                 coords[steps - i] = [where.usrCoords[0], X + dX * stepFun(i), Y + dY * stepFun(i)];
    1709             }
    1710
    1711             this.animationPath = coords;
    1712             this.animationCallback = options.callback;
    1713             this.board.addAnimation(this);
    1714
    1715             return this;
    1716         },
    1717
    1718         /**
    1719          * Starts an animated point movement towards the given coordinates <tt>where</tt>. After arriving at
    1720          * <tt>where</tt> the point moves back to where it started. The animation is done after <tt>time</tt>
    1721          * milliseconds.
    1722          * @param {Array} where Array containing the x and y coordinate of the target location.
    1723          * @param {Number} time Number of milliseconds the animation should last.
    1724          * @param {Object} [options] Optional settings for the animation
    1725          * @param {function} [options.callback] A function that is called as soon as the animation is finished.
    1726          * @param {String} [options.effect='<>'] animation effects like speed fade in and out. possible values are
    1727          * '<>' for speed increase on start and slow down at the end (default) and '--' for constant speed during
    1728          * the whole animation.
    1729          * @param {Number} [options.repeat=1] How often this animation should be repeated.
    1730          * @returns {JXG.CoordsElement} Reference to itself.
    1731          * @see JXG.CoordsElement#moveAlong
    1732          * @see JXG.CoordsElement#moveTo
    1733          * @see JXG.GeometryElement#animate
    1734          */
    1735         visit: function (where, time, options) {
    1736             where = new Coords(Const.COORDS_BY_USER, where, this.board);
    1737
    1738             var i, j, steps,
    1739                 delay = this.board.attr.animationdelay,
    1740                 coords = [],
    1741                 X = this.coords.usrCoords[1],
    1742                 Y = this.coords.usrCoords[2],
    1743                 dX = (where.usrCoords[1] - X),
    1744                 dY = (where.usrCoords[2] - Y),
    1745
    1746                 /** @ignore */
    1747                 stepFun = function (i) {
    1748                     var x = (i < steps / 2 ? 2 * i / steps : 2 * (steps - i) / steps);
    1749
    1750                     if (options.effect && options.effect === '<>') {
    1751                         return Math.pow(Math.sin(x * Math.PI / 2), 2);
    1752                     }
    1753
    1754                     return x;
    1755                 };
    1756
    1757             // support legacy interface where the third parameter was the number of repeats
    1758             if (Type.isNumber(options)) {
    1759                 options = {repeat: options};
    1760             } else {
    1761                 options = options || {};
    1762                 if (!Type.exists(options.repeat)) {
    1763                     options.repeat = 1;
    1764                 }
    1765             }
    1766
    1767             steps = Math.ceil(time / (delay * options.repeat));
    1768
    1769             for (j = 0; j < options.repeat; j++) {
    1770                 for (i = steps; i >= 0; i--) {
    1771                     coords[j * (steps + 1) + steps - i] = [where.usrCoords[0], X + dX * stepFun(i), Y + dY * stepFun(i)];
    1772                 }
    1773             }
    1774             this.animationPath = coords;
    1775             this.animationCallback = options.callback;
    1776             this.board.addAnimation(this);
    1777
    1778             return this;
    1779         },
    1780
    1781         /**
    1782          * Animates a glider. Is called by the browser after startAnimation is called.
    1783          * @param {Number} direction The direction the glider is animated.
    1784          * @param {Number} stepCount The number of steps in which the parent element is divided.
    1785          * Must be at least 1.
    1786          * @see #startAnimation
    1787          * @see #stopAnimation
    1788          * @private
    1789          * @returns {JXG.CoordsElement} Reference to itself.
    1790          */
    1791         _anim: function (direction, stepCount) {
    1792             var dX, dY, alpha, startPoint, newX, radius,
    1793                 sp1c, sp2c,
    1794                 res,
    1795                 d;
    1796
    1797             this.intervalCount += 1;
    1798             if (this.intervalCount > stepCount) {
    1799                 this.intervalCount = 0;
    1800             }
    1801
    1802             if (this.slideObject.elementClass === Const.OBJECT_CLASS_LINE) {
    1803                 sp1c = this.slideObject.point1.coords.scrCoords;
    1804                 sp2c = this.slideObject.point2.coords.scrCoords;
    1805
    1806                 dX = Math.round((sp2c[1] - sp1c[1]) * this.intervalCount / stepCount);
    1807                 dY = Math.round((sp2c[2] - sp1c[2]) * this.intervalCount / stepCount);
    1808                 if (direction > 0) {
    1809                     startPoint = this.slideObject.point1;
    1810                 } else {
    1811                     startPoint = this.slideObject.point2;
    1812                     dX *= -1;
    1813                     dY *= -1;
    1814                 }
    1815
    1816                 this.coords.setCoordinates(Const.COORDS_BY_SCREEN, [
    1817                     startPoint.coords.scrCoords[1] + dX,
    1818                     startPoint.coords.scrCoords[2] + dY
    1819                 ]);
    1820             } else if (this.slideObject.elementClass === Const.OBJECT_CLASS_CURVE) {
    1821                 if (direction > 0) {
    1822                     newX = Math.round(this.intervalCount / stepCount * this.board.canvasWidth);
    1823                 } else {
    1824                     newX = Math.round((stepCount - this.intervalCount) / stepCount * this.board.canvasWidth);
    1825                 }
    1826
    1827                 this.coords.setCoordinates(Const.COORDS_BY_SCREEN, [newX, 0]);
    1828                 res = Geometry.projectPointToCurve(this, this.slideObject, this.board);
    1829                 this.coords = res[0];
    1830                 this.position = res[1];
    1831             } else if (this.slideObject.elementClass === Const.OBJECT_CLASS_CIRCLE) {
    1832                 alpha = 2 * Math.PI;
    1833                 if (direction < 0) {
    1834                     alpha *= this.intervalCount / stepCount;
    1835                 } else {
    1836                     alpha *= (stepCount - this.intervalCount);
    1837                 }
    1838                 radius = this.slideObject.Radius();
    1839
    1840                 this.coords.setCoordinates(Const.COORDS_BY_USER, [
    1841                     this.slideObject.center.coords.usrCoords[1] + radius * Math.cos(alpha),
    1842                     this.slideObject.center.coords.usrCoords[2] + radius * Math.sin(alpha)
    1843                 ]);
    1844             }
    1845
    1846             this.board.update(this);
    1847             return this;
    1848         },
    1849
    1850         // documented in GeometryElement
    1851         getTextAnchor: function () {
    1852             return this.coords;
    1853         },
    1854
    1855         // documented in GeometryElement
    1856         getLabelAnchor: function () {
    1857             return this.coords;
    1858         },
    1859
    1860         // documented in element.js
    1861         getParents: function () {
    1862             var p = [this.Z(), this.X(), this.Y()];
    1863
    1864             if (this.parents.length !== 0) {
    1865                 p = this.parents;
    1866             }
    1867
    1868             if (this.type === Const.OBJECT_TYPE_GLIDER) {
    1869                 p = [this.X(), this.Y(), this.slideObject.id];
    1870             }
    1871
    1872             return p;
    1873         }
    1874
    1875     });
    1876
    1877     /**
    1878      * Generic method to create point, text or image.
    1879      * Determines the type of the construction, i.e. free, or constrained by function,
    1880      * transformation or of glider type.
    1881      * @param{Object} Callback Object type, e.g. JXG.Point, JXG.Text or JXG.Image
    1882      * @param{Object} board Link to the board object
    1883      * @param{Array} coords Array with coordinates. This may be: array of numbers, function
    1884      * returning an array of numbers, array of functions returning a number, object and transformation.
    1885      * If the attribute "slideObject" exists, a glider element is constructed.
    1886      * @param{Object} attr Attributes object
    1887      * @param{Object} arg1 Optional argument 1: in case of text this is the text content,
    1888      * in case of an image this is the url.
    1889      * @param{Array} arg2 Optional argument 2: in case of image this is an array containing the size of
    1890      * the image.
    1891      * @returns{Object} returns the created object or false.
    1892      */
    1893     JXG.CoordsElement.create = function (Callback, board, coords, attr, arg1, arg2) {
    1894         var el, isConstrained = false, i;
    1895
    1896         for (i = 0; i < coords.length; i++) {
    1897             if (Type.isFunction(coords[i]) || Type.isString(coords[i])) {
    1898                 isConstrained = true;
    1899             }
    1900         }
    1901
    1902         if (!isConstrained) {
    1903             if (Type.isNumber(coords[0]) && Type.isNumber(coords[1])) {
    1904                 el = new Callback(board, coords, attr, arg1, arg2);
    1905
    1906                 if (Type.exists(attr.slideobject)) {
    1907                     el.makeGlider(attr.slideobject);
    1908                 } else {
    1909                     // Free element
    1910                     el.baseElement = el;
    1911                 }
    1912                 el.isDraggable = true;
    1913             } else if (Type.isObject(coords[0]) && Type.isTransformationOrArray(coords[1])) {
    1914                 // Transformation
    1915                 // TODO less general specification of isObject
    1916                 el = new Callback(board, [0, 0], attr, arg1, arg2);
    1917                 el.addTransform(coords[0], coords[1]);
    1918                 el.isDraggable = false;
    1919             } else {
    1920                 return false;
    1921             }
    1922         } else {
    1923             el = new Callback(board, [0, 0], attr, arg1, arg2);
    1924             el.addConstraint(coords);
    1925         }
    1926
    1927         el.handleSnapToGrid();
    1928         el.handleSnapToPoints();
    1929         el.handleAttractors();
    1930
    1931         el.addParents(coords);
    1932         return el;
    1933     };
    1934
    1935     return JXG.CoordsElement;
    1936
    1937 });
    1938