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