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