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