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