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