      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