      1 /*
      2     Copyright 2008-2011
      3         Matthias Ehmann,
      4         Michael Gerhaeuser,
      5         Carsten Miller,
      6         Bianca Valentin,
      7         Alfred Wassermann,
      8         Peter Wilfahrt
      9
     10     This file is part of JSXGraph.
     11
     12     JSXGraph is free software: you can redistribute it and/or modify
     13     it under the terms of the GNU Lesser General Public License as published by
     14     the Free Software Foundation, either version 3 of the License, or
     15     (at your option) any later version.
     16
     17     JSXGraph is distributed in the hope that it will be useful,
     18     but WITHOUT ANY WARRANTY; without even the implied warranty of
     19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     20     GNU Lesser General Public License for more details.
     21
     22     You should have received a copy of the GNU Lesser General Public License
     23     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
     24 */
     25
     26 /**
     27  * @fileoverview The geometry object Point is defined in this file. Point stores all
     28  * style and functional properties that are required to draw and move a point on
     29  * a board.
     30  */
     31
     32
     33 /**
     34  * A point is the basic geometric element. Based on points lines and circles can be constructed which can be intersected
     35  * which in turn are points again which can be used to construct new lines, circles, polygons, etc. This class holds methods for
     36  * all kind of points like free points, gliders, and intersection points.
     37  * @class Creates a new point object. Do not use this constructor to create a point. Use {@link JXG.Board#create} with
     38  * type {@link Point}, {@link Glider}, or {@link Intersection} instead.
     39  * @augments JXG.GeometryElement
     40  * @param {string|JXG.Board} board The board the new point is drawn on.
     41  * @param {Array} coordinates An array with the affine user coordinates of the point.
     42  * @param {Object} attributes An object containing visual properties like in {@link JXG.Options#point} and
     43  * {@link JXG.Options#elements}, and optional a name and a id.
     44  * @see JXG.Board#generateName
     45  * @see JXG.Board#addPoint
     46  */
     47 JXG.Point = function (board, coordinates, attributes) {
     48     this.constructor(board, attributes, JXG.OBJECT_TYPE_POINT, JXG.OBJECT_CLASS_POINT);
     49
     50     if (coordinates==null) {
     51         coordinates=[0,0];
     52     }
     53     /**
     54      * Coordinates of the point.
     55      * @type JXG.Coords
     56      * @private
     57      */
     58     this.coords = new JXG.Coords(JXG.COORDS_BY_USER, coordinates, this.board);
     59     this.initialCoords = new JXG.Coords(JXG.COORDS_BY_USER, coordinates, this.board);
     60
     61     /**
     62      * Relative position on a line if point is a glider on a line.
     63      * @type Number
     64      * @private
     65      */
     66     this.position = null;
     67
     68     /**
     69      * Determines whether the point slides on a polygon if point is a glider.
     70      * @type boolean
     71      * @default false
     72      * @private
     73      */
     74     this.onPolygon = false;
     75
     76     /**
     77      * When used as a glider this member stores the object, where to glide on. To set the object to glide on use the method
     78      * {@link JXG.Point#makeGlider} and DO NOT set this property directly as it will break the dependency tree.
     79      * TODO: Requires renaming to glideObject
     80      * @type JXG.GeometryElement
     81      * @name Glider#slideObject
     82      */
     83     this.slideObject = null;
     84
     85     this.Xjc = null;
     86     this.Yjc = null;
     87
     88     // documented in GeometryElement
     89     this.methodMap = JXG.deepCopy(this.methodMap, {
     90         move: 'moveTo',
     91         glide: 'makeGlider',
     92         X: 'X',
     93         Y: 'Y',
     94         free: 'free',
     95         setPosition: 'setGliderPosition'
     96     });
     97
     98     /**
     99      * Stores the groups of this point in an array of Group.
    100      * @type array
    101      * @see JXG.Group
    102      * @private
    103      */
    104     this.group = [];
    105
    106     this.elType = 'point';
    107
    108     /* Register point at board. */
    109     this.id = this.board.setId(this, 'P');
    110     this.board.renderer.drawPoint(this);
    111     this.board.finalizeAdding(this);
    112
    113     this.createLabel();
    114 };
    115
    116 /**
    117  * Inherits here from {@link JXG.GeometryElement}.
    118  */
    119 JXG.Point.prototype = new JXG.GeometryElement();
    120
    121
    122 JXG.extend(JXG.Point.prototype, /** @lends JXG.Point.prototype */ {
    123     /**
    124      * Checks whether (x,y) is near the point.
    125      * @param {int} x Coordinate in x direction, screen coordinates.
    126      * @param {int} y Coordinate in y direction, screen coordinates.
    127      * @type boolean
    128      * @return True if (x,y) is near the point, False otherwise.
    129      * @private
    130      */
    131     hasPoint: function (x,y) {
    132         var coordsScr = this.coords.scrCoords, r;
    133         r = parseFloat(this.visProp.size);
    134         if(r < this.board.options.precision.hasPoint) {
    135             r = this.board.options.precision.hasPoint;
    136         }
    137
    138         return ((Math.abs(coordsScr[1]-x) < r+2) && (Math.abs(coordsScr[2]-y)) < r+2);
    139     },
    140
    141     /**
    142     * Dummy function for unconstrained points or gliders.
    143     * @private
    144     */
    145     updateConstraint: function() { return this; },
    146
    147     /**
    148      * Updates the position of the point.
    149      */
    150     update: function (fromParent) {
    151         if (!this.needsUpdate) { return this; }
    152
    153         if(typeof fromParent == 'undefined') {
    154             fromParent = false;
    155         }
    156
    157         /*
    158          * We need to calculate the new coordinates no matter of the points visibility because
    159          * a child could be visible and depend on the coordinates of the point (e.g. perpendicular).
    160          *
    161          * Check if point is a glider and calculate new coords in dependency of this.slideObject.
    162          * This function is called with fromParent==true for example if
    163          * the defining elements of the line or circle have been changed.
    164          */
    165         if(this.type == JXG.OBJECT_TYPE_GLIDER) {
    166             if (fromParent) {
    167                 this.updateGliderFromParent();
    168             } else {
    169                 this.updateGlider();
    170             }
    171         }
    172
    173         /**
    174         * If point is a calculated point, call updateConstraint() to calculate new coords.
    175         * The second test is for dynamic axes.
    176         */
    177         if (this.type == JXG.OBJECT_TYPE_CAS || this.type == JXG.OBJECT_TYPE_AXISPOINT) {
    178             this.updateConstraint();
    179         }
    180
    181         this.updateTransform();
    182
    183         if(this.visProp.trace) {
    184             this.cloneToBackground(true);
    185         }
    186
    187         return this;
    188     },
    189
    190     /**
    191     * Update of glider in case of dragging the glider or setting the postion of the glider.
    192     * The relative position of the glider has to be updated.
    193     * @private
    194     */
    195     updateGlider: function() {
    196         var i, p1c, p2c, d, v, poly, cc, pos;
    197
    198         if(this.slideObject.elementClass == JXG.OBJECT_CLASS_CIRCLE) {
    199             this.coords  = JXG.Math.Geometry.projectPointToCircle(this, this.slideObject, this.board);
    200             this.position = JXG.Math.Geometry.rad([this.slideObject.center.X()+1.0,this.slideObject.center.Y()],this.slideObject.center,this);
    201         } else if(this.slideObject.elementClass == JXG.OBJECT_CLASS_LINE) {
    202
    203             /**
    204              * onPolygon==true: the point is a slider on a seg,ent and this segment is one of the
    205              * "borders" of a polygon.
    206              * This is a GEONExT feature.
    207              **/
    208             if (this.onPolygon) {
    209                 p1c = this.slideObject.point1.coords.usrCoords;
    210                 p2c = this.slideObject.point2.coords.usrCoords;
    211                 i = 1;
    212                 d = p2c[i] - p1c[i];
    213                 if (Math.abs(d)<JXG.Math.eps) {
    214                     i = 2;
    215                     d = p2c[i] - p1c[i];
    216                 }
    217                 cc = JXG.Math.Geometry.projectPointToLine(this, this.slideObject, this.board);
    218                 pos = (cc.usrCoords[i] - p1c[i]) / d;
    219                 poly = this.slideObject.parentPolygon;
    220
    221                 if (pos<0.0) {
    222                     for (i=0; i<poly.borders.length; i++) {
    223                         if (this.slideObject == poly.borders[i]) {
    224                             this.slideObject = poly.borders[(i - 1 + poly.borders.length) % poly.borders.length];
    225                             break;
    226                         }
    227                     }
    228                 } else if (pos>1.0) {
    229                     for (i=0; i<poly.borders.length; i++) {
    230                         if(this.slideObject == poly.borders[i]) {
    231                             this.slideObject = poly.borders[(i + 1 + poly.borders.length) % poly.borders.length];
    232                             break;
    233                         }
    234                     }
    235                 }
    236             }
    237
    238             p1c = this.slideObject.point1.coords;
    239             p2c = this.slideObject.point2.coords;
    240             // Distance between the two defining points
    241             d = p1c.distance(JXG.COORDS_BY_USER, p2c);
    242             p1c = p1c.usrCoords.slice(0);
    243             p2c = p2c.usrCoords.slice(0);
    244
    245             if (d<JXG.Math.eps) {                                        // The defining points are identical
    246                 this.coords.setCoordinates(JXG.COORDS_BY_USER, p1c);
    247                 this.position = 0.0;
    248             } else {
    249                 if (d==Number.POSITIVE_INFINITY) {                       // At least one point is an ideal point
    250 					d = 1.0/JXG.Math.eps;
    251                     if (Math.abs(p2c[0])<JXG.Math.eps) {                 // The second point is an ideal point
    252                         d /= JXG.Math.Geometry.distance([0,0,0], p2c);
    253                         p2c = [1, p1c[1]+p2c[1]*d, p1c[2]+p2c[2]*d];
    254                     } else {                                             // The first point is an ideal point
    255                         d /= JXG.Math.Geometry.distance([0,0,0], p1c);
    256                         p1c = [1, p2c[1]+p1c[1]*d, p2c[2]+p1c[2]*d];
    257                     }
    258                 }
    259                 i = 1;
    260                 d = p2c[i] - p1c[i];
    261                 if (Math.abs(d)<JXG.Math.eps) {
    262                     i = 2;
    263                     d = p2c[i] - p1c[i];
    264                 }
    265
    266                 this.coords = JXG.Math.Geometry.projectPointToLine(this, this.slideObject, this.board);
    267                 this.position = (this.coords.usrCoords[i] - p1c[i]) / d;
    268             }
    269
    270             // Snap the glider point of the slider into its appropiate position
    271             // First, recalculate the new value of this.position
    272             // Second, call update(fromParent==true) to make the positioning snappier.
    273             if (this.visProp.snapwidth>0.0 && Math.abs(this._smax-this._smin)>=JXG.Math.eps) {
    274                 if (this.position<0.0) this.position = 0.0;
    275                 if (this.position>1.0) this.position = 1.0;
    276
    277                 v = this.position*(this._smax-this._smin)+this._smin;
    278                 v = Math.round(v/this.visProp.snapwidth)*this.visProp.snapwidth;
    279                 this.position = (v-this._smin)/(this._smax-this._smin);
    280                 this.update(true);
    281             }
    282
    283             p1c = this.slideObject.point1.coords.usrCoords;
    284             if (!this.slideObject.visProp.straightfirst && Math.abs(p1c[0])>JXG.Math.eps && this.position<0) {
    285                 this.coords.setCoordinates(JXG.COORDS_BY_USER, p1c);
    286                 this.position = 0;
    287             }
    288             p2c = this.slideObject.point2.coords.usrCoords;
    289             if (!this.slideObject.visProp.straightlast && Math.abs(p2c[0])>JXG.Math.eps && this.position>1) {
    290                 this.coords.setCoordinates(JXG.COORDS_BY_USER, p2c);
    291                 this.position = 1;
    292             }
    293
    294
    295         } else if(this.slideObject.type == JXG.OBJECT_TYPE_TURTLE) {
    296             this.updateConstraint(); // In case, the point is a constrained glider.
    297             this.coords  = JXG.Math.Geometry.projectPointToTurtle(this, this.slideObject, this.board);  // side-effect: this.position is overwritten
    298         } else if(this.slideObject.elementClass == JXG.OBJECT_CLASS_CURVE) {
    299             this.updateConstraint(); // In case, the point is a constrained glider.
    300             this.coords  = JXG.Math.Geometry.projectPointToCurve(this, this.slideObject, this.board);  // side-effect: this.position is overwritten
    301         } else if(this.slideObject.elementClass == JXG.OBJECT_CLASS_POINT) {
    302             this.coords  = JXG.Math.Geometry.projectPointToPoint(this, this.slideObject, this.board);
    303         }
    304     },
    305
    306     /**
    307     * Update of a glider in case a parent element has been updated. That means the
    308     * relative position of the glider stays the same.
    309     * @private
    310     */
    311     updateGliderFromParent: function() {
    312         var p1c, p2c, r, d;
    313
    314         if(this.slideObject.elementClass == JXG.OBJECT_CLASS_CIRCLE) {
    315 			r = this.slideObject.Radius();
    316             this.coords.setCoordinates(JXG.COORDS_BY_USER, [
    317 					this.slideObject.center.X() + r*Math.cos(this.position),
    318 					this.slideObject.center.Y() + r*Math.sin(this.position)
    319 				]);
    320         } else if(this.slideObject.elementClass == JXG.OBJECT_CLASS_LINE) {
    321             p1c = this.slideObject.point1.coords;
    322             p2c = this.slideObject.point2.coords;
    323             d = p1c.distance(JXG.COORDS_BY_USER, p2c);
    324             p1c = p1c.usrCoords.slice(0);
    325             p2c = p2c.usrCoords.slice(0);
    326
    327             if (d==Number.POSITIVE_INFINITY) {                       // At least one point is an ideal point
    328 				d = 1.0/JXG.Math.eps;
    329                 if (Math.abs(p2c[0])<JXG.Math.eps) {                 // The second point is an ideal point
    330                     d /= JXG.Math.Geometry.distance([0,0,0], p2c);
    331                     p2c = [1, p1c[1]+p2c[1]*d, p1c[2]+p2c[2]*d];
    332                 } else {                                             // The first point is an ideal point
    333                     d /= JXG.Math.Geometry.distance([0,0,0], p1c);
    334                     p1c = [1, p2c[1]+p1c[1]*d, p2c[2]+p1c[2]*d];
    335                 }
    336             }
    337             this.coords.setCoordinates(JXG.COORDS_BY_USER, [
    338                     p1c[1] + this.position*(p2c[1] - p1c[1]),
    339 					p1c[2] + this.position*(p2c[2] - p1c[2])
    340                 ]);
    341         } else if(this.slideObject.type == JXG.OBJECT_TYPE_TURTLE) {
    342             this.coords.setCoordinates(JXG.COORDS_BY_USER, [this.slideObject.Z(this.position), this.slideObject.X(this.position), this.slideObject.Y(this.position)]);
    343             this.updateConstraint(); // In case, the point is a constrained glider.
    344             this.coords  = JXG.Math.Geometry.projectPointToTurtle(this, this.slideObject, this.board);  // side-effect: this.position is overwritten
    345         } else if(this.slideObject.elementClass == JXG.OBJECT_CLASS_CURVE) {
    346             this.coords.setCoordinates(JXG.COORDS_BY_USER, [this.slideObject.Z(this.position), this.slideObject.X(this.position), this.slideObject.Y(this.position)]);
    347             this.updateConstraint(); // In case, the point is a constrained glider.
    348             this.coords  = JXG.Math.Geometry.projectPointToCurve(this, this.slideObject, this.board);  // side-effect: this.position is overwritten
    349         } else if(this.slideObject.elementClass == JXG.OBJECT_CLASS_POINT) {
    350             this.coords  = JXG.Math.Geometry.projectPointToPoint(this, this.slideObject, this.board);
    351         }
    352     },
    353
    354     /**
    355      * Calls the renderer to update the drawing.
    356      * @private
    357      */
    358     updateRenderer: function () {
    359         if (!this.needsUpdate) { return this; }
    360
    361         /* Call the renderer only if point is visible. */
    362         if(this.visProp.visible) {
    363             var wasReal = this.isReal;
    364             this.isReal = (!isNaN(this.coords.usrCoords[1] + this.coords.usrCoords[2]));
    365             this.isReal = (Math.abs(this.coords.usrCoords[0])>JXG.Math.eps)?this.isReal:false;  //Homogeneous coords: ideal point
    366             if (this.isReal) {
    367                 if (wasReal!=this.isReal) {
    368                     this.board.renderer.show(this);
    369                     if(this.hasLabel && this.label.content.visProp.visible) this.board.renderer.show(this.label.content);
    370                 }
    371                 this.board.renderer.updatePoint(this);
    372             } else {
    373                 if (wasReal!=this.isReal) {
    374                     this.board.renderer.hide(this);
    375                     if(this.hasLabel && this.label.content.visProp.visible) this.board.renderer.hide(this.label.content);
    376                 }
    377             }
    378         }
    379
    380         /* Update the label if visible. */
    381         if(this.hasLabel && this.visProp.visible && this.label.content && this.label.content.visProp.visible && this.isReal) {
    382             this.label.content.update();
    383             this.board.renderer.updateText(this.label.content);
    384         }
    385
    386         this.needsUpdate = false;
    387         return this;
    388     },
    389
    390     /**
    391      * Getter method for x, this is used by for CAS-points to access point coordinates.
    392      * @return User coordinate of point in x direction.
    393      * @type Number
    394      */
    395     X: function () {
    396         return this.coords.usrCoords[1];
    397     },
    398
    399     /**
    400      * Getter method for y, this is used by CAS-points to access point coordinates.
    401      * @return User coordinate of point in y direction.
    402      * @type Number
    403      */
    404     Y: function () {
    405         return this.coords.usrCoords[2];
    406     },
    407
    408     /**
    409      * Getter method for z, this is used by CAS-points to access point coordinates.
    410      * @return User coordinate of point in z direction.
    411      * @type Number
    412      */
    413     Z: function () {
    414         return this.coords.usrCoords[0];
    415     },
    416
    417     /**
    418      * New evaluation of the function term.
    419      * This is required for CAS-points: Their XTerm() method is overwritten in {@link #addConstraint}
    420      * @return User coordinate of point in x direction.
    421      * @type Number
    422      * @private
    423      */
    424     XEval: function () {
    425         return this.coords.usrCoords[1];
    426     },
    427
    428     /**
    429      * New evaluation of the function term.
    430      * This is required for CAS-points: Their YTerm() method is overwritten in {@link #addConstraint}
    431      * @return User coordinate of point in y direction.
    432      * @type Number
    433      * @private
    434      */
    435     YEval: function () {
    436         return this.coords.usrCoords[2];
    437     },
    438
    439     /**
    440      * New evaluation of the function term.
    441      * This is required for CAS-points: Their ZTerm() method is overwritten in {@link #addConstraint}
    442      * @return User coordinate of point in z direction.
    443      * @type Number
    444      * @private
    445      */
    446     ZEval: function () {
    447         return this.coords.usrCoords[0];
    448     },
    449
    450     // documented in JXG.GeometryElement
    451     bounds: function () {
    452         return this.coords.usrCoords.slice(1).concat(this.coords.usrCoords.slice(1));
    453     },
    454
    455     /**
    456      * Getter method for the distance to a second point, this is required for CAS-elements.
    457      * Here, function inlining seems to be worthwile  (for plotting).
    458      * @param {JXG.Point} point2 The point to which the distance shall be calculated.
    459      * @return Distance in user coordinate to the given point
    460      * @type Number
    461      */
    462     Dist: function(point2) {
    463         var sum,
    464             c = point2.coords.usrCoords,
    465             ucr = this.coords.usrCoords,
    466             f;
    467
    468         f = ucr[0]-c[0];
    469         sum = f*f;
    470         f = ucr[1]-c[1];
    471         sum += f*f;
    472         f = ucr[2]-c[2];
    473         sum += f*f;
    474         return Math.sqrt(sum);
    475     },
    476
    477     /**
    478      * Move a point to its nearest grid point.
    479      * The function uses the coords object of the point as
    480      * its actual position.
    481      **/
    482     handleSnapToGrid: function() {
    483         var x, y, sX = this.visProp.snapsizex, sY = this.visProp.snapsizey;
    484
    485         if (this.visProp.snaptogrid) {
    486             x = this.coords.usrCoords[1];
    487             y = this.coords.usrCoords[2];
    488
    489             if (sX <= 0 && this.board.defaultAxes && this.board.defaultAxes.x.defaultTicks) {
    490                 sX = this.board.defaultAxes.x.defaultTicks.ticksDelta*(this.board.defaultAxes.x.defaultTicks.visProp.minorticks+1);
    491             }
    492
    493             if (sY <= 0 && this.board.defaultAxes && this.board.defaultAxes.y.defaultTicks) {
    494                 sY = this.board.defaultAxes.y.defaultTicks.ticksDelta*(this.board.defaultAxes.y.defaultTicks.visProp.minorticks+1);
    495             }
    496
    497             // if no valid snap sizes are available, don't change the coords.
    498             if (sX > 0 && sY > 0) {
    499                 this.coords = new JXG.Coords(JXG.COORDS_BY_USER, [Math.round(x/sX)*sX, Math.round(y/sY)*sY], this.board);
    500             }
    501         }
    502         return this;
    503     },
    504
    505     /**
    506      * Let a point snap to the nearest point in distance of
    507      * {@link JXG.Point#attractorDistance}.
    508      * The function uses the coords object of the point as
    509      * its actual position.
    510      **/
    511     handleSnapToPoints: function() {
    512         var el, pEl, pCoords, d=0.0, dMax=Infinity, c=null;
    513
    514         if (this.visProp.snaptopoints) {
    515             for (el in this.board.objects) {
    516                 pEl = this.board.objects[el];
    517                 if (pEl.elementClass==JXG.OBJECT_CLASS_POINT && pEl!==this && pEl.visProp.visible) {
    518                     pCoords = JXG.Math.Geometry.projectPointToPoint(this, pEl, this.board);
    519                     d = pCoords.distance(JXG.COORDS_BY_USER, this.coords);
    520                     if (d<this.visProp.attractordistance && d<dMax) {
    521                         dMax = d;
    522                         c = pCoords;
    523                     }
    524                 }
    525             }
    526             if (c!=null) {
    527                 this.coords.setCoordinates(JXG.COORDS_BY_USER, c.usrCoords);
    528             }
    529         }
    530
    531         return this;
    532     },
    533
    534     /**
    535      * A point can change its type from free point to glider
    536      * and vice versa. If it is given an array of attractor elements
    537      * (attribute attractors) and the attribute attractorDistance
    538      * then the pint will be made a glider if it less than attractorDistance
    539      * apart from one of its attractor elements.
    540      * If attractorDistance is equal to zero, the point stays in its
    541      * current form.
    542      **/
    543     handleAttractors: function() {
    544         var len = this.visProp.attractors.length,
    545             found, i, el, projCoords, d = 0.0;
    546
    547         if (this.visProp.attractordistance==0.0) {
    548             return;
    549         }
    550         found = false;
    551         for (i=0; i<len; i++) {
    552             el = JXG.getRef(this.board, this.visProp.attractors[i]);
    553             if (!JXG.exists(el) || el===this) {
    554                 continue;
    555             }
    556             if (el.elementClass==JXG.OBJECT_CLASS_POINT) {
    557                 projCoords = JXG.Math.Geometry.projectPointToPoint(this, el, this.board);
    558             } else if (el.elementClass==JXG.OBJECT_CLASS_LINE) {
    559                 projCoords = JXG.Math.Geometry.projectPointToLine(this, el, this.board);
    560             } else if (el.elementClass==JXG.OBJECT_CLASS_CIRCLE) {
    561                 projCoords = JXG.Math.Geometry.projectPointToCircle(this, el, this.board);
    562             } else if (el.elementClass==JXG.OBJECT_CLASS_CURVE) {
    563                 projCoords = JXG.Math.Geometry.projectPointToCurve(this, el, this.board);
    564             } else if (el.type == JXG.OBJECT_TYPE_TURTLE) {
    565                 projCoords = JXG.Math.Geometry.projectPointToTurtle(this, el, this.board);
    566             }
    567             d = projCoords.distance(JXG.COORDS_BY_USER, this.coords);
    568             if (d<this.visProp.attractordistance) {
    569                 found = true;
    570                 if (!(this.type==JXG.OBJECT_TYPE_GLIDER && this.slideObject==el)) {
    571                     this.makeGlider(el);
    572                 }
    573                 break;
    574             } else {
    575                 if (el==this.slideObject && d>=this.visProp.snatchdistance) {
    576                     this.type = JXG.OBJECT_TYPE_POINT;
    577                 }
    578             }
    579         }
    580
    581         /*
    582         if (found==false) {
    583             this.type = JXG.OBJECT_TYPE_POINT;
    584         }
    585         */
    586         return this;
    587     },
    588
    589     /**
    590      * Sets x and y coordinate and calls the point's update() method.
    591      * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
    592      * @param {Number} x x coordinate in screen/user units
    593      * @param {Number} y y coordinate in screen/user units
    594      * @param {Number} x optional: previous x coordinate in screen/user units (ignored)
    595      * @param {Number} y optional: previous y coordinate in screen/user units (ignored)
    596      */
    597     setPositionDirectly: function (method, x, y) {
    598         var i, dx, dy, el, p,
    599             oldCoords = this.coords,
    600             newCoords;
    601
    602         this.coords = new JXG.Coords(method, [x, y], this.board);
    603
    604         this.handleSnapToGrid();
    605         this.handleSnapToPoints();
    606         this.handleAttractors();
    607
    608         if(this.group.length != 0) {
    609             // Update the initial coordinates. This is needed for free points
    610             // that have a transformation bound to it.
    611             dx = this.coords.usrCoords[1]-oldCoords.usrCoords[1];
    612             dy = this.coords.usrCoords[2]-oldCoords.usrCoords[2];
    613             for (i=0;i<this.group.length;i++) {
    614                 for (el in this.group[i].objects) {
    615                     p = this.group[i].objects[el];
    616                     p.initialCoords = new JXG.Coords(JXG.COORDS_BY_USER,
    617                         [p.initialCoords.usrCoords[1]+dx,p.initialCoords.usrCoords[2]+dy],
    618                         this.board);
    619                 }
    620             }
    621
    622             this.group[this.group.length-1].dX = this.coords.scrCoords[1] - oldCoords.scrCoords[1];
    623             this.group[this.group.length-1].dY = this.coords.scrCoords[2] - oldCoords.scrCoords[2];
    624             this.group[this.group.length-1].update(this);
    625         } else {
    626             // Update the initial coordinates. This is needed for free points
    627             // that have a transformation bound to it.
    628             for (i=this.transformations.length-1;i>=0;i--) {
    629                 if (method == JXG.COORDS_BY_SCREEN) {
    630                     newCoords = (new JXG.Coords(method, [x, y], this.board)).usrCoords;
    631                 } else {
    632                     newCoords = [1,x,y];
    633                 }
    634                 this.initialCoords = new JXG.Coords(JXG.COORDS_BY_USER,
    635                         JXG.Math.matVecMult(JXG.Math.inverse(this.transformations[i].matrix), newCoords),
    636                         this.board);
    637             }
    638             this.update();
    639         }
    640
    641         return this;
    642     },
    643
    644     /**
    645      * TODO
    646      * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
    647      * @param {Number} x x coordinate in screen/user units
    648      * @param {Number} y y coordinate in screen/user units
    649      */
    650     setPositionByTransform: function (method, x, y) {
    651         var t = this.board.create('transform', [x,y], {type:'translate'});
    652
    653         if (this.transformations.length > 0 && this.transformations[this.transformations.length - 1].isNumericMatrix) {
    654             this.transformations[this.transformations.length - 1].melt(t);
    655         } else {
    656             this.addTransform(this, t);
    657         }
    658
    659         if (this.group.length == 0) {
    660             this.update();
    661         }
    662         return this;
    663     },
    664
    665     /**
    666      * Sets x and y coordinate and calls the point's update() method.
    667      * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
    668      * @param {Number} x x coordinate in screen/user units
    669      * @param {Number} y y coordinate in screen/user units
    670      */
    671     setPosition: function (method, x, y) {
    672         this.setPositionDirectly(method, x, y);
    673         return this;
    674     },
    675
    676     /**
    677      * Sets the position of a glider relative to the defining elements of the {@link JXG.Point#slideObject}.
    678      * @param {Number} x
    679      * @returns {JXG.Point} Reference to the point element.
    680      */
    681     setGliderPosition: function (x) {
    682         if (this.type = JXG.OBJECT_TYPE_GLIDER) {
    683             this.position = x;
    684             this.board.update();
    685         }
    686
    687         return this;
    688     },
    689
    690     /**
    691      * Convert the point to glider and update the construction.
    692      * @param {String|Object} glideObject The Object the point will be bound to.
    693      */
    694     makeGlider: function (glideObject) {
    695         //var c = this.coords.usrCoords.slice(1);
    696         this.slideObject = JXG.getRef(this.board, glideObject);
    697         this.type = JXG.OBJECT_TYPE_GLIDER;
    698         this.elType = 'glider';
    699         this.visProp.snapwidth = -1;          // By default, deactivate snapWidth
    700         this.slideObject.addChild(this);
    701         this.isDraggable = true;
    702
    703         this.generatePolynomial = function() {
    704             return this.slideObject.generatePolynomial(this);
    705         };
    706
    707         this.updateGlider(); // Determine the initial value of this.position
    708         //this.moveTo(c);
    709         //this.prepareUpdate().update().updateRenderer();
    710         return this;
    711     },
    712
    713     /**
    714      * Converts a glider into a free point.
    715      */
    716     free: function () {
    717         var anc;
    718
    719         if (this.type !== JXG.OBJECT_TYPE_GLIDER) {
    720             if (!this.isDraggable) {
    721                 this.isDraggable = true;
    722                 this.type = JXG.OBJECT_TYPE_POINT;
    723
    724                 this.XEval = function () {
    725                     return this.coords.usrCoords[1];
    726                 };
    727
    728                 this.YEval = function () {
    729                     return this.coords.usrCoords[2];
    730                 };
    731
    732                 this.ZEval = function () {
    733                     return this.coords.usrCoords[0];
    734                 };
    735
    736                 this.Xjc = null;
    737                 this.Yjc = null;
    738             } else {
    739                 return;
    740             }
    741         }
    742
    743         for (anc in this.ancestors) {
    744             delete this.ancestors[anc].descendants[this.id];
    745             delete this.ancestors[anc].childElements[this.id];
    746         }
    747
    748         this.ancestors = [];
    749         this.slideObject = null;
    750         this.elType = 'point';
    751         this.type = JXG.OBJECT_TYPE_POINT;
    752     },
    753
    754     /**
    755      * Convert the point to CAS point and call update().
    756      * @param {Array} terms [[zterm], xterm, yterm] defining terms for the z, x and y coordinate.
    757      * The z-coordinate is optional and it is used for homogeneaous coordinates.
    758      * The coordinates may be either <ul>
    759      *   <li>a JavaScript function,</li>
    760      *   <li>a string containing GEONExT syntax. This string will be converted into a JavaScript
    761      *     function here,</li>
    762      *   <li>a Number</li>
    763      *   <li>a pointer to a slider object. This will be converted into a call of the Value()-method
    764      *     of this slider.</li>
    765      *   </ul>
    766      * @see JXG.GeonextParser#geonext2JS
    767      */
    768     addConstraint: function (terms) {
    769         this.type = JXG.OBJECT_TYPE_CAS;
    770         var newfuncs = [],
    771             fs, i, v, t,
    772             what = ['X', 'Y'];
    773
    774         this.isDraggable = false;
    775         for (i=0;i<terms.length;i++) {
    776             v = terms[i];
    777             if (typeof v=='string') {
    778                 // Convert GEONExT syntax into  JavaScript syntax
    779                 //t  = JXG.GeonextParser.geonext2JS(v, this.board);
    780                 //newfuncs[i] = new Function('','return ' + t + ';');
    781                 newfuncs[i] = this.board.jc.snippet(v, true, null, true);
    782
    783                 if (terms.length === 2) {
    784                     this[what[i] + 'jc'] = terms[i];
    785                 }
    786             } else if (typeof v=='function') {
    787                 newfuncs[i] = v;
    788             } else if (typeof v=='number') {
    789                 newfuncs[i] = function(z){ return function() { return z; }; }(v);
    790             } else if (typeof v == 'object' && typeof v.Value == 'function') {    // Slider
    791                 newfuncs[i] = (function(a) { return function() { return a.Value(); };})(v);
    792             }
    793
    794             newfuncs[i].origin = v;
    795         }
    796         if (terms.length==1) { // Intersection function
    797             this.updateConstraint = function() {
    798                     var c = newfuncs[0]();
    799                     if (JXG.isArray(c)) {      // Array
    800                         this.coords.setCoordinates(JXG.COORDS_BY_USER,c);
    801                     } else {                   // Coords object
    802                         this.coords = c;
    803                     }
    804                 };
    805         } else if (terms.length==2) { // Euclidean coordinates
    806             this.XEval = newfuncs[0];
    807             this.YEval = newfuncs[1];
    808             fs = 'this.coords.setCoordinates(JXG.COORDS_BY_USER,[this.XEval(),this.YEval()]);';
    809             this.updateConstraint = new Function('',fs);
    810         } else { // Homogeneous coordinates
    811             this.ZEval = newfuncs[0];
    812             this.XEval = newfuncs[1];
    813             this.YEval = newfuncs[2];
    814             fs = 'this.coords.setCoordinates(JXG.COORDS_BY_USER,[this.ZEval(),this.XEval(),this.YEval()]);';
    815             this.updateConstraint = new Function('',fs);
    816         }
    817
    818         if (!this.board.isSuspendedUpdate) { this.prepareUpdate().update().updateRenderer(); }
    819         return this;
    820     },
    821
    822     /**
    823      * Applies the transformations of the curve to {@link JXG.Point#baseElement}.
    824      * @returns {JXG.Point} Reference to this point object.
    825      */
    826     updateTransform: function () {
    827         if (this.transformations.length==0 || this.baseElement==null) {
    828             return this;
    829         }
    830         var c, i;
    831
    832         if (this===this.baseElement) {      // case of bindTo
    833             c = this.transformations[0].apply(this.baseElement, 'self');
    834         } else {                           // case of board.create('point',[baseElement,transform]);
    835             c = this.transformations[0].apply(this.baseElement);
    836         }
    837         this.coords.setCoordinates(JXG.COORDS_BY_USER,c);
    838         for (i = 1; i < this.transformations.length; i++) {
    839             this.coords.setCoordinates(JXG.COORDS_BY_USER, this.transformations[i].apply(this));
    840         }
    841         return this;
    842     },
    843
    844     /**
    845      * Add transformations to this point.
    846      * @param {JXG.GeometryElement} el TODO base element
    847      * @param {JXG.Transform|Array} transform Either one {@link JXG.Transform} or an array of {@link JXG.Transform}s.
    848      * @returns {JXG.Point} Reference to this point object.
    849      */
    850     addTransform: function (el, transform) {
    851         var i,
    852             list = JXG.isArray(transform) ? transform : [transform],
    853             len = list.length;
    854
    855         if (this.transformations.length === 0) { // There is only one baseElement possible
    856             this.baseElement = el;
    857         }
    858
    859         for (i = 0; i < len; i++) {
    860             this.transformations.push(list[i]);
    861         }
    862         return this;
    863     },
    864
    865     /**
    866      * Animate the point.
    867      * @param {Number} direction The direction the glider is animated. Can be +1 or -1.
    868      * @param {Number} stepCount The number of steps.
    869      * @name Glider#startAnimation
    870      * @see Glider#stopAnimation
    871      * @function
    872      */
    873     startAnimation: function(direction, stepCount) {
    874         if((this.type == JXG.OBJECT_TYPE_GLIDER) && (typeof this.intervalCode == 'undefined')) {
    875             this.intervalCode = window.setInterval('JXG.JSXGraph.boards[\'' + this.board.id + '\'].objects[\'' + this.id + '\']._anim('
    876                                                     + direction + ', ' + stepCount + ')', 250);
    877             if(typeof this.intervalCount == 'undefined')
    878                 this.intervalCount = 0;
    879         }
    880         return this;
    881     },
    882
    883     /**
    884      * Stop animation.
    885      * @name Glider#stopAnimation
    886      * @see Glider#startAnimation
    887      * @function
    888      */
    889     stopAnimation: function() {
    890         if(typeof this.intervalCode != 'undefined') {
    891             window.clearInterval(this.intervalCode);
    892             delete(this.intervalCode);
    893         }
    894         return this;
    895     },
    896
    897     /**
    898      * Starts an animation which moves the point along a given path in given time.
    899      * @param {Array,function} path The path the point is moved on. This can be either an array of arrays containing x and y values of the points of
    900      * the path, or  function taking the amount of elapsed time since the animation has started and returns an array containing a x and a y value or NaN.
    901      * In case of NaN the animation stops.
    902      * @param {Number} time The time in milliseconds in which to finish the animation
    903      * @param {Object} [options] Optional settings for the animation:<ul><li>callback: A function that is called as soon as the animation is finished.</li></ul>
    904      * @returns {JXG.Point} Reference to the point.
    905      */
    906     moveAlong: function(path, time, options) {
    907         options = options || {};
    908         var interpath = [],
    909             delay = 35,
    910             makeFakeFunction = function (i, j) {
    911                 return function() {
    912                     return path[i][j];
    913                 };
    914             },
    915             p = [], i, neville,
    916             steps = time/delay;
    917
    918         if (JXG.isArray(path)) {
    919             for (i = 0; i < path.length; i++) {
    920                 if (JXG.isPoint(path[i])) {
    921                     p[i] = path[i];
    922                 } else {
    923                     p[i] = {
    924                         elementClass: JXG.OBJECT_CLASS_POINT,
    925                         X: makeFakeFunction(i, 0),
    926                         Y: makeFakeFunction(i, 1)
    927                     };
    928                 }
    929             }
    930
    931             time = time || 0;
    932             if (time === 0) {
    933                 this.setPosition(JXG.COORDS_BY_USER, p[p.length - 1].X(), p[p.length - 1].Y());
    934                 return this.board.update(this);
    935             }
    936
    937             neville = JXG.Math.Numerics.Neville(p);
    938             for (i = 0; i < steps; i++) {
    939                 interpath[i] = [];
    940                 interpath[i][0] = neville[0]((steps - i) / steps * neville[3]());
    941                 interpath[i][1] = neville[1]((steps - i) / steps * neville[3]());
    942             }
    943
    944             this.animationPath = interpath;
    945         } else if (JXG.isFunction(path)) {
    946             this.animationPath = path;
    947             this.animationStart = new Date().getTime();
    948         }
    949         this.animationCallback = options.callback;
    950
    951         this.board.addAnimation(this);
    952         return this;
    953     },
    954
    955     /**
    956      * Starts an animated point movement towards the given coordinates <tt>where</tt>. The animation is done after <tt>time</tt> milliseconds.
    957      * If the second parameter is not given or is equal to 0, setPosition() is called, see #setPosition.
    958      * @param {Array} where Array containing the x and y coordinate of the target location.
    959      * @param {Number} [time] Number of milliseconds the animation should last.
    960      * @param {Object} [options] Optional settings for the animation:<ul><li>callback: A function that is called as soon as the animation is finished.</li>
    961      * <li>effect: animation effects like speed fade in and out. possible values are '<>' for speed increase on start and slow down at the end (default)
    962      * and '--' for constant speed during the whole animation.</li></ul>
    963      * @returns {JXG.Point} Reference to itself.
    964      * @see #animate
    965      */
    966     moveTo: function(where, time, options) {
    967         if (typeof time == 'undefined' || time == 0) {
    968             this.setPosition(JXG.COORDS_BY_USER, where[0], where[1]);
    969             return this.board.update(this);
    970         }
    971
    972         options = options || {};
    973
    974     	var delay = 35,
    975     	    steps = Math.ceil(time/(delay * 1.0)),
    976     		coords = new Array(steps+1),
    977     		X = this.coords.usrCoords[1],
    978     		Y = this.coords.usrCoords[2],
    979     		dX = (where[0] - X),
    980     		dY = (where[1] - Y),
    981     	    i,
    982             stepFun = function (i) {
    983                 if (options.effect && options.effect == '<>') {
    984                     return Math.pow(Math.sin((i/(steps*1.0))*Math.PI/2.), 2);
    985                 }
    986                 return i/steps;
    987             };
    988
    989         if(Math.abs(dX) < JXG.Math.eps && Math.abs(dY) < JXG.Math.eps)
    990             return this;
    991
    992     	for(i=steps; i>=0; i--) {
    993     		coords[steps-i] = [X + dX * stepFun(i), Y+ dY * stepFun(i)];
    994     	}
    995
    996     	this.animationPath = coords;
    997         this.animationCallback = options.callback;
    998         this.board.addAnimation(this);
    999         return this;
    1000     },
    1001
    1002     /**
    1003      * Starts an animated point movement towards the given coordinates <tt>where</tt>. After arriving at <tt>where</tt> the point moves back to where it started.
    1004      * The animation is done after <tt>time</tt> milliseconds.
    1005      * @param {Array} where Array containing the x and y coordinate of the target location.
    1006      * @param {Number} time Number of milliseconds the animation should last.
    1007      * @param {Object} [options] Optional settings for the animation:<ul><li>callback: A function that is called as soon as the animation is finished.</li>
    1008      * <li>effect: animation effects like speed fade in and out. possible values are '<>' for speed increase on start and slow down at the end (default)
    1009      * and '--' for constant speed during the whole animation.</li><li>repeat: How often this animation should be repeated (default: 1)</li></ul>
    1010      * @returns {JXG.Point} Reference to itself.
    1011      * @see #animate
    1012      */
    1013     visit: function(where, time, options) {
    1014         // support legacy interface where the third parameter was the number of repeats
    1015         if (typeof options == 'number') {
    1016             options = {repeat: options};
    1017         } else {
    1018             options = options || {};
    1019             if(typeof options.repeat == 'undefined')
    1020                 options.repeat = 1;
    1021         }
    1022
    1023         var delay = 35,
    1024             steps = Math.ceil(time/(delay*options.repeat)),
    1025             coords = new Array(options.repeat*(steps+1)),
    1026             X = this.coords.usrCoords[1],
    1027             Y = this.coords.usrCoords[2],
    1028             dX = (where[0] - X),
    1029             dY = (where[1] - Y),
    1030             i, j,
    1031             stepFun = function (i) {
    1032                 var x = (i < steps/2 ? 2*i/steps : 2*(steps-i)/steps);
    1033                 if (options.effect && options.effect == '<>') {
    1034                     return Math.pow(Math.sin((x)*Math.PI/2.), 2);
    1035                 }
    1036                 return x;
    1037             };
    1038
    1039         for (j = 0; j < options.repeat; j++) {
    1040             for(i = steps; i >= 0; i--) {
    1041                 coords[j*(steps+1) + steps-i] = [X + dX * stepFun(i),
    1042                     Y+ dY * stepFun(i)];
    1043             }
    1044         }
    1045         this.animationPath = coords;
    1046         this.animationCallback = options.callback;
    1047         this.board.addAnimation(this);
    1048         return this;
    1049     },
    1050
    1051     /**
    1052      * Animates a glider. Is called by the browser after startAnimation is called.
    1053      * @param {Number} direction The direction the glider is animated.
    1054      * @param {Number} stepCount The number of steps.
    1055      * @see #startAnimation
    1056      * @see #stopAnimation
    1057      * @private
    1058      */
    1059     _anim: function(direction, stepCount) {
    1060         var distance, slope, dX, dY, alpha, startPoint,
    1061             factor = 1, newX, radius;
    1062
    1063         this.intervalCount++;
    1064         if(this.intervalCount > stepCount)
    1065             this.intervalCount = 0;
    1066
    1067         if(this.slideObject.elementClass == JXG.OBJECT_CLASS_LINE) {
    1068             distance = this.slideObject.point1.coords.distance(JXG.COORDS_BY_SCREEN, this.slideObject.point2.coords);
    1069             slope = this.slideObject.getSlope();
    1070             if(slope != 'INF') {
    1071                 alpha = Math.atan(slope);
    1072                 dX = Math.round((this.intervalCount/stepCount) * distance*Math.cos(alpha));
    1073                 dY = Math.round((this.intervalCount/stepCount) * distance*Math.sin(alpha));
    1074             } else {
    1075                 dX = 0;
    1076                 dY = Math.round((this.intervalCount/stepCount) * distance);
    1077             }
    1078
    1079             if(direction < 0) {
    1080                 startPoint = this.slideObject.point2;
    1081                 if(this.slideObject.point2.coords.scrCoords[1] - this.slideObject.point1.coords.scrCoords[1] > 0)
    1082                     factor = -1;
    1083                 else if(this.slideObject.point2.coords.scrCoords[1] - this.slideObject.point1.coords.scrCoords[1] == 0) {
    1084                     if(this.slideObject.point2.coords.scrCoords[2] - this.slideObject.point1.coords.scrCoords[2] > 0)
    1085                         factor = -1;
    1086                 }
    1087             } else {
    1088                 startPoint = this.slideObject.point1;
    1089                 if(this.slideObject.point1.coords.scrCoords[1] - this.slideObject.point2.coords.scrCoords[1] > 0)
    1090                     factor = -1;
    1091                 else if(this.slideObject.point1.coords.scrCoords[1] - this.slideObject.point2.coords.scrCoords[1] == 0) {
    1092                     if(this.slideObject.point1.coords.scrCoords[2] - this.slideObject.point2.coords.scrCoords[2] > 0)
    1093                         factor = -1;
    1094                 }
    1095             }
    1096
    1097             this.coords.setCoordinates(JXG.COORDS_BY_SCREEN, [startPoint.coords.scrCoords[1] + factor*dX,
    1098                                                               startPoint.coords.scrCoords[2] + factor*dY]);
    1099         } else if(this.slideObject.elementClass == JXG.OBJECT_CLASS_CURVE) {
    1100             if(direction > 0) {
    1101                 newX = Math.round(this.intervalCount/stepCount * this.board.canvasWidth);
    1102             } else {
    1103                 newX = Math.round((stepCount - this.intervalCount)/stepCount * this.board.canvasWidth);
    1104             }
    1105
    1106             this.coords.setCoordinates(JXG.COORDS_BY_SCREEN, [newX, 0]);
    1107             this.coords = JXG.Math.Geometry.projectPointToCurve(this, this.slideObject, this.board);
    1108         } else if(this.slideObject.elementClass == JXG.OBJECT_CLASS_CIRCLE) {
    1109             if(direction < 0) {
    1110                 alpha = this.intervalCount/stepCount * 2*Math.PI;
    1111             } else {
    1112                 alpha = (stepCount - this.intervalCount)/stepCount * 2*Math.PI;
    1113             }
    1114
    1115             radius = this.slideObject.Radius();
    1116
    1117             this.coords.setCoordinates(JXG.COORDS_BY_USER, [this.slideObject.center.coords.usrCoords[1] + radius*Math.cos(alpha),
    1118                                                             this.slideObject.center.coords.usrCoords[2] + radius*Math.sin(alpha)]);
    1119         }
    1120
    1121         this.board.update(this);
    1122         return this;
    1123     },
    1124
    1125     /**
    1126      * Set the style of a point. Used for GEONExT import and should not be used to set the point's face and size.
    1127      * @param {Number} i Integer to determine the style.
    1128      * @private
    1129      */
    1130     setStyle: function(i) {
    1131         var facemap = [
    1132                 // 0-2
    1133                 'cross', 'cross', 'cross',
    1134                 // 3-6
    1135                 'circle', 'circle', 'circle', 'circle',
    1136                 // 7-9
    1137                 'square', 'square', 'square',
    1138                 // 10-12
    1139                 'plus', 'plus', 'plus'
    1140             ], sizemap = [
    1141                 // 0-2
    1142                 2, 3, 4,
    1143                 // 3-6
    1144                 1, 2, 3, 4,
    1145                 // 7-9
    1146                 2, 3, 4,
    1147                 // 10-12
    1148                 2, 3, 4
    1149             ];
    1150
    1151         this.visProp.face = facemap[i];
    1152         this.visProp.size = sizemap[i];
    1153
    1154         this.board.renderer.changePointStyle(this);
    1155         return this;
    1156     },
    1157
    1158     /**
    1159      * All point faces can be defined with more than one name, e.g. a cross faced point can be given
    1160      * by face equal to 'cross' or equal to 'x'. This method maps all possible values to fixed ones to
    1161      * simplify if- and switch-clauses regarding point faces. The translation table is as follows:
    1162      * <table>
    1163      * <tr><th>Input</th><th>Output</th></tr>
    1164      * <tr><td>cross, x</td><td>x</td></tr>
    1165      * <tr><td>circle, o</td><td>o</td></tr>
    1166      * <tr><td>square, []</td><td>[]</td></tr>
    1167      * <tr><td>plus, +</td><td>+</td></tr>
    1168      * <tr><td>diamond, <></td><td><></td></tr>
    1169      * <tr><td>triangleup, a, ^</td><td>A</td></tr>
    1170      * <tr><td>triangledown, v</td><td>v</td></tr>
    1171      * <tr><td>triangleleft, <</td><td><</td></tr>
    1172      * <tr><td>triangleright, ></td><td>></td></tr>
    1173      * </table>
    1174      * @param {String} s A string which should determine a valid point face.
    1175      * @returns {String} Returns a normalized string or undefined if the given string is not a valid
    1176      * point face.
    1177      */
    1178     normalizeFace: function(s) {
    1179         var map = {
    1180                 cross: 'x',
    1181                 x: 'x',
    1182                 circle: 'o',
    1183                 o: 'o',
    1184                 square: '[]',
    1185                 '[]': '[]',
    1186                 plus: '+',
    1187                 '+': '+',
    1188                 diamond: '<>',
    1189                 '<>': '<>',
    1190                 triangleup: '^',
    1191                 a: '^',
    1192                 '^': '^',
    1193                 triangledown: 'v',
    1194                 v: 'v',
    1195                 triangleleft: '<',
    1196                 '<': '<',
    1197                 triangleright: '>',
    1198                 '>': '>'
    1199             };
    1200
    1201         return map[s];
    1202     },
    1203
    1204     /**
    1205      * Remove the point from the drawing. This only removes the SVG or VML node of the point and its label from the renderer, to remove
    1206      * the object completely you should use {@link JXG.Board#removeObject}.
    1207      */
    1208     remove: function() {
    1209         if (this.hasLabel) {
    1210             this.board.renderer.remove(this.board.renderer.getElementById(this.label.content.id));
    1211         }
    1212         this.board.renderer.remove(this.board.renderer.getElementById(this.id));
    1213     },
    1214
    1215     // documented in GeometryElement
    1216     getTextAnchor: function() {
    1217         return this.coords;
    1218     },
    1219
    1220     // documented in GeometryElement
    1221     getLabelAnchor: function() {
    1222         return this.coords;
    1223     },
    1224
    1225     /**
    1226      * Set the face of a point element.
    1227      * @param {string} f String which determines the face of the point. See {@link JXG.GeometryElement#face} for a list of available faces.
    1228      * @see JXG.GeometryElement#face
    1229      */
    1230     face: function(f) {
    1231         this.setProperty({face:f});
    1232     },
    1233
    1234     /**
    1235      * Set the size of a point element
    1236      * @param {int} s Integer which determines the size of the point.
    1237      * @see JXG.GeometryElement#size
    1238      */
    1239     size: function(s) {
    1240         this.setProperty({size:s});
    1241     },
    1242
    1243     // already documented in GeometryElement
    1244     cloneToBackground: function() {
    1245         var copy = {};
    1246
    1247         copy.id = this.id + 'T' + this.numTraces;
    1248         this.numTraces++;
    1249
    1250         copy.coords = this.coords;
    1251         copy.visProp = JXG.deepCopy(this.visProp, this.visProp.traceattributes, true);
    1252         copy.visProp.layer = this.board.options.layer.trace;
    1253         copy.elementClass = JXG.OBJECT_CLASS_POINT;
    1254         copy.board = this.board;
    1255         JXG.clearVisPropOld(copy);
    1256
    1257         this.board.renderer.drawPoint(copy);
    1258         this.traces[copy.id] = copy.rendNode;
    1259
    1260         return this;
    1261     },
    1262
    1263     getParents: function () {
    1264         var p = [this.Z(), this.X(), this.Y()];
    1265
    1266         if (this.parents) {
    1267             p = this.parents;
    1268         }
    1269
    1270         if (this.type == JXG.OBJECT_TYPE_GLIDER) {
    1271             p = [this.X(), this.Y(), this.slideObject.id];
    1272
    1273         }
    1274
    1275         return p;
    1276     }
    1277 });
    1278
    1279
    1280 /**
    1281  * @class This element is used to provide a constructor for a general point. A free point is created if the given parent elements are all numbers
    1282  * and the property fixed is not set or set to false. If one or more parent elements is not a number but a string containing a GEONE<sub>x</sub>T
    1283  * constraint or a function the point will be considered as constrained). That means that the user won't be able to change the point's
    1284  * position directly.
    1285  * @pseudo
    1286  * @description
    1287  * @name Point
    1288  * @augments JXG.Point
    1289  * @constructor
    1290  * @type JXG.Point
    1291  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1292  * @param {Number,string,function_Number,string,function_Number,string,function} z_,x,y Parent elements can be two or three elements of type number, a string containing a GEONE<sub>x</sub>T
    1293  * constraint, or a function which takes no parameter and returns a number. Every parent element determines one coordinate. If a coordinate is
    1294  * given by a number, the number determines the initial position of a free point. If given by a string or a function that coordinate will be constrained
    1295  * that means the user won't be able to change the point's position directly by mouse because it will be calculated automatically depending on the string
    1296  * or the function's return value. If two parent elements are given the coordinates will be interpreted as 2D affine euclidean coordinates, if three such
    1297  * parent elements are given they will be interpreted as homogeneous coordinates.
    1298  * @param {JXG.Point_JXG.Transformation} Point,Transformation A point can also be created providing a transformation. The resulting point is a clone of the base
    1299  * point transformed by the given Transformation. {@see JXG.Transformation}.
    1300  * @example
    1301  * // Create a free point using affine euclidean coordinates
    1302  * var p1 = board.create('point', [3.5, 2.0]);
    1303  * </pre><div id="672f1764-7dfa-4abc-a2c6-81fbbf83e44b" style="width: 200px; height: 200px;"></div>
    1304  * <script type="text/javascript">
    1305  *   var board = JXG.JSXGraph.initBoard('672f1764-7dfa-4abc-a2c6-81fbbf83e44b', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
    1306  *   var p1 = board.create('point', [3.5, 2.0]);
    1307  * </script><pre>
    1308  * @example
    1309  * // Create a constrained point using anonymous function
    1310  * var p2 = board.create('point', [3.5, function () { return p1.X(); }]);
    1311  * </pre><div id="4fd4410c-3383-4e80-b1bb-961f5eeef224" style="width: 200px; height: 200px;"></div>
    1312  * <script type="text/javascript">
    1313  *   var fpex1_board = JXG.JSXGraph.initBoard('4fd4410c-3383-4e80-b1bb-961f5eeef224', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
    1314  *   var fpex1_p1 = fpex1_board.create('point', [3.5, 2.0]);
    1315  *   var fpex1_p2 = fpex1_board.create('point', [3.5, function () { return fpex1_p1.X(); }]);
    1316  * </script><pre>
    1317  * @example
    1318  * // Create a point using transformations
    1319  * var trans = board.create('transform', [2, 0.5], {type:'scale'});
    1320  * var p3 = board.create('point', [p2, trans]);
    1321  * </pre><div id="630afdf3-0a64-46e0-8a44-f51bd197bb8d" style="width: 400px; height: 400px;"></div>
    1322  * <script type="text/javascript">
    1323  *   var fpex2_board = JXG.JSXGraph.initBoard('630afdf3-0a64-46e0-8a44-f51bd197bb8d', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
    1324  *   var fpex2_trans = fpex2_board.create('transform', [2, 0.5], {type:'scale'});
    1325  *   var fpex2_p2 = fpex2_board.create('point', [3.5, 2.0]);
    1326  *   var fpex2_p3 = fpex2_board.create('point', [fpex2_p2, fpex2_trans]);
    1327  * </script><pre>
    1328  */
    1329 JXG.createPoint = function(board, parents, attributes) {
    1330     var el, isConstrained = false, i, attr;
    1331
    1332     attr = JXG.copyAttributes(attributes, board.options, 'point');
    1333
    1334     for (i=0;i<parents.length;i++) {
    1335         if (typeof parents[i]=='function' || typeof parents[i]=='string') {
    1336             isConstrained = true;
    1337         }
    1338     }
    1339     if (!isConstrained) {
    1340         if ( (JXG.isNumber(parents[0])) && (JXG.isNumber(parents[1])) ) {
    1341             el = new JXG.Point(board, parents, attr);
    1342             if ( JXG.exists(attr["slideobject"]) ) {
    1343                 el.makeGlider(attr["slideobject"]);
    1344             } else {
    1345                 el.baseElement = el; // Free point
    1346             }
    1347             el.isDraggable = true;
    1348         } else if ( (typeof parents[0]=='object') && (typeof parents[1]=='object') ) { // Transformation
    1349             el = new JXG.Point(board, [0,0], attr);
    1350             el.addTransform(parents[0],parents[1]);
    1351             el.isDraggable = false;
    1352
    1353             el.parents = [parents[0].id, parents[1].id];
    1354         }
    1355         else {// Failure
    1356             throw new Error("JSXGraph: Can't create point with parent types '" +
    1357                             (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    1358                             "\nPossible parent types: [x,y], [z,x,y], [point,transformation]");
    1359         }
    1360
    1361     } else {
    1362         el = new JXG.Point(board, [NaN, NaN], attr);
    1363         el.addConstraint(parents);
    1364     }
    1365
    1366     return el;
    1367 };
    1368
    1369 /**
    1370  * @class This element is used to provide a constructor for a glider point.
    1371  * @pseudo
    1372  * @description A glider is a point which lives on another geometric element like a line, circle, curve, turtle.
    1373  * @name Glider
    1374  * @augments JXG.Point
    1375  * @constructor
    1376  * @type JXG.Point
    1377  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1378  * @param {Number_Number_Number_JXG.GeometryElement} z_,x_,y_,GlideObject Parent elements can be two or three elements of type number and the object the glider lives on.
    1379  * The coordinates are completely optional. If not given the origin is used. If you provide two numbers for coordinates they will be interpreted as affine euclidean
    1380  * coordinates, otherwise they will be interpreted as homogeneous coordinates. In any case the point will be projected on the glide object.
    1381  * @example
    1382  * // Create a glider with user defined coordinates. If the coordinates are not on
    1383  * // the circle (like in this case) the point will be projected onto the circle.
    1384  * var p1 = board.create('point', [2.0, 2.0]);
    1385  * var c1 = board.create('circle', [p1, 2.0]);
    1386  * var p2 = board.create('glider', [2.0, 1.5, c1]);
    1387  * </pre><div id="4f65f32f-e50a-4b50-9b7c-f6ec41652930" style="width: 300px; height: 300px;"></div>
    1388  * <script type="text/javascript">
    1389  *   var gpex1_board = JXG.JSXGraph.initBoard('4f65f32f-e50a-4b50-9b7c-f6ec41652930', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
    1390  *   var gpex1_p1 = gpex1_board.create('point', [2.0, 2.0]);
    1391  *   var gpex1_c1 = gpex1_board.create('circle', [gpex1_p1, 2.0]);
    1392  *   var gpex1_p2 = gpex1_board.create('glider', [2.0, 1.5, gpex1_c1]);
    1393  * </script><pre>
    1394  * @example
    1395  * // Create a glider with default coordinates (1,0,0). Same premises as above.
    1396  * var p1 = board.create('point', [2.0, 2.0]);
    1397  * var c1 = board.create('circle', [p1, 2.0]);
    1398  * var p2 = board.create('glider', [c1]);
    1399  * </pre><div id="4de7f181-631a-44b1-a12f-bc4d995609e8" style="width: 200px; height: 200px;"></div>
    1400  * <script type="text/javascript">
    1401  *   var gpex2_board = JXG.JSXGraph.initBoard('4de7f181-631a-44b1-a12f-bc4d995609e8', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
    1402  *   var gpex2_p1 = gpex2_board.create('point', [2.0, 2.0]);
    1403  *   var gpex2_c1 = gpex2_board.create('circle', [gpex2_p1, 2.0]);
    1404  *   var gpex2_p2 = gpex2_board.create('glider', [gpex2_c1]);
    1405  * </script><pre>
    1406  */
    1407 JXG.createGlider = function(board, parents, attributes) {
    1408     var el,
    1409         attr = JXG.copyAttributes(attributes, board.options, 'point');
    1410
    1411     if (parents.length === 1) {
    1412         el = board.create('point', [0, 0], attr);
    1413     } else {
    1414         el = board.create('point', parents.slice(0, 2), attr);
    1415     }
    1416
    1417     // eltype is set in here
    1418     el.makeGlider(parents[parents.length-1]);
    1419
    1420     return el;
    1421 };
    1422
    1423 /**
    1424  * @class This element is used to provide a constructor for an intersection point.
    1425  * @pseudo
    1426  * @description An intersection point is a point which lives on two Lines or Circles or one Line and one Circle at the same time, i.e.
    1427  * an intersection point of the two elements.
    1428  * @name Intersection
    1429  * @augments JXG.Point
    1430  * @constructor
    1431  * @type JXG.Point
    1432  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1433  * @param {JXG.Line,JXG.Circle_JXG.Line,JXG.Circle_Number} el1,el2,i The result will be a intersection point on el1 and el2. i determines the
    1434  * intersection point if two points are available: <ul>
    1435  *   <li>i==0: use the positive square root,</li>
    1436  *   <li>i==1: use the negative square root.</li></ul>
    1437  * @example
    1438  * // Create an intersection point of circle and line
    1439  * var p1 = board.create('point', [2.0, 2.0]);
    1440  * var c1 = board.create('circle', [p1, 2.0]);
    1441  *
    1442  * var p2 = board.create('point', [2.0, 2.0]);
    1443  * var p3 = board.create('point', [2.0, 2.0]);
    1444  * var l1 = board.create('line', [p2, p3]);
    1445  *
    1446  * var i = board.create('intersection', [c1, l1, 0]);
    1447  * </pre><div id="e5b0e190-5200-4bc3-b995-b6cc53dc5dc0" style="width: 300px; height: 300px;"></div>
    1448  * <script type="text/javascript">
    1449  *   var ipex1_board = JXG.JSXGraph.initBoard('e5b0e190-5200-4bc3-b995-b6cc53dc5dc0', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    1450  *   var ipex1_p1 = ipex1_board.create('point', [4.0, 4.0]);
    1451  *   var ipex1_c1 = ipex1_board.create('circle', [ipex1_p1, 2.0]);
    1452  *   var ipex1_p2 = ipex1_board.create('point', [1.0, 1.0]);
    1453  *   var ipex1_p3 = ipex1_board.create('point', [5.0, 3.0]);
    1454  *   var ipex1_l1 = ipex1_board.create('line', [ipex1_p2, ipex1_p3]);
    1455  *   var ipex1_i = ipex1_board.create('intersection', [ipex1_c1, ipex1_l1, 0]);
    1456  * </script><pre>
    1457  */
    1458 JXG.createIntersectionPoint = function(board, parents, attributes) {
    1459     var el;
    1460     if (parents.length>=3) {
    1461         if(parents.length == 3)
    1462             parents.push(null);
    1463         el = board.create('point', [board.intersection(parents[0], parents[1], parents[2], parents[3])], attributes);
    1464     }
    1465
    1466     parents[0].addChild(el);
    1467     parents[1].addChild(el);
    1468
    1469     el.elType = 'intersection';
    1470     el.parents = [parents[0].id, parents[1].id, parents[2]];
    1471
    1472     if (parents[3] != null) {
    1473         el.parents.push(parents[3]);
    1474     }
    1475
    1476     el.generatePolynomial = function () {
    1477         var poly1 = parents[0].generatePolynomial(el);
    1478         var poly2 = parents[1].generatePolynomial(el);
    1479
    1480         if((poly1.length == 0) || (poly2.length == 0))
    1481             return [];
    1482         else
    1483             return [poly1[0], poly2[0]];
    1484     };
    1485
    1486     return el;
    1487 };
    1488
    1489 /**
    1490  * @class This element is used to provide a constructor for the "other" intersection point.
    1491  * @pseudo
    1492  * @description An intersection point is a point which lives on two Lines or Circles or one Line and one Circle at the same time, i.e.
    1493  * an intersection point of the two elements. Additionally, one intersection point is provided. The function returns the other intersection point.
    1494  * @name OtherIntersection
    1495  * @augments JXG.Point
    1496  * @constructor
    1497  * @type JXG.Point
    1498  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1499  * @param {JXG.Line,JXG.Circle_JXG.Line,JXG.Circle_JXG.Point} el1,el2,p The result will be a intersection point on el1 and el2. i determines the
    1500  * intersection point different from p:
    1501  * @example
    1502  * // Create an intersection point of circle and line
    1503  * var p1 = board.create('point', [2.0, 2.0]);
    1504  * var c1 = board.create('circle', [p1, 2.0]);
    1505  *
    1506  * var p2 = board.create('point', [2.0, 2.0]);
    1507  * var p3 = board.create('point', [2.0, 2.0]);
    1508  * var l1 = board.create('line', [p2, p3]);
    1509  *
    1510  * var i = board.create('intersection', [c1, l1, 0]);
    1511  * var j = board.create('otherintersection', [c1, l1, i]);
    1512  * </pre><div id="45e25f12-a1de-4257-a466-27a2ae73614c" style="width: 300px; height: 300px;"></div>
    1513  * <script type="text/javascript">
    1514  *   var ipex2_board = JXG.JSXGraph.initBoard('45e25f12-a1de-4257-a466-27a2ae73614c', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    1515  *   var ipex2_p1 = ipex2_board.create('point', [4.0, 4.0]);
    1516  *   var ipex2_c1 = ipex2_board.create('circle', [ipex2_p1, 2.0]);
    1517  *   var ipex2_p2 = ipex2_board.create('point', [1.0, 1.0]);
    1518  *   var ipex2_p3 = ipex2_board.create('point', [5.0, 3.0]);
    1519  *   var ipex2_l1 = ipex2_board.create('line', [ipex2_p2, ipex2_p3]);
    1520  *   var ipex2_i = ipex2_board.create('intersection', [ipex2_c1, ipex2_l1, 0], {name:'D'});
    1521  *   var ipex2_j = ipex2_board.create('otherintersection', [ipex2_c1, ipex2_l1, ipex2_i], {name:'E'});
    1522  * </script><pre>
    1523  */
    1524 JXG.createOtherIntersectionPoint = function(board, parents, attributes) {
    1525     var el;
    1526     if (parents.length!=3 ||
    1527         !JXG.isPoint(parents[2]) ||
    1528         (parents[0].elementClass != JXG.OBJECT_CLASS_LINE && parents[0].elementClass != JXG.OBJECT_CLASS_CIRCLE) ||
    1529         (parents[1].elementClass != JXG.OBJECT_CLASS_LINE && parents[1].elementClass != JXG.OBJECT_CLASS_CIRCLE) ) {
    1530         // Failure
    1531         throw new Error("JSXGraph: Can't create 'other intersection point' with parent types '" +
    1532                         (typeof parents[0]) + "',  '" + (typeof parents[1])+ "'and  '" + (typeof parents[2]) + "'." +
    1533                         "\nPossible parent types: [circle|line,circle|line,point]");
    1534     }
    1535     else {
    1536         el = board.create('point', [board.otherIntersection(parents[0], parents[1], parents[2])], attributes);
    1537     }
    1538     el.elType = 'otherintersection';
    1539     el.parents = [parents[0].id, parents[1].id, parents[2]];
    1540
    1541     parents[0].addChild(el);
    1542     parents[1].addChild(el);
    1543
    1544     el.generatePolynomial = function () {
    1545         var poly1 = parents[0].generatePolynomial(el);
    1546         var poly2 = parents[1].generatePolynomial(el);
    1547
    1548         if((poly1.length == 0) || (poly2.length == 0))
    1549             return [];
    1550         else
    1551             return [poly1[0], poly2[0]];
    1552     };
    1553
    1554     return el;
    1555 };
    1556
    1557
    1558 JXG.JSXGraph.registerElement('point',JXG.createPoint);
    1559 JXG.JSXGraph.registerElement('glider', JXG.createGlider);
    1560 JXG.JSXGraph.registerElement('intersection', JXG.createIntersectionPoint);
    1561 JXG.JSXGraph.registerElement('otherintersection', JXG.createOtherIntersectionPoint);
    1562