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