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