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