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