      1 /*
      2     Copyright 2008-2025
      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 <https://www.gnu.org/licenses/>
     27     and <https://opensource.org/licenses/MIT/>.
     28  */
     29
     30 /*global JXG: true, define: true, console: true, window: true*/
     31 /*jslint nomen: true, plusplus: true*/
     32
     33 /**
     34  * @fileoverview The geometry object CoordsElement is defined in this file.
     35  * This object provides the coordinate handling of points, images and texts.
     36  */
     37
     38 import JXG from "../jxg.js";
     39 import Mat from "../math/math.js";
     40 import Geometry from "../math/geometry.js";
     41 import Numerics from "../math/numerics.js";
     42 import Statistics from "../math/statistics.js";
     43 import Coords from "./coords.js";
     44 import Const from "./constants.js";
     45 import Type from "../utils/type.js";
     46
     47 /**
     48  * An element containing coords is a basic geometric element.
     49  * This is a parent class for points, images and texts.
     50  * It holds common methods for
     51  * all kind of coordinate elements like points, texts and images.
     52  * It can not be used directly.
     53  * @class Creates a new coords element object. It is a parent class for points, images and texts.
     54  * Do not use this constructor to create an element.
     55  *
     56  * @private
     57  * @augments JXG.GeometryElement
     58  * @param {Array} coordinates An array with the affine user coordinates of the point.
     59  * {@link JXG.Options#elements}, and - optionally - a name and an id.
     60  */
     61 JXG.CoordsElement = function (coordinates, isLabel) {
     62     var i;
     63
     64     if (!Type.exists(coordinates)) {
     65         coordinates = [1, 0, 0];
     66     }
     67
     68     for (i = 0; i < coordinates.length; ++i) {
     69         coordinates[i] = parseFloat(coordinates[i]);
     70     }
     71
     72     /**
     73      * Coordinates of the element.
     74      * @type JXG.Coords
     75      * @private
     76      */
     77     this.coords = new Coords(Const.COORDS_BY_USER, coordinates, this.board);
     78
     79     // initialCoords and actualCoords are needed to handle transformations
     80     // and dragging of objects simultaneously.
     81     // actualCoords are needed for non-points since the visible objects
     82     // is transformed in the renderer.
     83     // For labels and other relative texts, actualCoords is ignored, see
     84     // board.initMoveObject
     85     this.initialCoords = new Coords(Const.COORDS_BY_USER, coordinates, this.board);
     86     this.actualCoords = new Coords(Const.COORDS_BY_USER, coordinates, this.board);
     87
     88     /**
     89      * Relative position on a slide element (line, circle, curve) if element is a glider on this element.
     90      * @type Number
     91      * @private
     92      */
     93     this.position = null;
     94
     95     /**
     96      * True if there the method this.updateConstraint() has been set. It is
     97      * probably different from the prototype function() {return this;}.
     98      * Used in updateCoords fo glider elements.
     99      *
    100      * @see JXG.CoordsElement#updateCoords
    101      * @type Boolean
    102      * @private
    103      */
    104     this.isConstrained = false;
    105
    106     /**
    107      * Determines whether the element slides on a polygon if point is a glider.
    108      * @type Boolean
    109      * @default false
    110      * @private
    111      */
    112     this.onPolygon = false;
    113
    114     /**
    115      * When used as a glider this member stores the object, where to glide on.
    116      * To set the object to glide on use the method
    117      * {@link JXG.Point#makeGlider} and DO NOT set this property directly
    118      * as it will break the dependency tree.
    119      * @type JXG.GeometryElement
    120      */
    121     this.slideObject = null;
    122
    123     /**
    124      * List of elements the element is bound to, i.e. the element glides on.
    125      * Only the last entry is active.
    126      * Use {@link JXG.Point#popSlideObject} to remove the currently active slideObject.
    127      */
    128     this.slideObjects = [];
    129
    130     /**
    131      * A {@link JXG.CoordsElement#updateGlider} call is usually followed
    132      * by a general {@link JXG.Board#update} which calls
    133      * {@link JXG.CoordsElement#updateGliderFromParent}.
    134      * To prevent double updates, {@link JXG.CoordsElement#needsUpdateFromParent}
    135      * is set to false in updateGlider() and reset to true in the following call to
    136      * {@link JXG.CoordsElement#updateGliderFromParent}
    137      * @type Boolean
    138      */
    139     this.needsUpdateFromParent = true;
    140
    141     /**
    142      * Stores the groups of this element in an array of Group.
    143      * @type Array
    144      * @see JXG.Group
    145      * @private
    146      */
    147     this.groups = [];
    148
    149     /*
    150      * Do we need this?
    151      */
    152     this.Xjc = null;
    153     this.Yjc = null;
    154
    155     // documented in GeometryElement
    156     this.methodMap = Type.deepCopy(this.methodMap, {
    157         move: "moveTo",
    158         moveTo: "moveTo",
    159         moveAlong: "moveAlong",
    160         visit: "visit",
    161         glide: "makeGlider",
    162         makeGlider: "makeGlider",
    163         intersect: "makeIntersection",
    164         makeIntersection: "makeIntersection",
    165         X: "X",
    166         Y: "Y",
    167         Coords: "Coords",
    168         free: "free",
    169         setPosition: "setGliderPosition",
    170         setGliderPosition: "setGliderPosition",
    171         addConstraint: "addConstraint",
    172         dist: "Dist",
    173         Dist: "Dist",
    174         onPolygon: "onPolygon",
    175         startAnimation: "startAnimation",
    176         stopAnimation: "stopAnimation"
    177     });
    178
    179     /*
    180      * this.element may have been set by the object constructor.
    181      */
    182     if (Type.exists(this.element)) {
    183         this.addAnchor(coordinates, isLabel);
    184     }
    185     this.isDraggable = true;
    186 };
    187
    188 JXG.extend(
    189     JXG.CoordsElement.prototype,
    190     /** @lends JXG.CoordsElement.prototype */ {
    191         /**
    192          * Dummy function for unconstrained points or gliders.
    193          * @private
    194          */
    195         updateConstraint: function () {
    196             return this;
    197         },
    198
    199         /**
    200          * Updates the coordinates of the element.
    201          * @private
    202          */
    203         updateCoords: function (fromParent) {
    204             if (!this.needsUpdate) {
    205                 return this;
    206             }
    207
    208             if (!Type.exists(fromParent)) {
    209                 fromParent = false;
    210             }
    211
    212             if (!this.evalVisProp('frozen')) {
    213                 this.updateConstraint();
    214             }
    215
    216             /*
    217              * We need to calculate the new coordinates no matter of the elements visibility because
    218              * a child could be visible and depend on the coordinates of the element/point (e.g. perpendicular).
    219              *
    220              * Check if the element is a glider and calculate new coords in dependency of this.slideObject.
    221              * This function is called with fromParent==true in case it is a glider element for example if
    222              * the defining elements of the line or circle have been changed.
    223              */
    224             if (this.type === Const.OBJECT_TYPE_GLIDER) {
    225                 if (this.isConstrained) {
    226                     fromParent = false;
    227                 }
    228
    229                 if (fromParent) {
    230                     this.updateGliderFromParent();
    231                 } else {
    232                     this.updateGlider();
    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, d, v,
    255                 p1c, p2c, poly, cc, pos,
    256                 angle, sgn, alpha, beta,
    257                 delta = 2.0 * Math.PI,
    258                 cp, c, invMat,
    259                 newCoords, newPos,
    260                 doRound = false,
    261                 ev_sw,
    262                 snappedTo, snapValues,
    263                 slide = this.slideObject,
    264                 res, cu,
    265                 slides = [],
    266                 isTransformed;
    267
    268             this.needsUpdateFromParent = false;
    269             if (slide.elementClass === Const.OBJECT_CLASS_CIRCLE) {
    270                 if (this.evalVisProp('isgeonext')) {
    271                     delta = 1.0;
    272                 }
    273                 newCoords = Geometry.projectPointToCircle(this, slide, this.board);
    274                 newPos =
    275                     Geometry.rad(
    276                         [slide.center.X() + 1.0, slide.center.Y()],
    277                         slide.center,
    278                         this
    279                     ) / delta;
    280             } else if (slide.elementClass === Const.OBJECT_CLASS_LINE) {
    281                 /*
    282                  * onPolygon==true: the point is a slider on a segment and this segment is one of the
    283                  * "borders" of a polygon.
    284                  * This is a GEONExT feature.
    285                  */
    286                 if (this.onPolygon) {
    287                     p1c = slide.point1.coords.usrCoords;
    288                     p2c = slide.point2.coords.usrCoords;
    289                     i = 1;
    290                     d = p2c[i] - p1c[i];
    291
    292                     if (Math.abs(d) < Mat.eps) {
    293                         i = 2;
    294                         d = p2c[i] - p1c[i];
    295                     }
    296
    297                     cc = Geometry.projectPointToLine(this, slide, this.board);
    298                     pos = (cc.usrCoords[i] - p1c[i]) / d;
    299                     poly = slide.parentPolygon;
    300
    301                     if (pos < 0) {
    302                         for (i = 0; i < poly.borders.length; i++) {
    303                             if (slide === poly.borders[i]) {
    304                                 slide =
    305                                     poly.borders[
    306                                     (i - 1 + poly.borders.length) % poly.borders.length
    307                                     ];
    308                                 break;
    309                             }
    310                         }
    311                     } else if (pos > 1.0) {
    312                         for (i = 0; i < poly.borders.length; i++) {
    313                             if (slide === poly.borders[i]) {
    314                                 slide =
    315                                     poly.borders[
    316                                     (i + 1 + poly.borders.length) % poly.borders.length
    317                                     ];
    318                                 break;
    319                             }
    320                         }
    321                     }
    322
    323                     // If the slide object has changed, save the change to the glider.
    324                     if (slide.id !== this.slideObject.id) {
    325                         this.slideObject = slide;
    326                     }
    327                 }
    328
    329                 p1c = slide.point1.coords;
    330                 p2c = slide.point2.coords;
    331
    332                 // Distance between the two defining points
    333                 d = p1c.distance(Const.COORDS_BY_USER, p2c);
    334
    335                 // The defining points are identical
    336                 if (d < Mat.eps) {
    337                     //this.coords.setCoordinates(Const.COORDS_BY_USER, p1c);
    338                     newCoords = p1c;
    339                     doRound = true;
    340                     newPos = 0.0;
    341                 } else {
    342                     newCoords = Geometry.projectPointToLine(this, slide, this.board);
    343                     p1c = p1c.usrCoords.slice(0);
    344                     p2c = p2c.usrCoords.slice(0);
    345
    346                     // The second point is an ideal point
    347                     if (Math.abs(p2c[0]) < Mat.eps) {
    348                         i = 1;
    349                         d = p2c[i];
    350
    351                         if (Math.abs(d) < Mat.eps) {
    352                             i = 2;
    353                             d = p2c[i];
    354                         }
    355
    356                         d = (newCoords.usrCoords[i] - p1c[i]) / d;
    357                         sgn = d >= 0 ? 1 : -1;
    358                         d = Math.abs(d);
    359                         newPos = (sgn * d) / (d + 1);
    360
    361                         // The first point is an ideal point
    362                     } else if (Math.abs(p1c[0]) < Mat.eps) {
    363                         i = 1;
    364                         d = p1c[i];
    365
    366                         if (Math.abs(d) < Mat.eps) {
    367                             i = 2;
    368                             d = p1c[i];
    369                         }
    370
    371                         d = (newCoords.usrCoords[i] - p2c[i]) / d;
    372
    373                         // 1.0 - d/(1-d);
    374                         if (d < 0.0) {
    375                             newPos = (1 - 2.0 * d) / (1.0 - d);
    376                         } else {
    377                             newPos = 1 / (d + 1);
    378                         }
    379                     } else {
    380                         i = 1;
    381                         d = p2c[i] - p1c[i];
    382
    383                         if (Math.abs(d) < Mat.eps) {
    384                             i = 2;
    385                             d = p2c[i] - p1c[i];
    386                         }
    387                         newPos = (newCoords.usrCoords[i] - p1c[i]) / d;
    388                     }
    389                 }
    390
    391                 // Snap the glider to snap values.
    392                 snappedTo = this.findClosestSnapValue(newPos);
    393                 if (snappedTo !== null) {
    394                     snapValues = this.evalVisProp('snapvalues');
    395                     newPos = (snapValues[snappedTo] - this._smin) / (this._smax - this._smin);
    396                     this.update(true);
    397                 } else {
    398                     // Snap the glider point of the slider into its appropriate position
    399                     // First, recalculate the new value of this.position
    400                     // Second, call update(fromParent==true) to make the positioning snappier.
    401                     ev_sw = this.evalVisProp('snapwidth');
    402                     if (
    403                         ev_sw > 0.0 && Math.abs(this._smax - this._smin) >= Mat.eps
    404                     ) {
    405                         newPos = Math.max(Math.min(newPos, 1), 0);
    406                         // v = newPos * (this._smax - this._smin) + this._smin;
    407                         // v = Math.round(v / ev_sw) * ev_sw;
    408                         v = newPos * (this._smax - this._smin);
    409                         v = Math.round(v / ev_sw) * ev_sw + this._smin;
    410                         newPos = (v - this._smin) / (this._smax - this._smin);
    411                         this.update(true);
    412                     }
    413                 }
    414
    415                 p1c = slide.point1.coords;
    416                 if (
    417                     !slide.evalVisProp('straightfirst') &&
    418                     Math.abs(p1c.usrCoords[0]) > Mat.eps &&
    419                     newPos < 0
    420                 ) {
    421                     newCoords = p1c;
    422                     doRound = true;
    423                     newPos = 0;
    424                 }
    425
    426                 p2c = slide.point2.coords;
    427                 if (
    428                     !slide.evalVisProp('straightlast') &&
    429                     Math.abs(p2c.usrCoords[0]) > Mat.eps &&
    430                     newPos > 1
    431                 ) {
    432                     newCoords = p2c;
    433                     doRound = true;
    434                     newPos = 1;
    435                 }
    436             } else if (slide.type === Const.OBJECT_TYPE_TURTLE) {
    437                 // In case, the point is a constrained glider.
    438                 this.updateConstraint();
    439                 res = Geometry.projectPointToTurtle(this, slide, this.board);
    440                 newCoords = res[0];
    441                 newPos = res[1]; // save position for the overwriting below
    442             } else if (slide.elementClass === Const.OBJECT_CLASS_CURVE) {
    443                 if (
    444                     slide.type === Const.OBJECT_TYPE_ARC ||
    445                     slide.type === Const.OBJECT_TYPE_SECTOR
    446                 ) {
    447                     newCoords = Geometry.projectPointToCircle(this, slide, this.board);
    448
    449                     angle = Geometry.rad(slide.radiuspoint, slide.center, this);
    450                     alpha = 0.0;
    451                     beta = Geometry.rad(slide.radiuspoint, slide.center, slide.anglepoint);
    452                     newPos = angle;
    453
    454                     ev_sw = slide.evalVisProp('selection');
    455                     if (
    456                         (ev_sw === "minor" && beta > Math.PI) ||
    457                         (ev_sw === "major" && beta < Math.PI)
    458                     ) {
    459                         alpha = beta;
    460                         beta = 2 * Math.PI;
    461                     }
    462
    463                     // Correct the position if we are outside of the sector/arc
    464                     if (angle < alpha || angle > beta) {
    465                         newPos = beta;
    466
    467                         if (
    468                             (angle < alpha && angle > alpha * 0.5) ||
    469                             (angle > beta && angle > beta * 0.5 + Math.PI)
    470                         ) {
    471                             newPos = alpha;
    472                         }
    473
    474                         this.needsUpdateFromParent = true;
    475                         this.updateGliderFromParent();
    476                     }
    477
    478                     delta = beta - alpha;
    479                     if (this.visProp.isgeonext) {
    480                         delta = 1.0;
    481                     }
    482                     if (Math.abs(delta) > Mat.eps) {
    483                         newPos /= delta;
    484                     }
    485                 } else {
    486                     // In case, the point is a constrained glider.
    487                     this.updateConstraint();
    488
    489                     // Handle the case if the curve comes from a transformation of a continuous curve.
    490                     if (slide.transformations.length > 0) {
    491                         isTransformed = false;
    492                         // TODO this might buggy, see the recursion
    493                         // in line.js getCurveTangentDir
    494                         res = slide.getTransformationSource();
    495                         if (res[0]) {
    496                             isTransformed = res[0];
    497                             slides.push(slide);
    498                             slides.push(res[1]);
    499                         }
    500                         // Recurse
    501                         while (res[0] && Type.exists(res[1]._transformationSource)) {
    502                             res = res[1].getTransformationSource();
    503                             slides.push(res[1]);
    504                         }
    505
    506                         cu = this.coords.usrCoords;
    507                         if (isTransformed) {
    508                             for (i = 0; i < slides.length; i++) {
    509                                 slides[i].updateTransformMatrix();
    510                                 invMat = Mat.inverse(slides[i].transformMat);
    511                                 cu = Mat.matVecMult(invMat, cu);
    512                             }
    513                             cp = new Coords(Const.COORDS_BY_USER, cu, this.board).usrCoords;
    514                             c = Geometry.projectCoordsToCurve(
    515                                 cp[1],
    516                                 cp[2],
    517                                 this.position || 0,
    518                                 slides[slides.length - 1],
    519                                 this.board
    520                             );
    521                             // projectPointCurve() already would apply the transformation.
    522                             // Since we are projecting on the original curve, we have to do
    523                             // the transformations "by hand".
    524                             cu = c[0].usrCoords;
    525                             for (i = slides.length - 2; i >= 0; i--) {
    526                                 cu = Mat.matVecMult(slides[i].transformMat, cu);
    527                             }
    528                             c[0] = new Coords(Const.COORDS_BY_USER, cu, this.board);
    529                         } else {
    530                             slide.updateTransformMatrix();
    531                             invMat = Mat.inverse(slide.transformMat);
    532                             cu = Mat.matVecMult(invMat, cu);
    533                             cp = new Coords(Const.COORDS_BY_USER, cu, this.board).usrCoords;
    534                             c = Geometry.projectCoordsToCurve(
    535                                 cp[1],
    536                                 cp[2],
    537                                 this.position || 0,
    538                                 slide,
    539                                 this.board
    540                             );
    541                         }
    542
    543                         newCoords = c[0];
    544                         newPos = c[1];
    545                     } else {
    546                         res = Geometry.projectPointToCurve(this, slide, this.board);
    547                         newCoords = res[0];
    548                         newPos = res[1]; // save position for the overwriting below
    549                     }
    550                 }
    551             } else if (Type.isPoint(slide)) {
    552                 //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToPoint(this, slide, this.board).usrCoords, false);
    553                 newCoords = Geometry.projectPointToPoint(this, slide, this.board);
    554                 newPos = this.position; // save position for the overwriting below
    555             }
    556
    557             this.coords.setCoordinates(Const.COORDS_BY_USER, newCoords.usrCoords, doRound);
    558             this.position = newPos;
    559         },
    560
    561         /**
    562          * Find the closest entry in snapValues that is within snapValueDistance of pos.
    563          *
    564          * @param {Number} pos Value for which snapping is calculated.
    565          * @returns {Number} Index of the value to snap to, or null.
    566          * @private
    567          */
    568         findClosestSnapValue: function (pos) {
    569             var i, d,
    570                 snapValues, snapValueDistance,
    571                 snappedTo = null;
    572
    573             // Snap the glider to snap values.
    574             snapValues = this.evalVisProp('snapvalues');
    575             snapValueDistance = this.evalVisProp('snapvaluedistance');
    576
    577             if (Type.isArray(snapValues) &&
    578                 Math.abs(this._smax - this._smin) >= Mat.eps &&
    579                 snapValueDistance > 0.0) {
    580                 for (i = 0; i < snapValues.length; i++) {
    581                     d = Math.abs(pos * (this._smax - this._smin) + this._smin - snapValues[i]);
    582                     if (d < snapValueDistance) {
    583                         snapValueDistance = d;
    584                         snappedTo = i;
    585                     }
    586                 }
    587             }
    588
    589             return snappedTo;
    590         },
    591
    592         /**
    593          * Update of a glider in case a parent element has been updated. That means the
    594          * relative position of the glider stays the same.
    595          * @private
    596          */
    597         updateGliderFromParent: function () {
    598             var p1c, p2c, r, lbda, c,
    599                 slide = this.slideObject,
    600                 slides = [],
    601                 res, i, isTransformed,
    602                 baseangle, alpha, angle, beta,
    603                 delta = 2.0 * Math.PI;
    604
    605             if (!this.needsUpdateFromParent) {
    606                 this.needsUpdateFromParent = true;
    607                 return;
    608             }
    609
    610             if (slide.elementClass === Const.OBJECT_CLASS_CIRCLE) {
    611                 r = slide.Radius();
    612                 if (this.evalVisProp('isgeonext')) {
    613                     delta = 1.0;
    614                 }
    615                 c = [
    616                     slide.center.X() + r * Math.cos(this.position * delta),
    617                     slide.center.Y() + r * Math.sin(this.position * delta)
    618                 ];
    619             } else if (slide.elementClass === Const.OBJECT_CLASS_LINE) {
    620                 p1c = slide.point1.coords.usrCoords;
    621                 p2c = slide.point2.coords.usrCoords;
    622
    623                 // If one of the defining points of the line does not exist,
    624                 // the glider should disappear
    625                 if (
    626                     (p1c[0] === 0 && p1c[1] === 0 && p1c[2] === 0) ||
    627                     (p2c[0] === 0 && p2c[1] === 0 && p2c[2] === 0)
    628                 ) {
    629                     c = [0, 0, 0];
    630                     // The second point is an ideal point
    631                 } else if (Math.abs(p2c[0]) < Mat.eps) {
    632                     lbda = Math.min(Math.abs(this.position), 1 - Mat.eps);
    633                     lbda /= 1.0 - lbda;
    634
    635                     if (this.position < 0) {
    636                         lbda = -lbda;
    637                     }
    638
    639                     c = [
    640                         p1c[0] + lbda * p2c[0],
    641                         p1c[1] + lbda * p2c[1],
    642                         p1c[2] + lbda * p2c[2]
    643                     ];
    644                     // The first point is an ideal point
    645                 } else if (Math.abs(p1c[0]) < Mat.eps) {
    646                     lbda = Math.max(this.position, Mat.eps);
    647                     lbda = Math.min(lbda, 2 - Mat.eps);
    648
    649                     if (lbda > 1) {
    650                         lbda = (lbda - 1) / (lbda - 2);
    651                     } else {
    652                         lbda = (1 - lbda) / lbda;
    653                     }
    654
    655                     c = [
    656                         p2c[0] + lbda * p1c[0],
    657                         p2c[1] + lbda * p1c[1],
    658                         p2c[2] + lbda * p1c[2]
    659                     ];
    660                 } else {
    661                     lbda = this.position;
    662                     c = [
    663                         p1c[0] + lbda * (p2c[0] - p1c[0]),
    664                         p1c[1] + lbda * (p2c[1] - p1c[1]),
    665                         p1c[2] + lbda * (p2c[2] - p1c[2])
    666                     ];
    667                 }
    668             } else if (slide.type === Const.OBJECT_TYPE_TURTLE) {
    669                 this.coords.setCoordinates(Const.COORDS_BY_USER, [
    670                     slide.Z(this.position),
    671                     slide.X(this.position),
    672                     slide.Y(this.position)
    673                 ]);
    674                 // In case, the point is a constrained glider.
    675                 this.updateConstraint();
    676                 c = Geometry.projectPointToTurtle(this, slide, this.board)[0].usrCoords;
    677             } else if (slide.elementClass === Const.OBJECT_CLASS_CURVE) {
    678                 // Handle the case if the curve comes from a transformation of a continuous curve.
    679                 isTransformed = false;
    680                 res = slide.getTransformationSource();
    681                 if (res[0]) {
    682                     isTransformed = res[0];
    683                     slides.push(slide);
    684                     slides.push(res[1]);
    685                 }
    686                 // Recurse
    687                 while (res[0] && Type.exists(res[1]._transformationSource)) {
    688                     res = res[1].getTransformationSource();
    689                     slides.push(res[1]);
    690                 }
    691                 if (isTransformed) {
    692                     this.coords.setCoordinates(Const.COORDS_BY_USER, [
    693                         slides[slides.length - 1].Z(this.position),
    694                         slides[slides.length - 1].X(this.position),
    695                         slides[slides.length - 1].Y(this.position)
    696                     ]);
    697                 } else {
    698                     this.coords.setCoordinates(Const.COORDS_BY_USER, [
    699                         slide.Z(this.position),
    700                         slide.X(this.position),
    701                         slide.Y(this.position)
    702                     ]);
    703                 }
    704
    705                 if (
    706                     slide.type === Const.OBJECT_TYPE_ARC ||
    707                     slide.type === Const.OBJECT_TYPE_SECTOR
    708                 ) {
    709                     baseangle = Geometry.rad(
    710                         [slide.center.X() + 1, slide.center.Y()],
    711                         slide.center,
    712                         slide.radiuspoint
    713                     );
    714
    715                     alpha = 0.0;
    716                     beta = Geometry.rad(slide.radiuspoint, slide.center, slide.anglepoint);
    717
    718                     if (
    719                         (slide.visProp.selection === "minor" && beta > Math.PI) ||
    720                         (slide.visProp.selection === "major" && beta < Math.PI)
    721                     ) {
    722                         alpha = beta;
    723                         beta = 2 * Math.PI;
    724                     }
    725
    726                     delta = beta - alpha;
    727                     if (this.evalVisProp('isgeonext')) {
    728                         delta = 1.0;
    729                     }
    730                     angle = this.position * delta;
    731
    732                     // Correct the position if we are outside of the sector/arc
    733                     if (angle < alpha || angle > beta) {
    734                         angle = beta;
    735
    736                         if (
    737                             (angle < alpha && angle > alpha * 0.5) ||
    738                             (angle > beta && angle > beta * 0.5 + Math.PI)
    739                         ) {
    740                             angle = alpha;
    741                         }
    742
    743                         this.position = angle;
    744                         if (Math.abs(delta) > Mat.eps) {
    745                             this.position /= delta;
    746                         }
    747                     }
    748
    749                     r = slide.Radius();
    750                     c = [
    751                         slide.center.X() + r * Math.cos(this.position * delta + baseangle),
    752                         slide.center.Y() + r * Math.sin(this.position * delta + baseangle)
    753                     ];
    754                 } else {
    755                     // In case, the point is a constrained glider.
    756                     this.updateConstraint();
    757
    758                     if (isTransformed) {
    759                         c = Geometry.projectPointToCurve(
    760                             this,
    761                             slides[slides.length - 1],
    762                             this.board
    763                         )[0].usrCoords;
    764                         // projectPointCurve() already would do the transformation.
    765                         // But since we are projecting on the original curve, we have to do
    766                         // the transformation "by hand".
    767                         for (i = slides.length - 2; i >= 0; i--) {
    768                             c = new Coords(
    769                                 Const.COORDS_BY_USER,
    770                                 Mat.matVecMult(slides[i].transformMat, c),
    771                                 this.board
    772                             ).usrCoords;
    773                         }
    774                     } else {
    775                         c = Geometry.projectPointToCurve(this, slide, this.board)[0].usrCoords;
    776                     }
    777                 }
    778             } else if (Type.isPoint(slide)) {
    779                 c = Geometry.projectPointToPoint(this, slide, this.board).usrCoords;
    780             }
    781
    782             this.coords.setCoordinates(Const.COORDS_BY_USER, c, false);
    783         },
    784
    785         updateRendererGeneric: function (rendererMethod) {
    786             //var wasReal;
    787
    788             if (!this.needsUpdate || !this.board.renderer) {
    789                 return this;
    790             }
    791
    792             if (this.visPropCalc.visible) {
    793                 //wasReal = this.isReal;
    794                 this.isReal = !isNaN(this.coords.usrCoords[1] + this.coords.usrCoords[2]);
    795                 //Homogeneous coords: ideal point
    796                 this.isReal =
    797                     Math.abs(this.coords.usrCoords[0]) > Mat.eps ? this.isReal : false;
    798
    799                 if (
    800                     // wasReal &&
    801                     !this.isReal
    802                 ) {
    803                     this.updateVisibility(false);
    804                 }
    805             }
    806
    807             // Call the renderer only if element is visible.
    808             // Update the position
    809             if (this.visPropCalc.visible) {
    810                 this.board.renderer[rendererMethod](this);
    811             }
    812
    813             // Update the label if visible.
    814             if (
    815                 this.hasLabel &&
    816                 this.visPropCalc.visible &&
    817                 this.label &&
    818                 this.label.visPropCalc.visible &&
    819                 this.isReal
    820             ) {
    821                 this.label.update();
    822                 this.board.renderer.updateText(this.label);
    823             }
    824
    825             // Update rendNode display
    826             this.setDisplayRendNode();
    827             // if (this.visPropCalc.visible !== this.visPropOld.visible) {
    828             //     this.board.renderer.display(this, this.visPropCalc.visible);
    829             //     this.visPropOld.visible = this.visPropCalc.visible;
    830             //
    831             //     if (this.hasLabel) {
    832             //         this.board.renderer.display(this.label, this.label.visPropCalc.visible);
    833             //     }
    834             // }
    835
    836             this.needsUpdate = false;
    837             return this;
    838         },
    839
    840         /**
    841          * Getter method for x, this is used by for CAS-points to access point coordinates.
    842          * @returns {Number} User coordinate of point in x direction.
    843          */
    844         X: function () {
    845             return this.coords.usrCoords[1];
    846         },
    847
    848         /**
    849          * Getter method for y, this is used by CAS-points to access point coordinates.
    850          * @returns {Number} User coordinate of point in y direction.
    851          */
    852         Y: function () {
    853             return this.coords.usrCoords[2];
    854         },
    855
    856         /**
    857          * Getter method for z, this is used by CAS-points to access point coordinates.
    858          * @returns {Number} User coordinate of point in z direction.
    859          */
    860         Z: function () {
    861             return this.coords.usrCoords[0];
    862         },
    863
    864         /**
    865          * Getter method for coordinates x, y and (optional) z.
    866          * @param {Number|String} [digits='auto'] Truncating rule for the digits in the infobox.
    867          * <ul>
    868          * <li>'auto': done automatically by JXG.autoDigits()
    869          * <li>'none': no truncation
    870          * <li>number: truncate after "number digits" with JXG.toFixed()
    871          * </ul>
    872          * @param {Boolean} [withZ=false] If set to true the return value will be <tt>(x | y | z)</tt> instead of <tt>(x, y)</tt>.
    873          * @returns {String} User coordinates of point.
    874          */
    875         Coords: function (withZ) {
    876             if (withZ) {
    877                 return this.coords.usrCoords.slice();
    878             }
    879             return this.coords.usrCoords.slice(1);
    880         },
    881         // Coords: function (digits, withZ) {
    882         //     var arr, sep;
    883
    884         //     digits = digits || 'auto';
    885
    886         //     if (withZ) {
    887         //         sep = ' | ';
    888         //     } else {
    889         //         sep = ', ';
    890         //     }
    891
    892         //     if (digits === 'none') {
    893         //         arr = [this.X(), sep, this.Y()];
    894         //         if (withZ) {
    895         //             arr.push(sep, this.Z());
    896         //         }
    897
    898         //     } else if (digits === 'auto') {
    899         //         if (this.useLocale()) {
    900         //             arr = [this.formatNumberLocale(this.X()), sep, this.formatNumberLocale(this.Y())];
    901         //             if (withZ) {
    902         //                 arr.push(sep, this.formatNumberLocale(this.Z()));
    903         //             }
    904         //         } else {
    905         //             arr = [Type.autoDigits(this.X()), sep, Type.autoDigits(this.Y())];
    906         //             if (withZ) {
    907         //                 arr.push(sep, Type.autoDigits(this.Z()));
    908         //             }
    909         //         }
    910
    911         //     } else {
    912         //         if (this.useLocale()) {
    913         //             arr = [this.formatNumberLocale(this.X(), digits), sep, this.formatNumberLocale(this.Y(), digits)];
    914         //             if (withZ) {
    915         //                 arr.push(sep, this.formatNumberLocale(this.Z(), digits));
    916         //             }
    917         //         } else {
    918         //             arr = [Type.toFixed(this.X(), digits), sep, Type.toFixed(this.Y(), digits)];
    919         //             if (withZ) {
    920         //                 arr.push(sep, Type.toFixed(this.Z(), digits));
    921         //             }
    922         //         }
    923         //     }
    924
    925         //     return '(' + arr.join('') + ')';
    926         // },
    927
    928         /**
    929          * New evaluation of the function term.
    930          * This is required for CAS-points: Their XTerm() method is
    931          * overwritten in {@link JXG.CoordsElement#addConstraint}.
    932          *
    933          * @returns {Number} User coordinate of point in x direction.
    934          * @private
    935          */
    936         XEval: function () {
    937             return this.coords.usrCoords[1];
    938         },
    939
    940         /**
    941          * New evaluation of the function term.
    942          * This is required for CAS-points: Their YTerm() method is overwritten
    943          * in {@link JXG.CoordsElement#addConstraint}.
    944          *
    945          * @returns {Number} User coordinate of point in y direction.
    946          * @private
    947          */
    948         YEval: function () {
    949             return this.coords.usrCoords[2];
    950         },
    951
    952         /**
    953          * New evaluation of the function term.
    954          * This is required for CAS-points: Their ZTerm() method is overwritten in
    955          * {@link JXG.CoordsElement#addConstraint}.
    956          *
    957          * @returns {Number} User coordinate of point in z direction.
    958          * @private
    959          */
    960         ZEval: function () {
    961             return this.coords.usrCoords[0];
    962         },
    963
    964         /**
    965          * Getter method for the distance to a second point, this is required for CAS-elements.
    966          * Here, function inlining seems to be worthwile (for plotting).
    967          * @param {JXG.Point} point2 The point to which the distance shall be calculated.
    968          * @returns {Number} Distance in user coordinate to the given point
    969          */
    970         Dist: function (point2) {
    971             if (this.isReal && point2.isReal) {
    972                 return this.coords.distance(Const.COORDS_BY_USER, point2.coords);
    973             }
    974             return NaN;
    975         },
    976
    977         /**
    978          * Alias for {@link JXG.Element#handleSnapToGrid}
    979          * @param {Boolean} force force snapping independent of what the snaptogrid attribute says
    980          * @returns {JXG.CoordsElement} Reference to this element
    981          */
    982         snapToGrid: function (force) {
    983             return this.handleSnapToGrid(force);
    984         },
    985
    986         /**
    987          * Let a point snap to the nearest point in distance of
    988          * {@link JXG.Point#attractorDistance}.
    989          * The function uses the coords object of the point as
    990          * its actual position.
    991          * @param {Boolean} force force snapping independent of what the snaptogrid attribute says
    992          * @returns {JXG.CoordsElement} Reference to this element
    993          */
    994         handleSnapToPoints: function (force) {
    995             var i,
    996                 pEl,
    997                 pCoords,
    998                 d = 0,
    999                 len,
    1000                 dMax = Infinity,
    1001                 c = null,
    1002                 ev_au,
    1003                 ev_ad,
    1004                 ev_is2p = this.evalVisProp('ignoredsnaptopoints'),
    1005                 len2,
    1006                 j,
    1007                 ignore = false;
    1008
    1009             len = this.board.objectsList.length;
    1010
    1011             if (ev_is2p) {
    1012                 len2 = ev_is2p.length;
    1013             }
    1014
    1015             if (this.evalVisProp('snaptopoints') || force) {
    1016                 ev_au = this.evalVisProp('attractorunit');
    1017                 ev_ad = this.evalVisProp('attractordistance');
    1018
    1019                 for (i = 0; i < len; i++) {
    1020                     pEl = this.board.objectsList[i];
    1021
    1022                     if (ev_is2p) {
    1023                         ignore = false;
    1024                         for (j = 0; j < len2; j++) {
    1025                             if (pEl === this.board.select(ev_is2p[j])) {
    1026                                 ignore = true;
    1027                                 break;
    1028                             }
    1029                         }
    1030                         if (ignore) {
    1031                             continue;
    1032                         }
    1033                     }
    1034
    1035                     if (Type.isPoint(pEl) && pEl !== this && pEl.visPropCalc.visible) {
    1036                         pCoords = Geometry.projectPointToPoint(this, pEl, this.board);
    1037                         if (ev_au === "screen") {
    1038                             d = pCoords.distance(Const.COORDS_BY_SCREEN, this.coords);
    1039                         } else {
    1040                             d = pCoords.distance(Const.COORDS_BY_USER, this.coords);
    1041                         }
    1042
    1043                         if (d < ev_ad && d < dMax) {
    1044                             dMax = d;
    1045                             c = pCoords;
    1046                         }
    1047                     }
    1048                 }
    1049
    1050                 if (c !== null) {
    1051                     this.coords.setCoordinates(Const.COORDS_BY_USER, c.usrCoords);
    1052                 }
    1053             }
    1054
    1055             return this;
    1056         },
    1057
    1058         /**
    1059          * Alias for {@link JXG.CoordsElement#handleSnapToPoints}.
    1060          *
    1061          * @param {Boolean} force force snapping independent of what the snaptogrid attribute says
    1062          * @returns {JXG.CoordsElement} Reference to this element
    1063          */
    1064         snapToPoints: function (force) {
    1065             return this.handleSnapToPoints(force);
    1066         },
    1067
    1068         /**
    1069          * A point can change its type from free point to glider
    1070          * and vice versa. If it is given an array of attractor elements
    1071          * (attribute attractors) and the attribute attractorDistance
    1072          * then the point will be made a glider if it less than attractorDistance
    1073          * apart from one of its attractor elements.
    1074          * If attractorDistance is equal to zero, the point stays in its
    1075          * current form.
    1076          * @returns {JXG.CoordsElement} Reference to this element
    1077          */
    1078         handleAttractors: function () {
    1079             var i,
    1080                 el,
    1081                 projCoords,
    1082                 d = 0.0,
    1083                 projection,
    1084                 ev_au = this.evalVisProp('attractorunit'),
    1085                 ev_ad = this.evalVisProp('attractordistance'),
    1086                 ev_sd = this.evalVisProp('snatchdistance'),
    1087                 ev_a = this.evalVisProp('attractors'),
    1088                 len = ev_a.length;
    1089
    1090             if (ev_ad === 0.0) {
    1091                 return;
    1092             }
    1093
    1094             for (i = 0; i < len; i++) {
    1095                 el = this.board.select(ev_a[i]);
    1096
    1097                 if (Type.exists(el) && el !== this) {
    1098                     if (Type.isPoint(el)) {
    1099                         projCoords = Geometry.projectPointToPoint(this, el, this.board);
    1100                     } else if (el.elementClass === Const.OBJECT_CLASS_LINE) {
    1101                         projection = Geometry.projectCoordsToSegment(
    1102                             this.coords.usrCoords,
    1103                             el.point1.coords.usrCoords,
    1104                             el.point2.coords.usrCoords
    1105                         );
    1106                         if (!el.evalVisProp('straightfirst') && projection[1] < 0.0) {
    1107                             projCoords = el.point1.coords;
    1108                         } else if (
    1109                             !el.evalVisProp('straightlast') &&
    1110                             projection[1] > 1.0
    1111                         ) {
    1112                             projCoords = el.point2.coords;
    1113                         } else {
    1114                             projCoords = new Coords(
    1115                                 Const.COORDS_BY_USER,
    1116                                 projection[0],
    1117                                 this.board
    1118                             );
    1119                         }
    1120                     } else if (el.elementClass === Const.OBJECT_CLASS_CIRCLE) {
    1121                         projCoords = Geometry.projectPointToCircle(this, el, this.board);
    1122                     } else if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
    1123                         projCoords = Geometry.projectPointToCurve(this, el, this.board)[0];
    1124                     } else if (el.type === Const.OBJECT_TYPE_TURTLE) {
    1125                         projCoords = Geometry.projectPointToTurtle(this, el, this.board)[0];
    1126                     } else if (el.type === Const.OBJECT_TYPE_POLYGON) {
    1127                         projCoords = new Coords(
    1128                             Const.COORDS_BY_USER,
    1129                             Geometry.projectCoordsToPolygon(this.coords.usrCoords, el),
    1130                             this.board
    1131                         );
    1132                     }
    1133
    1134                     if (ev_au === "screen") {
    1135                         d = projCoords.distance(Const.COORDS_BY_SCREEN, this.coords);
    1136                     } else {
    1137                         d = projCoords.distance(Const.COORDS_BY_USER, this.coords);
    1138                     }
    1139
    1140                     if (d < ev_ad) {
    1141                         if (
    1142                             !(
    1143                                 this.type === Const.OBJECT_TYPE_GLIDER &&
    1144                                 (el === this.slideObject ||
    1145                                     (this.slideObject &&
    1146                                         this.onPolygon &&
    1147                                         this.slideObject.parentPolygon === el))
    1148                             )
    1149                         ) {
    1150                             this.makeGlider(el);
    1151                         }
    1152                         break; // bind the point to the first attractor in its list.
    1153                     }
    1154                     if (
    1155                         d >= ev_sd &&
    1156                         (el === this.slideObject ||
    1157                             (this.slideObject &&
    1158                                 this.onPolygon &&
    1159                                 this.slideObject.parentPolygon === el))
    1160                     ) {
    1161                         this.popSlideObject();
    1162                     }
    1163                 }
    1164             }
    1165
    1166             return this;
    1167         },
    1168
    1169         /**
    1170          * Sets coordinates and calls the elements's update() method.
    1171          * @param {Number} method The type of coordinates used here.
    1172          * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
    1173          * @param {Array} coords coordinates <tt>([z], x, y)</tt> in screen/user units
    1174          * @returns {JXG.CoordsElement} this element
    1175          */
    1176         setPositionDirectly: function (method, coords) {
    1177             var i,
    1178                 c, dc, m,
    1179                 oldCoords = this.coords,
    1180                 newCoords;
    1181
    1182             if (this.relativeCoords) {
    1183                 c = new Coords(method, coords, this.board);
    1184                 if (this.evalVisProp('islabel')) {
    1185                     dc = Statistics.subtract(c.scrCoords, oldCoords.scrCoords);
    1186                     this.relativeCoords.scrCoords[1] += dc[1];
    1187                     this.relativeCoords.scrCoords[2] += dc[2];
    1188                 } else {
    1189                     dc = Statistics.subtract(c.usrCoords, oldCoords.usrCoords);
    1190                     this.relativeCoords.usrCoords[1] += dc[1];
    1191                     this.relativeCoords.usrCoords[2] += dc[2];
    1192                 }
    1193
    1194                 return this;
    1195             }
    1196
    1197             this.coords.setCoordinates(method, coords);
    1198             this.handleSnapToGrid();
    1199             this.handleSnapToPoints();
    1200             this.handleAttractors();
    1201
    1202             // Here, we set the object's "actualCoords", because
    1203             // coords and initialCoords coincide since transformations
    1204             // for these elements are handled in the renderers.
    1205             this.actualCoords.setCoordinates(Const.COORDS_BY_USER, this.coords.usrCoords);
    1206
    1207             // The element's coords have been set above to the new position `coords`.
    1208             // Now, determine the preimage of `coords`, prior to all transformations.
    1209             // This is needed for free elements that have a transformation bound to it.
    1210             if (this.transformations.length > 0) {
    1211                 if (method === Const.COORDS_BY_SCREEN) {
    1212                     newCoords = new Coords(method, coords, this.board).usrCoords;
    1213                 } else {
    1214                     if (coords.length === 2) {
    1215                         coords = [1].concat(coords);
    1216                     }
    1217                     newCoords = coords;
    1218                 }
    1219                 m = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
    1220                 for (i = 0; i < this.transformations.length; i++) {
    1221                     m = Mat.matMatMult(this.transformations[i].matrix, m);
    1222                 }
    1223                 newCoords = Mat.matVecMult(Mat.inverse(m), newCoords);
    1224
    1225                 this.initialCoords.setCoordinates(Const.COORDS_BY_USER, newCoords);
    1226                 if (this.elementClass !== Const.OBJECT_CLASS_POINT) {
    1227                     // This is necessary for images and texts.
    1228                     this.coords.setCoordinates(Const.COORDS_BY_USER, newCoords);
    1229                 }
    1230             }
    1231             this.prepareUpdate().update();
    1232
    1233             // If the user suspends the board updates we need to recalculate the relative position of
    1234             // the point on the slide object. This is done in updateGlider() which is NOT called during the
    1235             // update process triggered by unsuspendUpdate.
    1236             if (this.board.isSuspendedUpdate && this.type === Const.OBJECT_TYPE_GLIDER) {
    1237                 this.updateGlider();
    1238             }
    1239
    1240             return this;
    1241         },
    1242
    1243         /**
    1244          * Translates the point by <tt>tv = (x, y)</tt>.
    1245          * @param {Number} method The type of coordinates used here.
    1246          * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
    1247          * @param {Array} tv (x, y)
    1248          * @returns {JXG.CoordsElement}
    1249          */
    1250         setPositionByTransform: function (method, tv) {
    1251             var t;
    1252
    1253             tv = new Coords(method, tv, this.board);
    1254             t = this.board.create("transform", tv.usrCoords.slice(1), {
    1255                 type: "translate"
    1256             });
    1257
    1258             if (
    1259                 this.transformations.length > 0 &&
    1260                 this.transformations[this.transformations.length - 1].isNumericMatrix
    1261             ) {
    1262                 this.transformations[this.transformations.length - 1].melt(t);
    1263             } else {
    1264                 this.addTransform(this, t);
    1265             }
    1266
    1267             this.prepareUpdate().update();
    1268
    1269             return this;
    1270         },
    1271
    1272         /**
    1273          * Sets coordinates and calls the element's update() method.
    1274          * @param {Number} method The type of coordinates used here.
    1275          * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
    1276          * @param {Array} coords coordinates in screen/user units
    1277          * @returns {JXG.CoordsElement}
    1278          */
    1279         setPosition: function (method, coords) {
    1280             return this.setPositionDirectly(method, coords);
    1281         },
    1282
    1283         /**
    1284          * Sets the position of a glider relative to the defining elements
    1285          * of the {@link JXG.Point#slideObject}.
    1286          * @param {Number} x
    1287          * @returns {JXG.Point} Reference to the point element.
    1288          */
    1289         setGliderPosition: function (x) {
    1290             if (this.type === Const.OBJECT_TYPE_GLIDER) {
    1291                 this.position = x;
    1292                 this.board.update();
    1293             }
    1294
    1295             return this;
    1296         },
    1297
    1298         /**
    1299          * Convert the point to glider and update the construction.
    1300          * To move the point visual onto the glider, a call of board update is necessary.
    1301          * @param {String|Object} slide The object the point will be bound to.
    1302          */
    1303         makeGlider: function (slide) {
    1304             var slideobj = this.board.select(slide),
    1305                 onPolygon = false,
    1306                 min, i, dist;
    1307
    1308             if (slideobj.type === Const.OBJECT_TYPE_POLYGON) {
    1309                 // Search for the closest edge of the polygon.
    1310                 min = Number.MAX_VALUE;
    1311                 for (i = 0; i < slideobj.borders.length; i++) {
    1312                     dist = JXG.Math.Geometry.distPointLine(
    1313                         this.coords.usrCoords,
    1314                         slideobj.borders[i].stdform
    1315                     );
    1316                     if (dist < min) {
    1317                         min = dist;
    1318                         slide = slideobj.borders[i];
    1319                     }
    1320                 }
    1321                 slideobj = this.board.select(slide);
    1322                 onPolygon = true;
    1323             }
    1324
    1325             /* Gliders on Ticks are forbidden */
    1326             if (!Type.exists(slideobj)) {
    1327                 throw new Error("JSXGraph: slide object undefined.");
    1328             } else if (slideobj.type === Const.OBJECT_TYPE_TICKS) {
    1329                 throw new Error("JSXGraph: gliders on ticks are not possible.");
    1330             }
    1331
    1332             this.slideObject = this.board.select(slide);
    1333             this.slideObjects.push(this.slideObject);
    1334             this.addParents(slide);
    1335
    1336             this.type = Const.OBJECT_TYPE_GLIDER;
    1337             this.elType = 'glider';
    1338             this.visProp.snapwidth = -1; // By default, deactivate snapWidth
    1339             this.slideObject.addChild(this);
    1340             this.isDraggable = true;
    1341             this.onPolygon = onPolygon;
    1342
    1343             this.generatePolynomial = function () {
    1344                 return this.slideObject.generatePolynomial(this);
    1345             };
    1346
    1347             // Determine the initial value of this.position
    1348             this.updateGlider();
    1349             this.needsUpdateFromParent = true;
    1350             this.updateGliderFromParent();
    1351
    1352             return this;
    1353         },
    1354
    1355         /**
    1356          * Remove the last slideObject. If there are more than one elements the point is bound to,
    1357          * the second last element is the new active slideObject.
    1358          */
    1359         popSlideObject: function () {
    1360             if (this.slideObjects.length > 0) {
    1361                 this.slideObjects.pop();
    1362
    1363                 // It may not be sufficient to remove the point from
    1364                 // the list of childElement. For complex dependencies
    1365                 // one may have to go to the list of ancestor and descendants.  A.W.
    1366                 // Yes indeed, see #51 on github bug tracker
    1367                 //   delete this.slideObject.childElements[this.id];
    1368                 this.slideObject.removeChild(this);
    1369
    1370                 if (this.slideObjects.length === 0) {
    1371                     this.type = this._org_type;
    1372                     if (this.type === Const.OBJECT_TYPE_POINT) {
    1373                         this.elType = "point";
    1374                     } else if (this.elementClass === Const.OBJECT_CLASS_TEXT) {
    1375                         this.elType = "text";
    1376                     } else if (this.type === Const.OBJECT_TYPE_IMAGE) {
    1377                         this.elType = "image";
    1378                     } else if (this.type === Const.OBJECT_TYPE_FOREIGNOBJECT) {
    1379                         this.elType = "foreignobject";
    1380                     }
    1381
    1382                     this.slideObject = null;
    1383                 } else {
    1384                     this.slideObject = this.slideObjects[this.slideObjects.length - 1];
    1385                 }
    1386             }
    1387         },
    1388
    1389         /**
    1390          * Converts a calculated element into a free element,
    1391          * i.e. it will delete all ancestors and transformations and,
    1392          * if the element is currently a glider, will remove the slideObject reference.
    1393          */
    1394         free: function () {
    1395             var ancestorId, ancestor;
    1396             // child;
    1397
    1398             if (this.type !== Const.OBJECT_TYPE_GLIDER) {
    1399                 // remove all transformations
    1400                 this.transformations.length = 0;
    1401
    1402                 delete this.updateConstraint;
    1403                 this.isConstrained = false;
    1404                 // this.updateConstraint = function () {
    1405                 //     return this;
    1406                 // };
    1407
    1408                 if (!this.isDraggable) {
    1409                     this.isDraggable = true;
    1410
    1411                     if (this.elementClass === Const.OBJECT_CLASS_POINT) {
    1412                         this.type = Const.OBJECT_TYPE_POINT;
    1413                         this.elType = "point";
    1414                     }
    1415
    1416                     this.XEval = function () {
    1417                         return this.coords.usrCoords[1];
    1418                     };
    1419
    1420                     this.YEval = function () {
    1421                         return this.coords.usrCoords[2];
    1422                     };
    1423
    1424                     this.ZEval = function () {
    1425                         return this.coords.usrCoords[0];
    1426                     };
    1427
    1428                     this.Xjc = null;
    1429                     this.Yjc = null;
    1430                 } else {
    1431                     return;
    1432                 }
    1433             }
    1434
    1435             // a free point does not depend on anything. And instead of running through tons of descendants and ancestor
    1436             // structures, where we eventually are going to visit a lot of objects twice or thrice with hard to read and
    1437             // comprehend code, just run once through all objects and delete all references to this point and its label.
    1438             for (ancestorId in this.board.objects) {
    1439                 if (this.board.objects.hasOwnProperty(ancestorId)) {
    1440                     ancestor = this.board.objects[ancestorId];
    1441
    1442                     if (ancestor.descendants) {
    1443                         delete ancestor.descendants[this.id];
    1444                         delete ancestor.childElements[this.id];
    1445
    1446                         if (this.hasLabel) {
    1447                             delete ancestor.descendants[this.label.id];
    1448                             delete ancestor.childElements[this.label.id];
    1449                         }
    1450                     }
    1451                 }
    1452             }
    1453
    1454             // A free point does not depend on anything. Remove all ancestors.
    1455             this.ancestors = {}; // only remove the reference
    1456             this.parents = [];
    1457
    1458             // Completely remove all slideObjects of the element
    1459             this.slideObject = null;
    1460             this.slideObjects = [];
    1461             if (this.elementClass === Const.OBJECT_CLASS_POINT) {
    1462                 this.type = Const.OBJECT_TYPE_POINT;
    1463                 this.elType = "point";
    1464             } else if (this.elementClass === Const.OBJECT_CLASS_TEXT) {
    1465                 this.type = this._org_type;
    1466                 this.elType = "text";
    1467             } else if (this.elementClass === Const.OBJECT_CLASS_OTHER) {
    1468                 this.type = this._org_type;
    1469                 this.elType = "image";
    1470             }
    1471         },
    1472
    1473         /**
    1474          * Convert the point to CAS point and call update().
    1475          * @param {Array} terms [[zterm], xterm, yterm] defining terms for the z, x and y coordinate.
    1476          * The z-coordinate is optional and it is used for homogeneous coordinates.
    1477          * The coordinates may be either <ul>
    1478          *   <li>a JavaScript function,</li>
    1479          *   <li>a string containing GEONExT syntax. This string will be converted into a JavaScript
    1480          *     function here,</li>
    1481          *   <li>a Number</li>
    1482          *   <li>a pointer to a slider object. This will be converted into a call of the Value()-method
    1483          *     of this slider.</li>
    1484          *   </ul>
    1485          * @see JXG.GeonextParser#geonext2JS
    1486          */
    1487         addConstraint: function (terms) {
    1488             var i, v,
    1489                 newfuncs = [],
    1490                 what = ["X", "Y"],
    1491                 makeConstFunction = function (z) {
    1492                     return function () {
    1493                         return z;
    1494                     };
    1495                 },
    1496                 makeSliderFunction = function (a) {
    1497                     return function () {
    1498                         return a.Value();
    1499                     };
    1500                 };
    1501
    1502             if (this.elementClass === Const.OBJECT_CLASS_POINT) {
    1503                 this.type = Const.OBJECT_TYPE_CAS;
    1504             }
    1505
    1506             this.isDraggable = false;
    1507
    1508             for (i = 0; i < terms.length; i++) {
    1509                 v = terms[i];
    1510
    1511                 if (Type.isString(v)) {
    1512                     // Convert GEONExT syntax into JavaScript syntax
    1513                     //t  = JXG.GeonextParser.geonext2JS(v, this.board);
    1514                     //newfuncs[i] = new Function('','return ' + t + ';');
    1515                     //v = GeonextParser.replaceNameById(v, this.board);
    1516                     newfuncs[i] = this.board.jc.snippet(v, true, null, true);
    1517                     this.addParentsFromJCFunctions([newfuncs[i]]);
    1518
    1519                     // Store original term as 'Xjc' or 'Yjc'
    1520                     if (terms.length === 2) {
    1521                         this[what[i] + "jc"] = terms[i];
    1522                     }
    1523                 } else if (Type.isFunction(v)) {
    1524                     newfuncs[i] = v;
    1525                 } else if (Type.isNumber(v)) {
    1526                     newfuncs[i] = makeConstFunction(v);
    1527                 } else if (Type.isObject(v) && Type.isFunction(v.Value)) {
    1528                     // Slider
    1529                     newfuncs[i] = makeSliderFunction(v);
    1530                 }
    1531
    1532                 newfuncs[i].origin = v;
    1533             }
    1534
    1535             // Intersection function
    1536             if (terms.length === 1) {
    1537                 this.updateConstraint = function () {
    1538                     var c = newfuncs[0]();
    1539
    1540                     // Array
    1541                     if (Type.isArray(c)) {
    1542                         this.coords.setCoordinates(Const.COORDS_BY_USER, c);
    1543                         // Coords object
    1544                     } else {
    1545                         this.coords = c;
    1546                     }
    1547                     return this;
    1548                 };
    1549                 // Euclidean coordinates
    1550             } else if (terms.length === 2) {
    1551                 this.XEval = newfuncs[0];
    1552                 this.YEval = newfuncs[1];
    1553                 this.addParents([newfuncs[0].origin, newfuncs[1].origin]);
    1554
    1555                 this.updateConstraint = function () {
    1556                     this.coords.setCoordinates(Const.COORDS_BY_USER, [
    1557                         this.XEval(),
    1558                         this.YEval()
    1559                     ]);
    1560                     return this;
    1561                 };
    1562                 // Homogeneous coordinates
    1563             } else {
    1564                 this.ZEval = newfuncs[0];
    1565                 this.XEval = newfuncs[1];
    1566                 this.YEval = newfuncs[2];
    1567
    1568                 this.addParents([newfuncs[0].origin, newfuncs[1].origin, newfuncs[2].origin]);
    1569
    1570                 this.updateConstraint = function () {
    1571                     this.coords.setCoordinates(Const.COORDS_BY_USER, [
    1572                         this.ZEval(),
    1573                         this.XEval(),
    1574                         this.YEval()
    1575                     ]);
    1576                     return this;
    1577                 };
    1578             }
    1579             this.isConstrained = true;
    1580
    1581             /**
    1582              * We have to do an update. Otherwise, elements relying on this point will receive NaN.
    1583              */
    1584             this.prepareUpdate().update();
    1585             if (!this.board.isSuspendedUpdate) {
    1586                 this.updateVisibility().updateRenderer();
    1587                 if (this.hasLabel) {
    1588                     this.label.fullUpdate();
    1589                 }
    1590             }
    1591
    1592             return this;
    1593         },
    1594
    1595         /**
    1596          * In case there is an attribute "anchor", the element is bound to
    1597          * this anchor element.
    1598          * This is handled with this.relativeCoords. If the element is a label
    1599          * relativeCoords are given in scrCoords, otherwise in usrCoords.
    1600          * @param{Array} coordinates Offset from the anchor element. These are the values for this.relativeCoords.
    1601          * In case of a label, coordinates are screen coordinates. Otherwise, coordinates are user coordinates.
    1602          * @param{Boolean} isLabel Yes/no
    1603          * @private
    1604          */
    1605         addAnchor: function (coordinates, isLabel) {
    1606             if (isLabel) {
    1607                 this.relativeCoords = new Coords(
    1608                     Const.COORDS_BY_SCREEN,
    1609                     coordinates.slice(0, 2),
    1610                     this.board
    1611                 );
    1612             } else {
    1613                 this.relativeCoords = new Coords(Const.COORDS_BY_USER, coordinates, this.board);
    1614             }
    1615             this.element.addChild(this);
    1616             if (isLabel) {
    1617                 this.addParents(this.element);
    1618             }
    1619
    1620             this.XEval = function () {
    1621                 var sx, coords, anchor, ev_o;
    1622
    1623                 if (this.evalVisProp('islabel')) {
    1624                     ev_o = this.evalVisProp('offset');
    1625                     sx = parseFloat(ev_o[0]);
    1626                     anchor = this.element.getLabelAnchor();
    1627                     coords = new Coords(
    1628                         Const.COORDS_BY_SCREEN,
    1629                         [sx + this.relativeCoords.scrCoords[1] + anchor.scrCoords[1], 0],
    1630                         this.board
    1631                     );
    1632
    1633                     return coords.usrCoords[1];
    1634                 }
    1635
    1636                 anchor = this.element.getTextAnchor();
    1637                 return this.relativeCoords.usrCoords[1] + anchor.usrCoords[1];
    1638             };
    1639
    1640             this.YEval = function () {
    1641                 var sy, coords, anchor, ev_o;
    1642
    1643                 if (this.evalVisProp('islabel')) {
    1644                     ev_o = this.evalVisProp('offset');
    1645                     sy = -parseFloat(ev_o[1]);
    1646                     anchor = this.element.getLabelAnchor();
    1647                     coords = new Coords(
    1648                         Const.COORDS_BY_SCREEN,
    1649                         [0, sy + this.relativeCoords.scrCoords[2] + anchor.scrCoords[2]],
    1650                         this.board
    1651                     );
    1652
    1653                     return coords.usrCoords[2];
    1654                 }
    1655
    1656                 anchor = this.element.getTextAnchor();
    1657                 return this.relativeCoords.usrCoords[2] + anchor.usrCoords[2];
    1658             };
    1659
    1660             this.ZEval = Type.createFunction(1, this.board, "");
    1661
    1662             this.updateConstraint = function () {
    1663                 this.coords.setCoordinates(Const.COORDS_BY_USER, [
    1664                     this.ZEval(),
    1665                     this.XEval(),
    1666                     this.YEval()
    1667                 ]);
    1668             };
    1669             this.isConstrained = true;
    1670
    1671             this.updateConstraint();
    1672         },
    1673
    1674         /**
    1675          * Applies the transformations of the element.
    1676          * This method applies to text and images. Point transformations are handled differently.
    1677          * @param {Boolean} fromParent True if the drag comes from a child element. Unused.
    1678          * @returns {JXG.CoordsElement} Reference to itself.
    1679          */
    1680         updateTransform: function (fromParent) {
    1681             var c, i;
    1682
    1683             if (this.transformations.length === 0 || this.baseElement === null) {
    1684                 return this;
    1685             }
    1686
    1687             // This method is called for non-points only.
    1688             // Here, we set the object's "actualCoords", because
    1689             // coords and initialCoords coincide since transformations
    1690             // for these elements are handled in the renderers.
    1691
    1692             this.transformations[0].update();
    1693             if (this === this.baseElement) {
    1694                 // Case of bindTo
    1695                 c = this.transformations[0].apply(this, "self");
    1696             } else {
    1697                 c = this.transformations[0].apply(this.baseElement);
    1698             }
    1699             for (i = 1; i < this.transformations.length; i++) {
    1700                 this.transformations[i].update();
    1701                 c = Mat.matVecMult(this.transformations[i].matrix, c);
    1702             }
    1703             this.actualCoords.setCoordinates(Const.COORDS_BY_USER, c);
    1704
    1705             return this;
    1706         },
    1707
    1708         /**
    1709          * Add transformations to this element.
    1710          * @param {JXG.GeometryElement} el
    1711          * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation}
    1712          * or an array of {@link JXG.Transformation}s.
    1713          * @returns {JXG.CoordsElement} Reference to itself.
    1714          */
    1715         addTransform: function (el, transform) {
    1716             var i,
    1717                 list = Type.isArray(transform) ? transform : [transform],
    1718                 len = list.length;
    1719
    1720             // There is only one baseElement possible
    1721             if (this.transformations.length === 0) {
    1722                 this.baseElement = el;
    1723             }
    1724
    1725             for (i = 0; i < len; i++) {
    1726                 this.transformations.push(list[i]);
    1727             }
    1728
    1729             return this;
    1730         },
    1731
    1732         /**
    1733          * Animate the point.
    1734          * @param {Number|Function} direction The direction the glider is animated. Can be +1 or -1.
    1735          * @param {Number|Function} stepCount The number of steps in which the parent element is divided.
    1736          * Must be at least 1.
    1737          * @param {Number|Function} delay Time in msec between two animation steps. Default is 250.
    1738          * @returns {JXG.CoordsElement} Reference to iself.
    1739          *
    1740          * @name Glider#startAnimation
    1741          * @see Glider#stopAnimation
    1742          * @function
    1743          * @example
    1744          * // Divide the circle line into 6 steps and
    1745          * // visit every step 330 msec counterclockwise.
    1746          * var ci = board.create('circle', [[-1,2], [2,1]]);
    1747          * var gl = board.create('glider', [0,2, ci]);
    1748          * gl.startAnimation(-1, 6, 330);
    1749          *
    1750          * </pre><div id="JXG0f35a50e-e99d-11e8-a1ca-04d3b0c2aad3" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1751          * <script type="text/javascript">
    1752          *     (function() {
    1753          *         var board = JXG.JSXGraph.initBoard('JXG0f35a50e-e99d-11e8-a1ca-04d3b0c2aad3',
    1754          *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    1755          *     // Divide the circle line into 6 steps and
    1756          *     // visit every step 330 msec counterclockwise.
    1757          *     var ci = board.create('circle', [[-1,2], [2,1]]);
    1758          *     var gl = board.create('glider', [0,2, ci]);
    1759          *     gl.startAnimation(-1, 6, 330);
    1760          *
    1761          *     })();
    1762          *
    1763          * </script><pre>
    1764          * @example
    1765          * //animate example closed curve
    1766          * var c1 = board.create('curve',[(u)=>4*Math.cos(u),(u)=>2*Math.sin(u)+2,0,2*Math.PI]);
    1767          * var p2 = board.create('glider', [c1]);
    1768          * var button1 = board.create('button', [1, 7, 'start animation',function(){p2.startAnimation(1,8)}]);
    1769          * var button2 = board.create('button', [1, 5, 'stop animation',function(){p2.stopAnimation()}]);
    1770          * </pre><div class="jxgbox" id="JXG10e885ea-b05d-4e7d-a473-bac2554bce68" style="width: 200px; height: 200px;"></div>
    1771          * <script type="text/javascript">
    1772          *   var gpex4_board = JXG.JSXGraph.initBoard('JXG10e885ea-b05d-4e7d-a473-bac2554bce68', {boundingbox: [-1, 10, 10, -1], axis: true, showcopyright: false, shownavigation: false});
    1773          *   var gpex4_c1 = gpex4_board.create('curve',[(u)=>4*Math.cos(u)+4,(u)=>2*Math.sin(u)+2,0,2*Math.PI]);
    1774          *   var gpex4_p2 = gpex4_board.create('glider', [gpex4_c1]);
    1775          *   gpex4_board.create('button', [1, 7, 'start animation',function(){gpex4_p2.startAnimation(1,8)}]);
    1776          *   gpex4_board.create('button', [1, 5, 'stop animation',function(){gpex4_p2.stopAnimation()}]);
    1777          * </script><pre>
    1778          *
    1779          * @example
    1780          * // Divide the slider area into 20 steps and
    1781          * // visit every step 30 msec.
    1782          * var n = board.create('slider',[[-2,4],[2,4],[1,5,100]],{name:'n'});
    1783          * n.startAnimation(1, 20, 30);
    1784          *
    1785          * </pre><div id="JXG40ce04b8-e99c-11e8-a1ca-04d3b0c2aad3" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1786          * <script type="text/javascript">
    1787          *     (function() {
    1788          *         var board = JXG.JSXGraph.initBoard('JXG40ce04b8-e99c-11e8-a1ca-04d3b0c2aad3',
    1789          *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    1790          *     // Divide the slider area into 20 steps and
    1791          *     // visit every step 30 msec.
    1792          *     var n = board.create('slider',[[-2,4],[2,4],[1,5,100]],{name:'n'});
    1793          *     n.startAnimation(1, 20, 30);
    1794          *
    1795          *     })();
    1796          * </script><pre>
    1797          *
    1798          */
    1799         startAnimation: function (direction, stepCount, delay) {
    1800             var dir = Type.evaluate(direction),
    1801                 sc = Type.evaluate(stepCount),
    1802                 that = this;
    1803
    1804             delay = Type.evaluate(delay) || 250;
    1805
    1806             if (this.type === Const.OBJECT_TYPE_GLIDER && !Type.exists(this.intervalCode)) {
    1807                 this.intervalCode = window.setInterval(function () {
    1808                     that._anim(dir, sc);
    1809                 }, delay);
    1810
    1811                 if (!Type.exists(this.intervalCount)) {
    1812                     this.intervalCount = 0;
    1813                 }
    1814             }
    1815             return this;
    1816         },
    1817
    1818         /**
    1819          * Stop animation.
    1820          * @name Glider#stopAnimation
    1821          * @see Glider#startAnimation
    1822          * @function
    1823          * @returns {JXG.CoordsElement} Reference to itself.
    1824          */
    1825         stopAnimation: function () {
    1826             if (Type.exists(this.intervalCode)) {
    1827                 window.clearInterval(this.intervalCode);
    1828                 delete this.intervalCode;
    1829             }
    1830
    1831             return this;
    1832         },
    1833
    1834         /**
    1835          * Starts an animation which moves the point along a given path in given time.
    1836          * @param {Array|function} path The path the point is moved on.
    1837          * This can be either an array of arrays or containing x and y values of the points of
    1838          * the path, or an array of points, or a function taking the amount of elapsed time since the animation
    1839          * has started and returns an array containing a x and a y value or NaN.
    1840          * In case of NaN the animation stops.
    1841          * @param {Number} time The time in milliseconds in which to finish the animation
    1842          * @param {Object} [options] Optional settings for the animation.
    1843          * @param {function} [options.callback] A function that is called as soon as the animation is finished.
    1844          * @param {Boolean} [options.interpolate=true] If <tt>path</tt> is an array moveAlong()
    1845          * will interpolate the path
    1846          * using {@link JXG.Math.Numerics.Neville}. Set this flag to false if you don't want to use interpolation.
    1847          * @returns {JXG.CoordsElement} Reference to itself.
    1848          * @see JXG.CoordsElement#moveTo
    1849          * @see JXG.CoordsElement#visit
    1850          * @see JXG.CoordsElement#moveAlongES6
    1851          * @see JXG.GeometryElement#animate
    1852          */
    1853         moveAlong: function (path, time, options) {
    1854             options = options || {};
    1855
    1856             var i,
    1857                 neville,
    1858                 interpath = [],
    1859                 p = [],
    1860                 delay = this.board.attr.animationdelay,
    1861                 steps = time / delay,
    1862                 len,
    1863                 pos,
    1864                 part,
    1865                 makeFakeFunction = function (i, j) {
    1866                     return function () {
    1867                         return path[i][j];
    1868                     };
    1869                 };
    1870
    1871             if (Type.isArray(path)) {
    1872                 len = path.length;
    1873                 for (i = 0; i < len; i++) {
    1874                     if (Type.isPoint(path[i])) {
    1875                         p[i] = path[i];
    1876                     } else {
    1877                         p[i] = {
    1878                             elementClass: Const.OBJECT_CLASS_POINT,
    1879                             X: makeFakeFunction(i, 0),
    1880                             Y: makeFakeFunction(i, 1)
    1881                         };
    1882                     }
    1883                 }
    1884
    1885                 time = time || 0;
    1886                 if (time === 0) {
    1887                     this.setPosition(Const.COORDS_BY_USER, [
    1888                         p[p.length - 1].X(),
    1889                         p[p.length - 1].Y()
    1890                     ]);
    1891                     return this.board.update(this);
    1892                 }
    1893
    1894                 if (!Type.exists(options.interpolate) || options.interpolate) {
    1895                     neville = Numerics.Neville(p);
    1896                     for (i = 0; i < steps; i++) {
    1897                         interpath[i] = [];
    1898                         interpath[i][0] = neville[0](((steps - i) / steps) * neville[3]());
    1899                         interpath[i][1] = neville[1](((steps - i) / steps) * neville[3]());
    1900                     }
    1901                 } else {
    1902                     len = path.length - 1;
    1903                     for (i = 0; i < steps; ++i) {
    1904                         pos = Math.floor((i / steps) * len);
    1905                         part = (i / steps) * len - pos;
    1906
    1907                         interpath[i] = [];
    1908                         interpath[i][0] = (1.0 - part) * p[pos].X() + part * p[pos + 1].X();
    1909                         interpath[i][1] = (1.0 - part) * p[pos].Y() + part * p[pos + 1].Y();
    1910                     }
    1911                     interpath.push([p[len].X(), p[len].Y()]);
    1912                     interpath.reverse();
    1913                     /*
    1914                     for (i = 0; i < steps; i++) {
    1915                         interpath[i] = [];
    1916                         interpath[i][0] = path[Math.floor((steps - i) / steps * (path.length - 1))][0];
    1917                         interpath[i][1] = path[Math.floor((steps - i) / steps * (path.length - 1))][1];
    1918                     }
    1919                     */
    1920                 }
    1921
    1922                 this.animationPath = interpath;
    1923             } else if (Type.isFunction(path)) {
    1924                 this.animationPath = path;
    1925                 this.animationStart = new Date().getTime();
    1926             }
    1927
    1928             this.animationCallback = options.callback;
    1929             this.board.addAnimation(this);
    1930
    1931             return this;
    1932         },
    1933
    1934         /**
    1935          * Starts an animated point movement towards the given coordinates <tt>where</tt>.
    1936          * The animation is done after <tt>time</tt> milliseconds.
    1937          * If the second parameter is not given or is equal to 0, setPosition() is called, see
    1938          * {@link JXG.CoordsElement#setPosition},
    1939          * i.e. the coordinates are changed without animation.
    1940          * @param {Array} where Array containing the x and y coordinate of the target location.
    1941          * @param {Number} [time] Number of milliseconds the animation should last.
    1942          * @param {Object} [options] Optional settings for the animation
    1943          * @param {function} [options.callback] A function that is called as soon as the animation is finished.
    1944          * @param {String} [options.effect='<>'|'>'|'<'] animation effects like speed fade in and out. possible values are
    1945          * '<>' for speed increase on start and slow down at the end (default), '<' for speed up, '>' for slow down, and '--' for constant speed during
    1946          * the whole animation.
    1947          * @returns {JXG.CoordsElement} Reference to itself.
    1948          * @see JXG.CoordsElement#setPosition
    1949          * @see JXG.CoordsElement#moveAlong
    1950          * @see JXG.CoordsElement#visit
    1951          * @see JXG.CoordsElement#moveToES6
    1952          * @see JXG.GeometryElement#animate
    1953          * @example
    1954          * // moveTo() with different easing options and callback options
    1955          * let yInit = 3
    1956          * let [A, B, C, D] = ['==', '<>', '<', '>'].map((s) => board.create('point', [4, yInit--], { name: s, label: { fontSize: 24 } }))
    1957          * let seg = board.create('segment', [A, [() => A.X(), 0]])  // shows linear
    1958          *
    1959          * let isLeftRight = true;
    1960          * let buttonMove = board.create('button', [-2, 4, 'left',
    1961          * () => {
    1962          *    isLeftRight = !isLeftRight;
    1963          *    buttonMove.rendNodeButton.innerHTML = isLeftRight ? 'left' : 'right'
    1964          *    let x = isLeftRight ? 4 : -4
    1965          *    let sym = isLeftRight ? 'triangleleft' : 'triangleright'
    1966          *
    1967          *    A.moveTo([x, 3], 1000, { callback: () => A.setAttribute({ face: sym, size: 5 }) })
    1968          *    B.moveTo([x, 2], 1000, { callback: () => B.setAttribute({ face: sym, size: 5 }), effect: "<>" })
    1969          *    C.moveTo([x, 1], 1000, { callback: () => C.setAttribute({ face: sym, size: 5 }), effect: "<" })
    1970          *    D.moveTo([x, 0], 1000, { callback: () => D.setAttribute({ face: sym, size: 5 }), effect: ">" })
    1971          *
    1972          * }]);
    1973          *
    1974          * </pre><div id="JXG0f35a50e-e99d-11e8-a1ca-04d3b0c2aad4" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1975          * <script type="text/javascript">
    1976          * {
    1977          * let board = JXG.JSXGraph.initBoard('JXG0f35a50e-e99d-11e8-a1ca-04d3b0c2aad4')
    1978          * let yInit = 3
    1979          * let [A, B, C, D] = ['==', '<>', '<', '>'].map((s) => board.create('point', [4, yInit--], { name: s, label: { fontSize: 24 } }))
    1980          * let seg = board.create('segment', [A, [() => A.X(), 0]])  // shows linear
    1981          *
    1982          * let isLeftRight = true;
    1983          * let buttonMove = board.create('button', [-2, 4, 'left',
    1984          * () => {
    1985          *    isLeftRight = !isLeftRight;
    1986          *    buttonMove.rendNodeButton.innerHTML = isLeftRight ? 'left' : 'right'
    1987          *    let x = isLeftRight ? 4 : -4
    1988          *    let sym = isLeftRight ? 'triangleleft' : 'triangleright'
    1989          *
    1990          *    A.moveTo([x, 3], 1000, { callback: () => A.setAttribute({ face: sym, size: 5 }) })
    1991          *    B.moveTo([x, 2], 1000, { callback: () => B.setAttribute({ face: sym, size: 5 }), effect: "<>" })
    1992          *    C.moveTo([x, 1], 1000, { callback: () => C.setAttribute({ face: sym, size: 5 }), effect: "<" })
    1993          *    D.moveTo([x, 0], 1000, { callback: () => D.setAttribute({ face: sym, size: 5 }), effect: ">" })
    1994          *
    1995          * }]);
    1996          *}
    1997          *</script><pre>
    1998          */
    1999         moveTo: function (where, time, options) {
    2000             options = options || {};
    2001             where = new Coords(Const.COORDS_BY_USER, where, this.board);
    2002
    2003             var i,
    2004                 delay = this.board.attr.animationdelay,
    2005                 steps = Math.ceil(time / delay),
    2006                 coords = [],
    2007                 X = this.coords.usrCoords[1],
    2008                 Y = this.coords.usrCoords[2],
    2009                 dX = where.usrCoords[1] - X,
    2010                 dY = where.usrCoords[2] - Y,
    2011                 /** @ignore */
    2012                 stepFun = function (i) {
    2013                     var x = i / steps;  // absolute progress of the animatin
    2014
    2015                     if (options.effect) {
    2016                         if (options.effect === "<>") {
    2017                             return Math.pow(Math.sin((x * Math.PI) / 2), 2);
    2018                         }
    2019                         if (options.effect === "<") {   // cubic ease in
    2020                             return x * x * x;
    2021                         }
    2022                         if (options.effect === ">") {   // cubic ease out
    2023                             return 1 - Math.pow(1 - x, 3);
    2024                         }
    2025                         if (options.effect === "==") {
    2026                             return i / steps;       // linear
    2027                         }
    2028                         throw new Error("valid effects are '==', '<>', '>', and '<'.");
    2029                     }
    2030                     return i / steps;  // default
    2031                 };
    2032
    2033             if (
    2034                 !Type.exists(time) ||
    2035                 time === 0 ||
    2036                 Math.abs(where.usrCoords[0] - this.coords.usrCoords[0]) > Mat.eps
    2037             ) {
    2038                 this.setPosition(Const.COORDS_BY_USER, where.usrCoords);
    2039                 return this.board.update(this);
    2040             }
    2041
    2042             // In case there is no callback and we are already at the endpoint we can stop here
    2043             if (
    2044                 !Type.exists(options.callback) &&
    2045                 Math.abs(dX) < Mat.eps &&
    2046                 Math.abs(dY) < Mat.eps
    2047             ) {
    2048                 return this;
    2049             }
    2050
    2051             for (i = steps; i >= 0; i--) {
    2052                 coords[steps - i] = [
    2053                     where.usrCoords[0],
    2054                     X + dX * stepFun(i),
    2055                     Y + dY * stepFun(i)
    2056                 ];
    2057             }
    2058
    2059             this.animationPath = coords;
    2060             this.animationCallback = options.callback;
    2061             this.board.addAnimation(this);
    2062
    2063             return this;
    2064         },
    2065
    2066         /**
    2067          * Starts an animated point movement towards the given coordinates <tt>where</tt>. After arriving at
    2068          * <tt>where</tt> the point moves back to where it started. The animation is done after <tt>time</tt>
    2069          * milliseconds.
    2070          * @param {Array} where Array containing the x and y coordinate of the target location.
    2071          * @param {Number} time Number of milliseconds the animation should last.
    2072          * @param {Object} [options] Optional settings for the animation
    2073          * @param {function} [options.callback] A function that is called as soon as the animation is finished.
    2074          * @param {String} [options.effect='<>'|'>'|'<'] animation effects like speed fade in and out. possible values are
    2075          * '<>' for speed increase on start and slow down at the end (default), '<' for speed up, '>' for slow down, and '--' for constant speed during
    2076          * the whole animation.
    2077          * @param {Number} [options.repeat=1] How often this animation should be repeated.
    2078          * @returns {JXG.CoordsElement} Reference to itself.
    2079          * @see JXG.CoordsElement#moveAlong
    2080          * @see JXG.CoordsElement#moveTo
    2081          * @see JXG.CoordsElement#visitES6
    2082          * @see JXG.GeometryElement#animate
    2083          * @example
    2084          * // visit() with different easing options
    2085          * let yInit = 3
    2086          * let [A, B, C, D] = ['==', '<>', '<', '>'].map((s) => board.create('point', [4, yInit--], { name: s, label: { fontSize: 24 } }))
    2087          * let seg = board.create('segment', [A, [() => A.X(), 0]])  // shows linear
    2088          *
    2089          *let isLeftRight = true;
    2090          *let buttonVisit = board.create('button', [0, 4, 'visit',
    2091          *    () => {
    2092          *        let x = isLeftRight ? 4 : -4
    2093          *
    2094          *        A.visit([-x, 3], 4000, { effect: "==", repeat: 2 })  // linear
    2095          *        B.visit([-x, 2], 4000, { effect: "<>", repeat: 2 })
    2096          *        C.visit([-x, 1], 4000, { effect: "<", repeat: 2 })
    2097          *        D.visit([-x, 0], 4000, { effect: ">", repeat: 2 })
    2098          *    }])
    2099          *
    2100          * </pre><div id="JXG0f35a50e-e99d-11e8-a1ca-04d3b0c2aad5" class="jxgbox" style="width: 300px; height: 300px;"></div>
    2101          * <script type="text/javascript">
    2102          * {
    2103          *  let board = JXG.JSXGraph.initBoard('JXG0f35a50e-e99d-11e8-a1ca-04d3b0c2aad5')
    2104          * let yInit = 3
    2105          * let [A, B, C, D] = ['==', '<>', '<', '>'].map((s) => board.create('point', [4, yInit--], { name: s, label: { fontSize: 24 } }))
    2106          * let seg = board.create('segment', [A, [() => A.X(), 0]])  // shows linear
    2107          *
    2108          * let isLeftRight = true;
    2109          * let buttonVisit = board.create('button', [0, 4, 'visit',
    2110          *    () => {
    2111          *        let x = isLeftRight ? 4 : -4
    2112          *
    2113          *        A.visit([-x, 3], 4000, { effect: "==", repeat: 2 })  // linear
    2114          *        B.visit([-x, 2], 4000, { effect: "<>", repeat: 2 })
    2115          *        C.visit([-x, 1], 4000, { effect: "<", repeat: 2 })
    2116          *        D.visit([-x, 0], 4000, { effect: ">", repeat: 2 })
    2117          *    }])
    2118          *   }
    2119          * </script><pre>
    2120          *
    2121          */
    2122         visit: function (where, time, options) {
    2123             where = new Coords(Const.COORDS_BY_USER, where, this.board);
    2124
    2125             var i,
    2126                 j,
    2127                 steps,
    2128                 delay = this.board.attr.animationdelay,
    2129                 coords = [],
    2130                 X = this.coords.usrCoords[1],
    2131                 Y = this.coords.usrCoords[2],
    2132                 dX = where.usrCoords[1] - X,
    2133                 dY = where.usrCoords[2] - Y,
    2134                 /** @ignore */
    2135                 stepFun = function (i) {
    2136                     var x = i < steps / 2 ? (2 * i) / steps : (2 * (steps - i)) / steps;
    2137
    2138                     if (options.effect) {
    2139                         if (options.effect === "<>") {        // slow at beginning and end
    2140                             return Math.pow(Math.sin((x * Math.PI) / 2), 2);
    2141                         }
    2142                         if (options.effect === "<") {   // cubic ease in
    2143                             return x * x * x;
    2144                         }
    2145                         if (options.effect === ">") {   // cubic ease out
    2146                             return 1 - Math.pow(1 - x, 3);
    2147                         }
    2148                         if (options.effect === "==") {
    2149                             return x;       // linear
    2150                         }
    2151                         throw new Error("valid effects are '==', '<>', '>', and '<'.");
    2152
    2153                     }
    2154                     return x;
    2155                 };
    2156
    2157             // support legacy interface where the third parameter was the number of repeats
    2158             if (Type.isNumber(options)) {
    2159                 options = { repeat: options };
    2160             } else {
    2161                 options = options || {};
    2162                 if (!Type.exists(options.repeat)) {
    2163                     options.repeat = 1;
    2164                 }
    2165             }
    2166
    2167             steps = Math.ceil(time / (delay * options.repeat));
    2168
    2169             for (j = 0; j < options.repeat; j++) {
    2170                 for (i = steps; i >= 0; i--) {
    2171                     coords[j * (steps + 1) + steps - i] = [
    2172                         where.usrCoords[0],
    2173                         X + dX * stepFun(i),
    2174                         Y + dY * stepFun(i)
    2175                     ];
    2176                 }
    2177             }
    2178             this.animationPath = coords;
    2179             this.animationCallback = options.callback;
    2180             this.board.addAnimation(this);
    2181
    2182             return this;
    2183         },
    2184
    2185         /**
    2186          * ES6 version of {@link JXG.CoordsElement#moveAlong} using a promise.
    2187          *
    2188          * @param {Array} where Array containing the x and y coordinate of the target location.
    2189          * @param {Number} [time] Number of milliseconds the animation should last.
    2190          * @param {Object} [options] Optional settings for the animation
    2191          * @returns Promise
    2192          * @see JXG.CoordsElement#moveAlong
    2193          * @example
    2194          * var A = board.create('point', [4, 4]);
    2195          * A.moveAlongES6([[3, -2], [4, 0], [3, 1], [4, 4]], 2000)
    2196          *     .then(() => A.moveToES6([-3, -3], 1000));
    2197          *
    2198          * </pre><div id="JXGa45032e5-a517-4f1d-868a-65d698d344cf" class="jxgbox" style="width: 300px; height: 300px;"></div>
    2199          * <script type="text/javascript">
    2200          *     (function() {
    2201          *         var board = JXG.JSXGraph.initBoard('JXGa45032e5-a517-4f1d-868a-65d698d344cf',
    2202          *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    2203          *     var A = board.create('point', [4, 4]);
    2204          *     A.moveAlongES6([[3, -2], [4, 0], [3, 1], [4, 4]], 2000)
    2205          *         .then(() => A.moveToES6([-3, -3], 1000));
    2206          *
    2207          *     })();
    2208          *
    2209          * </script><pre>
    2210          *
    2211          */
    2212         moveAlongES6: function (path, time, options) {
    2213             return new Promise((resolve, reject) => {
    2214                 if (Type.exists(options) && Type.exists(options.callback)) {
    2215                     options.callback = resolve;
    2216                 } else {
    2217                     options = {
    2218                         callback: resolve
    2219                     };
    2220                 }
    2221                 this.moveAlong(path, time, options);
    2222             });
    2223         },
    2224
    2225         /**
    2226          * ES6 version of {@link JXG.CoordsElement#moveTo} using a promise.
    2227          *
    2228          * @param {Array} where Array containing the x and y coordinate of the target location.
    2229          * @param {Number} [time] Number of milliseconds the animation should last.
    2230          * @param {Object} [options] Optional settings for the animation
    2231          * @returns Promise
    2232          * @see JXG.CoordsElement#moveTo
    2233          *
    2234          * @example
    2235          * var A = board.create('point', [4, 4]);
    2236          * A.moveToES6([-3, 3], 1000)
    2237          *     .then(() => A.moveToES6([-3, -3], 1000))
    2238          *     .then(() => A.moveToES6([3, -3], 1000))
    2239          *     .then(() => A.moveToES6([3, -3], 1000));
    2240          *
    2241          * </pre><div id="JXGabdc7771-34f0-4655-bb7b-fc329e773b89" class="jxgbox" style="width: 300px; height: 300px;"></div>
    2242          * <script type="text/javascript">
    2243          *     (function() {
    2244          *         var board = JXG.JSXGraph.initBoard('JXGabdc7771-34f0-4655-bb7b-fc329e773b89',
    2245          *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    2246          *     var A = board.create('point', [4, 4]);
    2247          *     A.moveToES6([-3, 3], 1000)
    2248          *         .then(() => A.moveToES6([-3, -3], 1000))
    2249          *         .then(() => A.moveToES6([3, -3], 1000))
    2250          *         .then(() => A.moveToES6([3, -3], 1000));
    2251          *
    2252          *     })();
    2253          *
    2254          * </script><pre>
    2255          *
    2256          * @example
    2257          *         var A = board.create('point', [4, 4]);
    2258          *         A.moveToES6([-3, 3], 1000)
    2259          *             .then(function() {
    2260          *                 return A.moveToES6([-3, -3], 1000);
    2261          *             }).then(function() {
    2262          *                 return A.moveToES6([ 3, -3], 1000);
    2263          *             }).then(function() {
    2264          *                 return A.moveToES6([ 3, -3], 1000);
    2265          *             }).then(function() {
    2266          *                 return A.moveAlongES6([[3, -2], [4, 0], [3, 1], [4, 4]], 5000);
    2267          *             }).then(function() {
    2268          *                 return A.visitES6([-4, -4], 3000);
    2269          *             });
    2270          *
    2271          * </pre><div id="JXGa9439ce5-516d-4dba-9233-2a4ad9589995" class="jxgbox" style="width: 300px; height: 300px;"></div>
    2272          * <script type="text/javascript">
    2273          *     (function() {
    2274          *         var board = JXG.JSXGraph.initBoard('JXGa9439ce5-516d-4dba-9233-2a4ad9589995',
    2275          *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    2276          *             var A = board.create('point', [4, 4]);
    2277          *             A.moveToES6([-3, 3], 1000)
    2278          *                 .then(function() {
    2279          *                     return A.moveToES6([-3, -3], 1000);
    2280          *                 }).then(function() {
    2281          *                     return A.moveToES6([ 3, -3], 1000);
    2282          *                 }).then(function() {
    2283          *                     return A.moveToES6([ 3, -3], 1000);
    2284          *                 }).then(function() {
    2285          *                     return A.moveAlongES6([[3, -2], [4, 0], [3, 1], [4, 4]], 5000);
    2286          *                 }).then(function() {
    2287          *                     return A.visitES6([-4, -4], 3000);
    2288          *                 });
    2289          *
    2290          *     })();
    2291          *
    2292          * </script><pre>
    2293          *
    2294          */
    2295         moveToES6: function (where, time, options) {
    2296             return new Promise((resolve, reject) => {
    2297                 if (Type.exists(options) && Type.exists(options.callback)) {
    2298                     options.callback = resolve;
    2299                 } else {
    2300                     options = {
    2301                         callback: resolve
    2302                     };
    2303                 }
    2304                 this.moveTo(where, time, options);
    2305             });
    2306         },
    2307
    2308         /**
    2309          * ES6 version of {@link JXG.CoordsElement#moveVisit} using a promise.
    2310          *
    2311          * @param {Array} where Array containing the x and y coordinate of the target location.
    2312          * @param {Number} [time] Number of milliseconds the animation should last.
    2313          * @param {Object} [options] Optional settings for the animation
    2314          * @returns Promise
    2315          * @see JXG.CoordsElement#visit
    2316          * @example
    2317          * var A = board.create('point', [4, 4]);
    2318          * A.visitES6([-4, -4], 3000)
    2319          *     .then(() => A.moveToES6([-3, 3], 1000));
    2320          *
    2321          * </pre><div id="JXG640f1fd2-05ec-46cb-b977-36d96648ce41" class="jxgbox" style="width: 300px; height: 300px;"></div>
    2322          * <script type="text/javascript">
    2323          *     (function() {
    2324          *         var board = JXG.JSXGraph.initBoard('JXG640f1fd2-05ec-46cb-b977-36d96648ce41',
    2325          *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    2326          *     var A = board.create('point', [4, 4]);
    2327          *     A.visitES6([-4, -4], 3000)
    2328          *         .then(() => A.moveToES6([-3, 3], 1000));
    2329          *
    2330          *     })();
    2331          *
    2332          * </script><pre>
    2333          *
    2334          */
    2335         visitES6: function (where, time, options) {
    2336             return new Promise((resolve, reject) => {
    2337                 if (Type.exists(options) && Type.exists(options.callback)) {
    2338                     options.callback = resolve;
    2339                 } else {
    2340                     options = {
    2341                         callback: resolve
    2342                     };
    2343                 }
    2344                 this.visit(where, time, options);
    2345             });
    2346         },
    2347
    2348         /**
    2349          * Animates a glider. Is called by the browser after startAnimation is called.
    2350          * @param {Number} direction The direction the glider is animated.
    2351          * @param {Number} stepCount The number of steps in which the parent element is divided.
    2352          * Must be at least 1.
    2353          * @see JXG.CoordsElement#startAnimation
    2354          * @see JXG.CoordsElement#stopAnimation
    2355          * @private
    2356          * @returns {JXG.CoordsElement} Reference to itself.
    2357          */
    2358         _anim: function (direction, stepCount) {
    2359             var dX, dY, alpha, startPoint, newX, radius, sp1c, sp2c, res;
    2360
    2361             this.intervalCount += 1;
    2362             if (this.intervalCount > stepCount) {
    2363                 this.intervalCount = 0;
    2364             }
    2365
    2366             if (this.slideObject.elementClass === Const.OBJECT_CLASS_LINE) {
    2367                 sp1c = this.slideObject.point1.coords.scrCoords;
    2368                 sp2c = this.slideObject.point2.coords.scrCoords;
    2369
    2370                 dX = Math.round(((sp2c[1] - sp1c[1]) * this.intervalCount) / stepCount);
    2371                 dY = Math.round(((sp2c[2] - sp1c[2]) * this.intervalCount) / stepCount);
    2372                 if (direction > 0) {
    2373                     startPoint = this.slideObject.point1;
    2374                 } else {
    2375                     startPoint = this.slideObject.point2;
    2376                     dX *= -1;
    2377                     dY *= -1;
    2378                 }
    2379
    2380                 this.coords.setCoordinates(Const.COORDS_BY_SCREEN, [
    2381                     startPoint.coords.scrCoords[1] + dX,
    2382                     startPoint.coords.scrCoords[2] + dY
    2383                 ]);
    2384             } else if (this.slideObject.elementClass === Const.OBJECT_CLASS_CURVE) {
    2385                 if (direction > 0) {
    2386                     newX = (this.slideObject.maxX() - this.slideObject.minX()) * this.intervalCount / stepCount + this.slideObject.minX();
    2387                 } else {
    2388                     newX = -(this.slideObject.maxX() - this.slideObject.minX()) * this.intervalCount / stepCount + this.slideObject.maxX();
    2389                 }
    2390                 this.coords.setCoordinates(Const.COORDS_BY_USER, [this.slideObject.X(newX), this.slideObject.Y(newX)]);
    2391
    2392                 res = Geometry.projectPointToCurve(this, this.slideObject, this.board);
    2393                 this.coords = res[0];
    2394                 this.position = res[1];
    2395             } else if (this.slideObject.elementClass === Const.OBJECT_CLASS_CIRCLE) {
    2396                 alpha = 2 * Math.PI;
    2397                 if (direction < 0) {
    2398                     alpha *= this.intervalCount / stepCount;
    2399                 } else {
    2400                     alpha *= (stepCount - this.intervalCount) / stepCount;
    2401                 }
    2402                 radius = this.slideObject.Radius();
    2403
    2404                 this.coords.setCoordinates(Const.COORDS_BY_USER, [
    2405                     this.slideObject.center.coords.usrCoords[1] + radius * Math.cos(alpha),
    2406                     this.slideObject.center.coords.usrCoords[2] + radius * Math.sin(alpha)
    2407                 ]);
    2408             }
    2409
    2410             this.board.update(this);
    2411             return this;
    2412         },
    2413
    2414         // documented in GeometryElement
    2415         getTextAnchor: function () {
    2416             return this.coords;
    2417         },
    2418
    2419         // documented in GeometryElement
    2420         getLabelAnchor: function () {
    2421             return this.coords;
    2422         },
    2423
    2424         // documented in element.js
    2425         getParents: function () {
    2426             var p = [this.Z(), this.X(), this.Y()];
    2427
    2428             if (this.parents.length !== 0) {
    2429                 p = this.parents;
    2430             }
    2431
    2432             if (this.type === Const.OBJECT_TYPE_GLIDER) {
    2433                 p = [this.X(), this.Y(), this.slideObject.id];
    2434             }
    2435
    2436             return p;
    2437         }
    2438     }
    2439 );
    2440
    2441 /**
    2442  * Generic method to create point, text or image.
    2443  * Determines the type of the construction, i.e. free, or constrained by function,
    2444  * transformation or of glider type.
    2445  * @param{Object} Callback Object type, e.g. JXG.Point, JXG.Text or JXG.Image
    2446  * @param{Object} board Link to the board object
    2447  * @param{Array} coords Array with coordinates. This may be: array of numbers, function
    2448  * returning an array of numbers, array of functions returning a number, object and transformation.
    2449  * If the attribute "slideObject" exists, a glider element is constructed.
    2450  * @param{Object} attr Attributes object
    2451  * @param{Object} arg1 Optional argument 1: in case of text this is the text content,
    2452  * in case of an image this is the url.
    2453  * @param{Array} arg2 Optional argument 2: in case of image this is an array containing the size of
    2454  * the image.
    2455  * @returns{Object} returns the created object or false.
    2456  */
    2457 JXG.CoordsElement.create = function (Callback, board, coords, attr, arg1, arg2) {
    2458     var el,
    2459         isConstrained = false,
    2460         i;
    2461
    2462     for (i = 0; i < coords.length; i++) {
    2463         if (Type.isFunction(coords[i]) || Type.isString(coords[i])) {
    2464             isConstrained = true;
    2465         }
    2466     }
    2467
    2468     if (!isConstrained) {
    2469         if (Type.isNumber(coords[0]) && Type.isNumber(coords[1])) {
    2470             el = new Callback(board, coords, attr, arg1, arg2);
    2471
    2472             if (Type.exists(attr.slideobject)) {
    2473                 el.makeGlider(attr.slideobject);
    2474             } else {
    2475                 // Free element
    2476                 el.baseElement = el;
    2477             }
    2478             el.isDraggable = true;
    2479         } else if (Type.isObject(coords[0]) && Type.isTransformationOrArray(coords[1])) {
    2480             // Transformation
    2481             // TODO less general specification of isObject
    2482             el = new Callback(board, [0, 0], attr, arg1, arg2);
    2483             el.addTransform(coords[0], coords[1]);
    2484             el.isDraggable = false;
    2485         } else {
    2486             return false;
    2487         }
    2488     } else {
    2489         el = new Callback(board, [0, 0], attr, arg1, arg2);
    2490         el.addConstraint(coords);
    2491     }
    2492
    2493     el.handleSnapToGrid();
    2494     el.handleSnapToPoints();
    2495     el.handleAttractors();
    2496
    2497     el.addParents(coords);
    2498     return el;
    2499 };
    2500
    2501 export default JXG.CoordsElement;
    2502