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