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