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