      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 Line is defined in this file. Line stores all
     28  * style and functional properties that are required to draw and move a line on
     29  * a board.
     30  */
     31
     32 /**
     33  * The Line class is a basic class for all kind of line objects, e.g. line, arrow, and axis. It is usually defined by two points and can
     34  * be intersected with some other geometry elements.
     35  * @class Creates a new basic line object. Do not use this constructor to create a line. Use {@link JXG.Board#create} with
     36  * type {@link Line}, {@link Arrow}, or {@link Axis} instead.
     37  * @constructor
     38  * @augments JXG.GeometryElement
     39  * @param {String,JXG.Board} board The board the new line is drawn on.
     40  * @param {Point} p1 Startpoint of the line.
     41  * @param {Point} p2 Endpoint of the line.
     42  * @param {String} id Unique identifier for this object. If null or an empty string is given,
     43  * an unique id will be generated by Board
     44  * @param {String} name Not necessarily unique name. If null or an
     45  * empty string is given, an unique name will be generated.
     46  * @param {boolean} withLabel construct label, yes/no
     47  * @param {integer} layer display layer [0-9]
     48  * @see JXG.Board#generateName
     49  */
     50 JXG.Line = function (board, p1, p2, attributes) {
     51     this.constructor(board, attributes, JXG.OBJECT_TYPE_LINE, JXG.OBJECT_CLASS_LINE);
     52
     53     /**
     54      * Startpoint of the line. You really should not set this field directly as it may break JSXGraph's
     55      * udpate system so your construction won't be updated properly.
     56      * @type JXG.Point
     57      */
     58     this.point1 = JXG.getReference(this.board, p1);
     59
     60     /**
     61      * Endpoint of the line. Just like {@link #point1} you shouldn't write this field directly.
     62      * @type JXG.Point
     63      */
     64     this.point2 = JXG.getReference(this.board, p2);
     65
     66     /**
     67      * Array of ticks storing all the ticks on this line. Do not set this field directly and use
     68      * {@link JXG.Line#addTicks} and {@link JXG.Line#removeTicks} to add and remove ticks to and from the line.
     69      * @type Array
     70      * @see JXG.Ticks
     71      */
     72     this.ticks = [];
     73
     74     /**
     75      * Reference of the ticks created automatically when constructing an axis.
     76      * @type JXG.Ticks
     77      * @see JXG.Ticks
     78      */
     79     this.defaultTicks = null;
     80
     81     /**
     82     * If the line is the border of a polygon, the polygon object is stored, otherwise null.
     83     * @type JXG.Polygon
     84     * @default null
     85     * @private
     86     */
     87     this.parentPolygon = null;
     88
     89     /* Register line at board */
     90     this.id = this.board.setId(this, 'L');
     91     this.board.renderer.drawLine(this);
     92     this.board.finalizeAdding(this);
     93
     94     this.elType = 'line';
     95     // create Label
     96     this.createLabel();
     97
     98     /* Add arrow as child to defining points */
     99     this.point1.addChild(this);
    100     this.point2.addChild(this);
    101
    102
    103     this.updateStdform(); // This is needed in the following situation:
    104                           // * the line is defined by three coordinates
    105                           // * and it will have a glider
    106                           // * and board.suspendUpdate() has been called.
    107 };
    108
    109 JXG.Line.prototype = new JXG.GeometryElement;
    110
    111
    112 JXG.extend(JXG.Line.prototype, /** @lends JXG.Line.prototype */ {
    113     /**
    114      * Checks whether (x,y) is near the line.
    115      * @param {int} x Coordinate in x direction, screen coordinates.
    116      * @param {int} y Coordinate in y direction, screen coordinates.
    117      * @return {boolean} True if (x,y) is near the line, False otherwise.
    118      */
    119     hasPoint: function (x, y) {
    120         // Compute the stdform of the line in screen coordinates.
    121         var c = [], s,
    122             v = [1, x, y],
    123             vnew,
    124             p1c, p2c, d, pos, i;
    125
    126         c[0] = this.stdform[0] -
    127                this.stdform[1]*this.board.origin.scrCoords[1]/this.board.unitX+
    128                this.stdform[2]*this.board.origin.scrCoords[2]/this.board.unitY;
    129         c[1] = this.stdform[1]/this.board.unitX;
    130         c[2] = this.stdform[2]/(-this.board.unitY);
    131
    132         // Project the point orthogonally onto the line
    133         vnew = [0, c[1], c[2]];
    134         vnew = JXG.Math.crossProduct(vnew, v); // Orthogonal line to c through v
    135         vnew = JXG.Math.crossProduct(vnew, c); // Intersect orthogonal line with line
    136
    137         // Normalize the projected point
    138         vnew[1] /= vnew[0];
    139         vnew[2] /= vnew[0];
    140         vnew[0] = 1.0;
    141
    142         // The point is too far away from the line
    143         // dist(v,vnew)^2 projective
    144         s = /*(v[0]-vnew[0])*(v[0]-vnew[0])+*/ (v[1]-vnew[1])*(v[1]-vnew[1])+(v[2]-vnew[2])*(v[2]-vnew[2]);
    145         if (isNaN(s) || s>this.board.options.precision.hasPoint*this.board.options.precision.hasPoint) {
    146             return false;
    147         }
    148
    149         if(this.visProp.straightfirst && this.visProp.straightlast) {
    150             return true;
    151         } else { // If the line is a ray or segment we have to check if the projected point is between P1 and P2.
    152             p1c = this.point1.coords;
    153             p2c = this.point2.coords;
    154             vnew = (new JXG.Coords(JXG.COORDS_BY_SCREEN, vnew.slice(1), this.board)).usrCoords;
    155             d = p1c.distance(JXG.COORDS_BY_USER, p2c);
    156             p1c = p1c.usrCoords.slice(0);
    157             p2c = p2c.usrCoords.slice(0);
    158             if (d<JXG.Math.eps) {                                        // The defining points are identical
    159                 pos = 0.0;
    160             } else {
    161                 if (d==Number.POSITIVE_INFINITY) {                       // At least one point is an ideal point
    162 					d = 1.0/JXG.Math.eps;
    163                     if (Math.abs(p2c[0])<JXG.Math.eps) {                 // The second point is an ideal point
    164                         d /= JXG.Math.Geometry.distance([0,0,0], p2c);
    165                         p2c = [1, p1c[1]+p2c[1]*d, p1c[2]+p2c[2]*d];
    166                     } else {                                             // The first point is an ideal point
    167                         d /= JXG.Math.Geometry.distance([0,0,0], p1c);
    168                         p1c = [1, p2c[1]+p1c[1]*d, p2c[2]+p1c[2]*d];
    169                     }
    170                 }
    171                 i = 1;
    172                 d = p2c[i] - p1c[i];
    173                 if (Math.abs(d)<JXG.Math.eps) {
    174                     i = 2;
    175                     d = p2c[i] - p1c[i];
    176                 }
    177                 pos = (vnew[i] - p1c[i]) / d;
    178             }
    179             if(!this.visProp.straightfirst && pos<0) {
    180                 return false;
    181             }
    182             if(!this.visProp.straightlast && pos>1.0) {
    183                 return false;
    184             }
    185             return true;
    186         }
    187     },
    188
    189     /**
    190      * TODO description. maybe. already documented in geometryelement?
    191      * @private
    192      */
    193     update: function() {
    194         var funps, d1, d2, d, dnew, x, y, drag1, drag2;
    195
    196         if (!this.needsUpdate) { return this; }
    197
    198         if(this.constrained) {
    199             if(typeof this.funps != 'undefined') {
    200                 funps = this.funps();
    201                 if (funps && funps.length && funps.length === 2) {
    202                     this.point1 = funps[0];
    203                     this.point2 = funps[1];
    204                 }
    205             } else {
    206                 if (typeof this.funp1 === 'function') {
    207                     funps = this.funp1();
    208                     if (JXG.isPoint(funps)) {
    209                         this.point1 = funps;
    210                     } else if (funps && funps.length && funps.length === 2) {
    211                         this.point1.setPositionDirectly(JXG.COORDS_BY_USER, funps[0], funps[1]);
    212                     }
    213                 }
    214                 if (typeof this.funp2 === 'function') {
    215                     funps = this.funp2();
    216                     if (JXG.isPoint(funps)) {
    217                         this.point2 = funps;
    218                     } else if (funps && funps.length && funps.length === 2) {
    219                         this.point2.setPositionDirectly(JXG.COORDS_BY_USER, funps[0], funps[1]);
    220                     }
    221                 }
    222             }
    223         }
    224
    225         this.updateSegmentFixedLength();
    226
    227         this.updateStdform();
    228
    229         if(this.visProp.trace) {
    230             this.cloneToBackground(true);
    231         }
    232         return this;
    233     },
    234
    235     /**
    236      * Update segments with fixed length and at least one movable point.
    237      * @private
    238      */
    239     updateSegmentFixedLength: function() {
    240         //
    241         if (!this.hasFixedLength) { return this; }
    242         // Compute the actual length of the segment
    243         d = this.point1.Dist(this.point2);
    244         // Determine the length the segment ought to have
    245         dnew = this.fixedLength();
    246         // Distances between the two points and their respective
    247         // position before the update
    248         d1 = this.fixedLengthOldCoords[0].distance(JXG.COORDS_BY_USER, this.point1.coords);
    249         d2 = this.fixedLengthOldCoords[1].distance(JXG.COORDS_BY_USER, this.point2.coords);
    250
    251         // If the position of the points or the fixed length function has been changed
    252         // we have to work.
    253         if (d1>JXG.Math.eps || d2>JXG.Math.eps || d!=dnew) {
    254             drag1 = this.point1.isDraggable && (this.point1.type != JXG.OBJECT_TYPE_GLIDER) && !this.point1.visProp.fixed;
    255             drag2 = this.point2.isDraggable && (this.point2.type != JXG.OBJECT_TYPE_GLIDER) && !this.point2.visProp.fixed;
    256
    257             // First case: the two points are different
    258             // Then we try to adapt the point that was not dragged
    259             // If this point can not be moved (e.g. because it is a glider)
    260             // we try move the other point
    261             if (d>JXG.Math.eps) {
    262                 if ((d1>d2 && drag2) ||
    263                     (d1<=d2 && drag2 && !drag1)) {
    264                     this.point2.setPositionDirectly(JXG.COORDS_BY_USER,
    265                         this.point1.X() + (this.point2.X()-this.point1.X())*dnew/d,
    266                         this.point1.Y() + (this.point2.Y()-this.point1.Y())*dnew/d
    267                         );
    268                     this.point2.prepareUpdate().updateRenderer();
    269                 } else if ((d1<=d2 && drag1) ||
    270                            (d1>d2 && drag1 && !drag2)) {
    271                     this.point1.setPositionDirectly(JXG.COORDS_BY_USER,
    272                         this.point2.X() + (this.point1.X()-this.point2.X())*dnew/d,
    273                         this.point2.Y() + (this.point1.Y()-this.point2.Y())*dnew/d
    274                         );
    275                     this.point1.prepareUpdate().updateRenderer();
    276                 }
    277             // Second case: the two points are identical. In this situation
    278             // we choose a random direction.
    279             } else {
    280                 x = Math.random()-0.5;
    281                 y = Math.random()-0.5;
    282                 d = Math.sqrt(x*x+y*y);
    283                 if (drag2) {
    284                     this.point2.setPositionDirectly(JXG.COORDS_BY_USER,
    285                         this.point1.X() + x*dnew/d,
    286                         this.point1.Y() + y*dnew/d
    287                         );
    288                     this.point2.prepareUpdate().updateRenderer();
    289                 } else if (drag1) {
    290                     this.point1.setPositionDirectly(JXG.COORDS_BY_USER,
    291                         this.point2.X() + x*dnew/d,
    292                         this.point2.Y() + y*dnew/d
    293                         );
    294                     this.point1.prepareUpdate().updateRenderer();
    295                 }
    296             }
    297             // Finally, we save the position of the two points.
    298             this.fixedLengthOldCoords[0].setCoordinates(JXG.COORDS_BY_USER, this.point1.coords.usrCoords);
    299             this.fixedLengthOldCoords[1].setCoordinates(JXG.COORDS_BY_USER, this.point2.coords.usrCoords);
    300         }
    301         return this;
    302     },
    303
    304     /**
    305      * TODO description. already documented in geometryelement?
    306      * @private
    307      */
    308     updateStdform: function() {
    309         var v = JXG.Math.crossProduct(this.point1.coords.usrCoords, this.point2.coords.usrCoords);
    310         this.stdform[0] = v[0];
    311         this.stdform[1] = v[1];
    312         this.stdform[2] = v[2];
    313         this.stdform[3] = 0;
    314         this.normalize();
    315     },
    316
    317     /**
    318      * Uses the boards renderer to update the line.
    319      * @private
    320      */
    321      updateRenderer: function () {
    322         var wasReal;
    323
    324         if (this.needsUpdate && this.visProp.visible) {
    325             wasReal = this.isReal;
    326             this.isReal = (
    327                     !isNaN(this.point1.coords.usrCoords[1] +
    328                            this.point1.coords.usrCoords[2] +
    329                            this.point2.coords.usrCoords[1] +
    330                            this.point2.coords.usrCoords[2]
    331                           )
    332                     && (JXG.Math.innerProduct(this.stdform,this.stdform,3)>=JXG.Math.eps*JXG.Math.eps)
    333                     );
    334             if (this.isReal) {
    335                 if (wasReal!=this.isReal) {
    336                     this.board.renderer.show(this);
    337                     if(this.hasLabel && this.label.content.visProp.visible) this.board.renderer.show(this.label.content);
    338                 }
    339                 this.board.renderer.updateLine(this);
    340             } else {
    341                 if (wasReal!=this.isReal) {
    342                     this.board.renderer.hide(this);
    343                     if(this.hasLabel && this.label.content.visProp.visible) this.board.renderer.hide(this.label.content);
    344                 }
    345             }
    346
    347             //this.board.renderer.updateLine(this); // Why should we need this?
    348             this.needsUpdate = false;
    349         }
    350
    351         /* Update the label if visible. */
    352         if(this.hasLabel && this.label.content.visProp.visible && this.isReal) {
    353             //this.label.setCoordinates(this.coords);
    354             this.label.content.update();
    355             //this.board.renderer.updateLabel(this.label);
    356             this.board.renderer.updateText(this.label.content);
    357         }
    358         return this;
    359     },
    360
    361     /**
    362      * Used to generate a polynomial for a point p that lies on this line, i.e. p is collinear to {@link #point1}
    363      * and {@link #point2}.
    364      * @param p The point for that the polynomial is generated.
    365      * @return An array containing the generated polynomial.
    366      * @private
    367      */
    368     generatePolynomial: function (/** JXG.Point */ p) /** array */{
    369         var u1 = this.point1.symbolic.x,
    370             u2 = this.point1.symbolic.y,
    371             v1 = this.point2.symbolic.x,
    372             v2 = this.point2.symbolic.y,
    373             w1 = p.symbolic.x,
    374             w2 = p.symbolic.y;
    375
    376         /*
    377          * The polynomial in this case is determined by three points being collinear:
    378          *
    379          *      U (u1,u2)      W (w1,w2)                V (v1,v2)
    380          *  ----x--------------x------------------------x----------------
    381          *
    382          *  The collinearity condition is
    383          *
    384          *      u2-w2       w2-v2
    385          *     -------  =  -------           (1)
    386          *      u1-w1       w1-v1
    387          *
    388          * Multiplying (1) with denominators and simplifying is
    389          *
    390          *    u2w1 - u2v1 + w2v1 - u1w2 + u1v2 - w1v2 = 0
    391          */
    392
    393         return [['(',u2,')*(',w1,')-(',u2,')*(',v1,')+(',w2,')*(',v1,')-(',u1,')*(',w2,')+(',u1,')*(',v2,')-(',w1,')*(',v2,')'].join('')];
    394     },
    395
    396     /**
    397      * Calculates the rise of the line.
    398      * @type float
    399      * @return The rise of the line.
    400      */
    401     getRise: function () {
    402         if (Math.abs(this.stdform[2])>=JXG.Math.eps) {
    403             return -this.stdform[0]/this.stdform[2];
    404         } else {
    405             return Infinity;
    406         }
    407     },
    408
    409     /**
    410      * Calculates the slope of the line.
    411      * @type float
    412      * @return The slope of the line or Infinity if the line is parallel to the y-axis.
    413      */
    414     getSlope: function () {
    415         if (Math.abs(this.stdform[2])>=JXG.Math.eps) {
    416             return -this.stdform[1]/this.stdform[2];
    417         } else {
    418             return Infinity;
    419         }
    420     },
    421
    422     /**
    423      * Determines the angle between the positive x axis and the line.
    424      * @returns {Number}
    425      */
    426     getAngle: function () {
    427         return Math.atan2(this.point2.Y() - this.point1.Y(), this.point2.X() - this.point1.X());
    428     },
    429
    430     /**
    431      * Determines whether the line is drawn beyond {@link #point1} and {@link #point2} and updates the line.
    432      * @param {boolean} straightFirst True if the Line shall be drawn beyond {@link #point1}, false otherwise.
    433      * @param {boolean} straightLast True if the Line shall be drawn beyond {@link #point2}, false otherwise.
    434      * @see #straightFirst
    435      * @see #straightLast
    436      * @private
    437      */
    438     setStraight: function (straightFirst, straightLast) {
    439         this.visProp.straightfirst = straightFirst;
    440         this.visProp.straightlast = straightLast;
    441
    442         this.board.renderer.updateLine(this);
    443         return this;
    444     },
    445
    446     // documented in geometry element
    447     getTextAnchor: function() {
    448         return new JXG.Coords(JXG.COORDS_BY_USER, [0.5*(this.point2.X() + this.point1.X()), 0.5*(this.point2.Y() + this.point1.Y())],this.board);
    449     },
    450
    451     /**
    452      * Adjusts Label coords relative to Anchor. DESCRIPTION
    453      * @private
    454      */
    455     setLabelRelativeCoords: function(relCoords) {
    456         if (JXG.exists(this.label.content)) {
    457             this.label.content.relativeCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [relCoords[0],-relCoords[1]], this.board);
    458         }
    459     },
    460
    461     // documented in geometry element
    462     getLabelAnchor: function() {
    463         var c1, c2,
    464             x, y,
    465             sx = 0, sy = 0,
    466
    467         c1 = new JXG.Coords(JXG.COORDS_BY_USER, this.point1.coords.usrCoords, this.board);
    468         c2 = new JXG.Coords(JXG.COORDS_BY_USER, this.point2.coords.usrCoords, this.board);
    469
    470         if (this.visProp.straightfirst || this.visProp.straightlast) {
    471             JXG.Math.Geometry.calcStraight(this, c1, c2);
    472         }
    473         c1 = c1.scrCoords;
    474         c2 = c2.scrCoords;
    475         switch (this.label.content.visProp.position/*this.visProp.label.position*/) {
    476             case 'lft':
    477             case 'llft':
    478             case 'ulft':
    479                 if (c1[1] <= c2[1]) {
    480                     x = c1[1]; y = c1[2];
    481                 } else {
    482                     x = c2[1]; y = c2[2];
    483                 }
    484                 break;
    485             case 'rt':
    486             case 'lrt':
    487             case 'urt':
    488                 if (c1[1] > c2[1]) {
    489                     x = c1[1]; y = c1[2];
    490                 } else {
    491                     x = c2[1]; y = c2[2];
    492                 }
    493                 break;
    494             default:
    495                 x = 0.5*(c1[1] + c2[1]);
    496                 y = 0.5*(c1[2] + c2[2]);
    497         }
    498
    499         if (this.visProp.straightfirst || this.visProp.straightlast) {
    500             if (JXG.exists(this.label.content)) {  // Does not exist during createLabel
    501                 sx = parseFloat(this.label.content.visProp.offsets[0]);
    502                 sy = parseFloat(this.label.content.visProp.offsets[1]);
    503             }
    504             if (Math.abs(x)<JXG.Math.eps) {
    505                 if (this.visProp.label.position=='ulft'
    506                     || this.visProp.label.position=='llft'
    507                     || this.visProp.label.position=='lft') {
    508                     x += 2*sx;
    509                 }
    510             }
    511             if (Math.abs(x-this.board.canvasWidth)<JXG.Math.eps) {
    512                 if (this.visProp.label.position=='urt'
    513                     || this.visProp.label.position=='lrt'
    514                     || this.visProp.label.position=='rt') {
    515                     x -= 2.5*sx;
    516                 }
    517             }
    518
    519             if (Math.abs(y-this.board.canvasHeight)<JXG.Math.eps) {
    520                 if (this.visProp.label.position=='llft'
    521                     || this.visProp.label.position=='lrt'
    522                     || this.visProp.label.position=='bot') {
    523                     y -= 3*sy;
    524                 }
    525             }
    526             if (Math.abs(y)<JXG.Math.eps) {
    527                 if (this.visProp.label.position=='ulft'
    528                     || this.visProp.label.position=='urt'
    529                     || this.visProp.label.position=='top') {
    530                     y += 2*sy;
    531                 }
    532             }
    533         }
    534         return new JXG.Coords(JXG.COORDS_BY_SCREEN, [x, y], this.board);
    535     },
    536
    537     // documented in geometry element
    538     cloneToBackground: function() {
    539         var copy = {}, r, s, er;
    540
    541         copy.id = this.id + 'T' + this.numTraces;
    542         copy.elementClass = JXG.OBJECT_CLASS_LINE;
    543         this.numTraces++;
    544         copy.point1 = this.point1;
    545         copy.point2 = this.point2;
    546
    547         copy.stdform = this.stdform;
    548
    549         copy.board = this.board;
    550
    551         copy.visProp = JXG.deepCopy(this.visProp, this.visProp.traceattributes, true);
    552         copy.visProp.layer = this.board.options.layer.trace;
    553         JXG.clearVisPropOld(copy);
    554
    555         s = this.getSlope();
    556         r = this.getRise();
    557         copy.getSlope = function() { return s; };
    558         copy.getRise = function() { return r; };
    559
    560         er = this.board.renderer.enhancedRendering;
    561         this.board.renderer.enhancedRendering = true;
    562         this.board.renderer.drawLine(copy);
    563         this.board.renderer.enhancedRendering = er;
    564         this.traces[copy.id] = copy.rendNode;
    565
    566         delete copy;
    567
    568         return this;
    569     },
    570
    571     /**
    572      * Add transformations to this line.
    573      * @param {JXG.Transform|Array} transform Either one {@link JXG.Transform} or an array of {@link JXG.Transform}s.
    574      * @returns {JXG.Line} Reference to this line object.
    575      */
    576     addTransform: function (transform) {
    577         var i,
    578             list = JXG.isArray(transform) ? transform : [transform],
    579             len = list.length;
    580
    581         for (i = 0; i < len; i++) {
    582             this.point1.transformations.push(list[i]);
    583             this.point2.transformations.push(list[i]);
    584         }
    585
    586         return this;
    587     },
    588
    589     /**
    590      * TODO DESCRIPTION.
    591      * @param {null} method ignored
    592      * @param {Number} x
    593      * @param {Number} y
    594      * @returns {JXG.Line} Reference to this line object.
    595      */
    596     setPosition: function (method, x, y) {
    597         var t = this.board.create('transform', [x, y], {type:'translate'});
    598
    599         if (this.point1.transformations.length>0 && this.point1.transformations[this.point1.transformations.length-1].isNumericMatrix) {
    600             this.point1.transformations[this.point1.transformations.length-1].melt(t);
    601         } else {
    602             this.point1.addTransform(this.point1,t);
    603         }
    604         if (this.point2.transformations.length>0 && this.point2.transformations[this.point2.transformations.length-1].isNumericMatrix) {
    605             this.point2.transformations[this.point2.transformations.length-1].melt(t);
    606         } else {
    607             this.point2.addTransform(this.point2,t);
    608         }
    609
    610         return this;
    611     },
    612
    613     /**
    614      * Sets x and y coordinate and calls the circle's update() method.
    615      * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
    616      * @param {Number} x x coordinate in screen/user units
    617      * @param {Number} y y coordinate in screen/user units
    618      * @param {Number} oldx previous x coordinate in screen/user units
    619      * @param {Number} oldy previous y coordinate in screen/user units
    620      */
    621     setPositionDirectly: function (method, x, y, oldx, oldy) {
    622         var dx = x - oldx,
    623             dy = y - oldy;
    624
    625         if (!this.point1.draggable() || !this.point2.draggable()) {
    626             return this;
    627         }
    628
    629         dx /= this.board.unitX;
    630         dy /= -this.board.unitY;
    631         var t = this.board.create('transform', [dx, dy, 0], {type:'translate'});
    632         t.applyOnce([this.point1, this.point2] );
    633
    634         return this;
    635     },
    636
    637     /**
    638      * Treat the line as parametric curve in homogeneous coordinates, where the parameter t runs from 0 to 1.
    639      * First we transform the interval [0,1] to [-1,1].
    640      * If the line has homogeneous coordinates [c,a,b] = stdform[] then the direction of the line is [b,-a].
    641      * Now, we take one finite point that defines the line, i.e. we take either point1 or point2 (in case the line is not the ideal line).
    642      * Let the coordinates of that point be [z, x, y].
    643      * Then, the curve runs linearly from
    644      * [0, b, -a] (t=-1) to [z, x, y] (t=0)
    645      * and
    646      * [z, x, y] (t=0) to [0, -b, a] (t=1)
    647      *
    648      * @param {Number} t Parameter running from 0 to 1.
    649      * @returns {Number} X(t) x-coordinate of the line treated as parametric curve.
    650      * */
    651     X: function (t) {
    652         var b = this.stdform[2], x;
    653
    654         x = (Math.abs(this.point1.coords.usrCoords[0])>JXG.Math.eps) ?
    655             this.point1.coords.usrCoords[1] : this.point2.coords.usrCoords[1];
    656         t = (t-0.5)*2.0;
    657         if (t<0.0) {
    658             t *= (-1);
    659             return (1.0-t)*x + t*b;
    660         } else {
    661             return (1.0-t)*x - t*b;
    662         }
    663     },
    664
    665     /**
    666      * Treat the line as parametric curve in homogeneous coordinates. See {@link #X} for a detailed description.
    667      * @param {Number} t Parameter running from 0 to 1.
    668      * @returns {Number} Y(t) y-coordinate of the line treated as parametric curve.
    669      */
    670     Y: function (t) {
    671         var a = this.stdform[1], y;
    672
    673         y = (Math.abs(this.point1.coords.usrCoords[0])>JXG.Math.eps) ?
    674             this.point1.coords.usrCoords[2] : this.point2.coords.usrCoords[2];
    675         t = (t-0.5)*2.0;
    676         if (t<0.0) {
    677             t *= (-1);
    678             return (1.0-t)*y - t*a;
    679         } else {
    680             return (1.0-t)*y + t*a;
    681         }
    682     },
    683
    684     /**
    685      * Treat the line as parametric curve in homogeneous coordinates. See {@link #X} for a detailed description.
    686      * @param {Number} t Parameter running from 0 to 1.
    687      * @returns {Number} Z(t) z-coordinate of the line treated as parametric curve.
    688      */
    689     Z: function (t) {
    690         var z = (Math.abs(this.point1.coords.usrCoords[0])>JXG.Math.eps) ?
    691             this.point1.coords.usrCoords[0] : this.point2.coords.usrCoords[0];
    692
    693         t = (t-0.5)*2.0;
    694         if (t<0.0) {
    695             t *= (-1);
    696         }
    697         return (1.0-t) * z;
    698     },
    699
    700
    701     /*
    702      * This is needed for GEONExT compatibility
    703      * @type float
    704      * @return the distance between the two points defining the line
    705      */
    706     L: function() {
    707         return this.point1.Dist(this.point2);
    708     },
    709
    710     /**
    711      * TODO circle?!? --michael
    712      * private or public? --michael
    713      * Treat the circle as parametric curve:
    714      * t runs from 0 to 1
    715      * @private
    716      */
    717     minX: function () {
    718         return 0.0;
    719     },
    720
    721     /**
    722      * TODO circle?!? --michael
    723      * private or public? --michael
    724      * Treat the circle as parametric curve:
    725      * t runs from 0 to 1
    726      * @private
    727      */
    728     maxX: function () {
    729         return 1.0;
    730     },
    731
    732     // documented in geometry element
    733     bounds: function () {
    734         var p1c = this.point1.coords.usrCoords,
    735             p2c = this.point2.coords.usrCoords;
    736
    737         return [Math.min(p1c[1], p2c[1]), Math.max(p1c[2], p2c[2]), Math.max(p1c[1], p2c[1]), Math.min(p1c[2], p2c[2])];
    738     },
    739
    740     /**
    741      * Adds ticks to this line. Ticks can be added to any kind of line: line, arrow, and axis.
    742      * @param {JXG.Ticks} ticks Reference to a ticks object which is describing the ticks (color, distance, how many, etc.).
    743      * @type String
    744      * @return Id of the ticks object.
    745      */
    746     addTicks: function(ticks) {
    747         if(ticks.id == '' || typeof ticks.id == 'undefined')
    748             ticks.id = this.id + '_ticks_' + (this.ticks.length+1);
    749
    750         this.board.renderer.drawTicks(ticks);
    751         this.ticks.push(ticks);
    752
    753         return ticks.id;
    754     },
    755
    756     /**
    757      * Removes all ticks from a line.
    758      */
    759     removeAllTicks: function() {
    760         var i, t;
    761
    762         for(t = this.ticks.length; t > 0; t--) {
    763             this.removeTicks(this.ticks[t-1]);
    764         }
    765
    766         this.ticks = new Array();
    767         this.board.update();
    768     },
    769
    770     /**
    771      * Removes ticks identified by parameter named tick from this line.
    772      * @param {JXG.Ticks} tick Reference to tick object to remove.
    773      */
    774     removeTicks: function(tick) {
    775         var t, j;
    776         if(this.defaultTicks != null && this.defaultTicks == tick) {
    777             this.defaultTicks = null;
    778         }
    779
    780         for(t = this.ticks.length; t > 0; t--) {
    781             if(this.ticks[t-1] == tick) {
    782                 this.board.removeObject(this.ticks[t-1]);
    783
    784                 for(j=0; j<this.ticks[t-1].ticks.length; j++) {
    785                     if(this.ticks[t-1].labels[j] != null)
    786                         this.board.removeObject(this.ticks[t-1].labels[j]);
    787                 }
    788                 delete(this.ticks[t-1]);
    789                 break;
    790             }
    791         }
    792     }
    793 });
    794
    795 /**
    796  * @class This element is used to provide a constructor for a general line. A general line is given by two points. By setting additional properties
    797  * a line can be used as an arrow and/or axis.
    798  * @pseudo
    799  * @description
    800  * @name Line
    801  * @augments JXG.Line
    802  * @constructor
    803  * @type JXG.Line
    804  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    805  * @param {JXG.Point,array,function_JXG.Point,array,function} point1,point2 Parent elements can be two elements either of type {@link JXG.Point} or array of
    806  * numbers describing the coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
    807  * It is possible to provide a function returning an array or a point, instead of providing an array or a point.
    808  * @param {Number,function_Number,function_Number,function} a,b,c A line can also be created providing three numbers. The line is then described by
    809  * the set of solutions of the equation <tt>a*x+b*y+c*z = 0</tt>. It is possible to provide three functions returning numbers, too.
    810  * @param {function} f This function must return an array containing three numbers forming the line's homogeneous coordinates.
    811  * @example
    812  * // Create a line using point and coordinates/
    813  * // The second point will be fixed and invisible.
    814  * var p1 = board.create('point', [4.5, 2.0]);
    815  * var l1 = board.create('line', [p1, [1.0, 1.0]]);
    816  * </pre><div id="c0ae3461-10c4-4d39-b9be-81d74759d122" style="width: 300px; height: 300px;"></div>
    817  * <script type="text/javascript">
    818  *   var glex1_board = JXG.JSXGraph.initBoard('c0ae3461-10c4-4d39-b9be-81d74759d122', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    819  *   var glex1_p1 = glex1_board.create('point', [4.5, 2.0]);
    820  *   var glex1_l1 = glex1_board.create('line', [glex1_p1, [1.0, 1.0]]);
    821  * </script><pre>
    822  * @example
    823  * // Create a line using three coordinates
    824  * var l1 = board.create('line', [1.0, -2.0, 3.0]);
    825  * </pre><div id="cf45e462-f964-4ba4-be3a-c9db94e2593f" style="width: 300px; height: 300px;"></div>
    826  * <script type="text/javascript">
    827  *   var glex2_board = JXG.JSXGraph.initBoard('cf45e462-f964-4ba4-be3a-c9db94e2593f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    828  *   var glex2_l1 = glex2_board.create('line', [1.0, -2.0, 3.0]);
    829  * </script><pre>
    830  */
    831 JXG.createLine = function(board, parents, attributes) {
    832     var el, p1, p2, i, attr,
    833         c = [],
    834         constrained = false,
    835         isDraggable;
    836
    837     /**
    838      * The line is defined by two points or coordinates of two points.
    839      * In the latter case, the points are created.
    840      */
    841     if (parents.length == 2) {
    842         // point 1 given by coordinates
    843         if (JXG.isArray(parents[0]) && parents[0].length>1) {
    844             attr = JXG.copyAttributes(attributes, board.options, 'line', 'point1');
    845             p1 = board.create('point', parents[0], attr);
    846         } else if (JXG.isString(parents[0]) || parents[0].elementClass == JXG.OBJECT_CLASS_POINT) {
    847             p1 =  JXG.getReference(board,parents[0]);
    848         } else if ((typeof parents[0] == 'function') && (parents[0]().elementClass == JXG.OBJECT_CLASS_POINT)) {
    849             p1 = parents[0]();
    850             constrained = true;
    851         } else if ((typeof parents[0] == 'function') && (parents[0]().length && parents[0]().length === 2)) {
    852             attr = JXG.copyAttributes(attributes, board.options, 'line', 'point1');
    853             p1 = JXG.createPoint(board, parents[0](), attr);
    854             constrained = true;
    855         } else
    856             throw new Error("JSXGraph: Can't create line with parent types '" +
    857                             (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    858                             "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]");
    859
    860         // point 2 given by coordinates
    861         if (JXG.isArray(parents[1]) && parents[1].length>1) {
    862             attr = JXG.copyAttributes(attributes, board.options, 'line', 'point2');
    863             p2 = board.create('point', parents[1], attr);
    864         } else if (JXG.isString(parents[1]) || parents[1].elementClass == JXG.OBJECT_CLASS_POINT) {
    865             p2 =  JXG.getReference(board, parents[1]);
    866         } else if ((typeof parents[1] == 'function') && (parents[1]().elementClass == JXG.OBJECT_CLASS_POINT)) {
    867             p2 = parents[1]();
    868             constrained = true;
    869         } else if ((typeof parents[1] == 'function') && (parents[1]().length && parents[1]().length === 2)) {
    870             attr = JXG.copyAttributes(attributes, board.options, 'line', 'point2');
    871             p2 = JXG.createPoint(board, parents[1](), attr);
    872             constrained = true;
    873         } else
    874             throw new Error("JSXGraph: Can't create line with parent types '" +
    875                             (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    876                             "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]");
    877
    878         attr = JXG.copyAttributes(attributes, board.options, 'line');
    879
    880         el = new JXG.Line(board, p1, p2, attr);
    881         if (constrained) {
    882         	el.constrained = true;
    883         	el.funp1 = parents[0];
    884         	el.funp2 = parents[1];
    885         } else {
    886             el.isDraggable = true;
    887         }
    888
    889         if (!el.constrained) {
    890             el.parents = [p1.id, p2.id];
    891         }
    892     }
    893     /**
    894      * Line is defined by three homogeneous coordinates.
    895      * Also in this case points are created.
    896      */
    897     else if (parents.length==3) {
    898         // free line
    899         isDraggable = true;
    900         for (i=0;i<3;i++) {
    901             if (typeof parents[i]=='number') {
    902                 c[i] = function(z){ return function() { return z; }; }(parents[i]);
    903             } else if (typeof parents[i]=='function') {
    904                 c[i] = parents[i];
    905                 isDraggable = false;
    906             } else {
    907                 throw new Error("JSXGraph: Can't create line with parent types '" +
    908                     (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2])+ "'." +
    909                     "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]");
    910             }
    911         }
    912         // point 1 is the midpoint between (0,c,-b) and point 2. => point1 is finite.
    913         attr = JXG.copyAttributes(attributes, board.options, 'line', 'point1');
    914         if (isDraggable) {
    915             p1 = board.create('point',[c[2]()*c[2]()+c[1]()*c[1](), c[2]()-c[1]()*c[0]()+c[2](), -c[1]()-c[2]()*c[0]()-c[1]()], attr);
    916         } else {
    917             p1 = board.create('point',[
    918                 function() { return (0.0 + c[2]()*c[2]()+c[1]()*c[1]())*0.5;},
    919                 function() { return (c[2]() - c[1]()*c[0]()+c[2]())*0.5;},
    920                 function() { return (-c[1]() - c[2]()*c[0]()-c[1]())*0.5;}], attr);
    921         }
    922         /*
    923          */
    924         // point 2: (b^2+c^2,-ba+c,-ca-b)
    925         attr = JXG.copyAttributes(attributes, board.options, 'line', 'point2');
    926         if (isDraggable) {
    927             p2 = board.create('point',[c[2]()*c[2]()+c[1]()*c[1](), -c[1]()*c[0]()+c[2](), -c[2]()*c[0]()-c[1]()], attr);
    928         } else {
    929             p2 = board.create('point',[
    930                 function() { return c[2]()*c[2]()+c[1]()*c[1]();},
    931                 function() { return -c[1]()*c[0]()+c[2]();},
    932                 function() { return -c[2]()*c[0]()-c[1]();}], attr);
    933         }
    934
    935         // If the line will have a glider
    936         // and board.suspendUpdate() has been called, we
    937         // need to compute the initial position of the two points p1 and p2.
    938         p1.prepareUpdate().update();
    939         p2.prepareUpdate().update();
    940         attr = JXG.copyAttributes(attributes, board.options, 'line');
    941         el = new JXG.Line(board, p1, p2, attr);
    942         el.isDraggable = isDraggable;             // Not yet working, because the points are not draggable.
    943
    944         if (isDraggable) {
    945             el.parents = [c[0](), c[1](), c[2]()];
    946         }
    947     }
    948     /**
    949      * The parent array contains a function which returns two points.
    950      */
    951     else if ((parents.length==1) && (typeof parents[0] == 'function') && (parents[0]().length == 2) &&
    952     		 (parents[0]()[0].elementClass == JXG.OBJECT_CLASS_POINT) && (parents[0]()[1].elementClass == JXG.OBJECT_CLASS_POINT)) {
    953     	var ps = parents[0]();
    954         attr = JXG.copyAttributes(attributes, board.options, 'line');
    955         el = new JXG.Line(board, ps[0], ps[1], attr);
    956         el.constrained = true;
    957         el.funps = parents[0];
    958     } else if ((parents.length==1) && (typeof parents[0] == 'function') && (parents[0]().length == 3) &&
    959     		 (typeof parents[0]()[0] === 'number') && (typeof parents[0]()[1] === 'number') && (typeof parents[0]()[2] === 'number')) {
    960         ps = parents[0];
    961
    962         attr = JXG.copyAttributes(attributes, board.options, 'line', 'point1');
    963         p1 = board.create('point',[
    964             function () {
    965                 var c = ps();
    966                 return [
    967                     (0.0 + c[2]*c[2]+c[1]*c[1])*0.5,
    968                     (c[2] - c[1]*c[0]+c[2])*0.5,
    969                     (-c[1] - c[2]*c[0]-c[1])*0.5
    970                     ];
    971             }], attr);
    972
    973         attr = JXG.copyAttributes(attributes, board.options, 'line', 'point2');
    974         p2 = board.create('point',[
    975             function () {
    976                 var c = ps();
    977                 return [
    978                     c[2]*c[2]+c[1]*c[1],
    979                     -c[1]*c[0]+c[2],
    980                     -c[2]*c[0]-c[1]
    981                     ];
    982             }], attr);
    983
    984         attr = JXG.copyAttributes(attributes, board.options, 'line');
    985         el = new JXG.Line(board, p1, p2, attr);
    986
    987         el.constrained = true;
    988         el.funps = parents[0];
    989     } else {
    990         throw new Error("JSXGraph: Can't create line with parent types '" +
    991                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    992                         "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]");
    993     }
    994
    995     return el;
    996 };
    997
    998 JXG.JSXGraph.registerElement('line', JXG.createLine);
    999
    1000 /**
    1001  * @class This element is used to provide a constructor for a segment.
    1002  * It's strictly spoken just a wrapper for element {@link Line} with {@link JXG.Line#straightFirst}
    1003  * and {@link JXG.Line#straightLast} properties set to false. If there is a third variable then the
    1004  * segment has a fixed length (which may be a function, too).
    1005  * @pseudo
    1006  * @description
    1007  * @name Segment
    1008  * @augments JXG.Line
    1009  * @constructor
    1010  * @type JXG.Line
    1011  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1012  * @param {JXG.Point,array_JXG.Point,array} point1, point2 Parent elements can be two elements either of type {@link JXG.Point}
    1013  * or array of numbers describing the
    1014  * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
    1015   * @param {number,function} length (optional) The points are adapted - if possible - such that their distance
    1016   * has a this value.
    1017 * @see Line
    1018  * @example
    1019  * // Create a segment providing two points.
    1020  *   var p1 = board.create('point', [4.5, 2.0]);
    1021  *   var p2 = board.create('point', [1.0, 1.0]);
    1022  *   var l1 = board.create('segment', [p1, p2]);
    1023  * </pre><div id="d70e6aac-7c93-4525-a94c-a1820fa38e2f" style="width: 300px; height: 300px;"></div>
    1024  * <script type="text/javascript">
    1025  *   var slex1_board = JXG.JSXGraph.initBoard('d70e6aac-7c93-4525-a94c-a1820fa38e2f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    1026  *   var slex1_p1 = slex1_board.create('point', [4.5, 2.0]);
    1027  *   var slex1_p2 = slex1_board.create('point', [1.0, 1.0]);
    1028  *   var slex1_l1 = slex1_board.create('segment', [slex1_p1, slex1_p2]);
    1029  * </script><pre>
    1030  *
    1031  * @example
    1032  * // Create a segment providing two points.
    1033  *   var p1 = board.create('point', [4.0, 1.0]);
    1034  *   var p2 = board.create('point', [1.0, 1.0]);
    1035  *   var l1 = board.create('segment', [p1, p2]);
    1036  *   var p3 = board.create('point', [4.0, 2.0]);
    1037  *   var p4 = board.create('point', [1.0, 2.0]);
    1038  *   var l2 = board.create('segment', [p3, p4, 3]);
    1039  *   var p5 = board.create('point', [4.0, 3.0]);
    1040  *   var p6 = board.create('point', [1.0, 4.0]);
    1041  *   var l3 = board.create('segment', [p5, p6, function(){ return l1.L();} ]);
    1042  * </pre><div id="617336ba-0705-4b2b-a236-c87c28ef25be" style="width: 300px; height: 300px;"></div>
    1043  * <script type="text/javascript">
    1044  *   var slex2_board = JXG.JSXGraph.initBoard('617336ba-0705-4b2b-a236-c87c28ef25be', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    1045  *   var slex2_p1 = slex1_board.create('point', [4.0, 1.0]);
    1046  *   var slex2_p2 = slex1_board.create('point', [1.0, 1.0]);
    1047  *   var slex2_l1 = slex1_board.create('segment', [slex1_p1, slex1_p2]);
    1048  *   var slex2_p3 = slex1_board.create('point', [4.0, 2.0]);
    1049  *   var slex2_p4 = slex1_board.create('point', [1.0, 2.0]);
    1050  *   var slex2_l2 = slex1_board.create('segment', [slex1_p3, slex1_p4, 3]);
    1051  *   var slex2_p5 = slex1_board.create('point', [4.0, 2.0]);
    1052  *   var slex2_p6 = slex1_board.create('point', [1.0, 2.0]);
    1053  *   var slex2_l3 = slex1_board.create('segment', [slex1_p5, slex1_p6, function(){ return slex2_l1.L();}]);
    1054  * </script><pre>
    1055  *
    1056  */
    1057 JXG.createSegment = function(board, parents, attributes) {
    1058     var el, i;
    1059
    1060     attributes.straightFirst = false;
    1061     attributes.straightLast = false;
    1062     el = board.create('line', parents.slice(0,2), attributes);
    1063
    1064     if (parents.length==3) {
    1065         el.hasFixedLength = true;
    1066         if (JXG.isNumber(parents[2])) {
    1067             el.fixedLength = function() { return parents[2]; };
    1068         } else if (JXG.isFunction(parents[2])) {
    1069             el.fixedLength = parents[2];
    1070         }   else {
    1071             throw new Error("JSXGraph: Can't create segment with third parent type '" +
    1072                         (typeof parents[2]) + "'." +
    1073                         "\nPossible third parent types: number or function");
    1074         }
    1075         el.fixedLengthOldCoords = [];
    1076         el.fixedLengthOldCoords[0] = new JXG.Coords(JXG.COORDS_BY_USER, el.point1.coords.usrCoords.slice(1,3), board);
    1077         el.fixedLengthOldCoords[1] = new JXG.Coords(JXG.COORDS_BY_USER, el.point2.coords.usrCoords.slice(1,3), board);
    1078     }
    1079
    1080     el.elType = 'segment';
    1081
    1082     return el;
    1083 };
    1084
    1085 JXG.JSXGraph.registerElement('segment', JXG.createSegment);
    1086
    1087 /**
    1088  * @class This element is used to provide a constructor for arrow, which is just a wrapper for element {@link Line} with {@link JXG.Line#straightFirst}
    1089  * and {@link JXG.Line#straightLast} properties set to false and {@link JXG.Line#lastArrow} set to true.
    1090  * @pseudo
    1091  * @description
    1092  * @name Arrow
    1093  * @augments JXG.Line
    1094  * @constructor
    1095  * @type JXG.Line
    1096  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1097  * @param {JXG.Point,array_JXG.Point,array} point1,point2 Parent elements can be two elements either of type {@link JXG.Point} or array of numbers describing the
    1098  * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
    1099  * @param {Number_Number_Number} a,b,c A line can also be created providing three numbers. The line is then described by the set of solutions
    1100  * of the equation <tt>a*x+b*y+c*z = 0</tt>.
    1101  * @see Line
    1102  * @example
    1103  * // Create an arrow providing two points.
    1104  *   var p1 = board.create('point', [4.5, 2.0]);
    1105  *   var p2 = board.create('point', [1.0, 1.0]);
    1106  *   var l1 = board.create('arrow', [p1, p2]);
    1107  * </pre><div id="1d26bd22-7d6d-4018-b164-4c8bc8d22ccf" style="width: 300px; height: 300px;"></div>
    1108  * <script type="text/javascript">
    1109  *   var alex1_board = JXG.JSXGraph.initBoard('1d26bd22-7d6d-4018-b164-4c8bc8d22ccf', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    1110  *   var alex1_p1 = alex1_board.create('point', [4.5, 2.0]);
    1111  *   var alex1_p2 = alex1_board.create('point', [1.0, 1.0]);
    1112  *   var alex1_l1 = alex1_board.create('arrow', [alex1_p1, alex1_p2]);
    1113  * </script><pre>
    1114  */
    1115 JXG.createArrow = function(board, parents, attributes) {
    1116     var el;
    1117
    1118     el = board.create('line', parents, attributes).setStraight(false, false);
    1119     el.setArrow(false, true);
    1120     el.type = JXG.OBJECT_TYPE_VECTOR;
    1121
    1122     el.elType = 'arrow';
    1123
    1124     return el;
    1125 };
    1126
    1127 JXG.JSXGraph.registerElement('arrow', JXG.createArrow);
    1128
    1129 /**
    1130  * @class This element is used to provide a constructor for an axis. It's strictly spoken just a wrapper for element {@link Line} with {@link JXG.Line#straightFirst}
    1131  * and {@link JXG.Line#straightLast} properties set to true. Additionally {@link JXG.Line#lastArrow} is set to true and default {@link Ticks} will be created.
    1132  * @pseudo
    1133  * @description
    1134  * @name Axis
    1135  * @augments JXG.Line
    1136  * @constructor
    1137  * @type JXG.Line
    1138  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1139  * @param {JXG.Point,array_JXG.Point,array} point1,point2 Parent elements can be two elements either of type {@link JXG.Point} or array of numbers describing the
    1140  * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
    1141  * @param {Number_Number_Number} a,b,c A line can also be created providing three numbers. The line is then described by the set of solutions
    1142  * of the equation <tt>a*x+b*y+c*z = 0</tt>.
    1143  * @example
    1144  * // Create an axis providing two coord pairs.
    1145  *   var l1 = board.create('axis', [[0.0, 1.0], [1.0, 1.3]]);
    1146  * </pre><div id="4f414733-624c-42e4-855c-11f5530383ae" style="width: 300px; height: 300px;"></div>
    1147  * <script type="text/javascript">
    1148  *   var axex1_board = JXG.JSXGraph.initBoard('4f414733-624c-42e4-855c-11f5530383ae', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    1149  *   var axex1_l1 = axex1_board.create('axis', [[0.0, 1.0], [1.0, 1.3]]);
    1150  * </script><pre>
    1151  */
    1152 JXG.createAxis = function(board, parents, attributes) {
    1153     var attr,
    1154         el,
    1155         dist;
    1156
    1157     // Arrays oder Punkte, mehr brauchen wir nicht.
    1158     if ( (JXG.isArray(parents[0]) || JXG.isPoint(parents[0]) ) && (JXG.isArray(parents[1]) || JXG.isPoint(parents[1])) ) {
    1159         attr = JXG.copyAttributes(attributes, board.options, 'axis');
    1160         el = board.create('line', parents, attr);
    1161         el.type = JXG.OBJECT_TYPE_AXIS;
    1162         el.isDraggable = false;
    1163         el.point1.isDraggable = false;
    1164         el.point2.isDraggable = false;
    1165
    1166         for (var els in el.ancestors)
    1167             el.ancestors[els].type = JXG.OBJECT_TYPE_AXISPOINT;
    1168
    1169         attr = JXG.copyAttributes(attributes, board.options, 'axis', 'ticks');
    1170         if (JXG.exists(attr.ticksdistance)) {
    1171             dist = attr.ticksdistance;
    1172         } else if(JXG.isArray(attr.ticks)) {
    1173             dist = attr.ticks;
    1174         } else {
    1175             dist = 1.0;
    1176         }
    1177
    1178         /**
    1179          * The ticks attached to the axis.
    1180          * @memberOf Axis.prototype
    1181          * @name defaultTicks
    1182          * @type JXG.Ticks
    1183          */
    1184         el.defaultTicks = board.create('ticks', [el, dist], attr);
    1185
    1186         el.defaultTicks.dump = false;
    1187
    1188         el.elType = 'axis';
    1189         el.subs = {
    1190             ticks: el.defaultTicks
    1191         };
    1192     }
    1193     else
    1194         throw new Error("JSXGraph: Can't create point with parent types '" +
    1195                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    1196                         "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]");
    1197
    1198     return el;
    1199 };
    1200
    1201 JXG.JSXGraph.registerElement('axis', JXG.createAxis);
    1202
    1203 /**
    1204  * @class With the element tangent the slope of a line, circle, or curve in a certain point can be visualized. A tangent is always constructed
    1205  * by a glider on a line, circle, or curve and describes the tangent in the glider point on that line, circle, or curve.
    1206  * @pseudo
    1207  * @description
    1208  * @name Tangent
    1209  * @augments JXG.Line
    1210  * @constructor
    1211  * @type JXG.Line
    1212  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1213  * @param {Glider} g A glider on a line, circle, or curve.
    1214  * @example
    1215  * // Create a tangent providing a glider on a function graph
    1216  *   var c1 = board.create('curve', [function(t){return t},function(t){return t*t*t;}]);
    1217  *   var g1 = board.create('glider', [0.6, 1.2, c1]);
    1218  *   var t1 = board.create('tangent', [g1]);
    1219  * </pre><div id="7b7233a0-f363-47dd-9df5-4018d0d17a98" style="width: 400px; height: 400px;"></div>
    1220  * <script type="text/javascript">
    1221  *   var tlex1_board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-4018d0d17a98', {boundingbox: [-6, 6, 6, -6], axis: true, showcopyright: false, shownavigation: false});
    1222  *   var tlex1_c1 = tlex1_board.create('curve', [function(t){return t},function(t){return t*t*t;}]);
    1223  *   var tlex1_g1 = tlex1_board.create('glider', [0.6, 1.2, tlex1_c1]);
    1224  *   var tlex1_t1 = tlex1_board.create('tangent', [tlex1_g1]);
    1225  * </script><pre>
    1226  */
    1227 JXG.createTangent = function(board, parents, attributes) {
    1228     var p,
    1229         c,
    1230         g, f, i, j, el, tangent;
    1231
    1232     if (parents.length==1) { // One arguments: glider on line, circle or curve
    1233         p = parents[0];
    1234         c = p.slideObject;
    1235     } else if (parents.length==2) {     // Two arguments: (point,F"|conic) or (line|curve|circle|conic,point). // Not yet: curve!
    1236         if (JXG.isPoint(parents[0])) {  // In fact, for circles and conics it is the polar.
    1237             p = parents[0];
    1238             c = parents[1];
    1239         } else if (JXG.isPoint(parents[1])) {
    1240             c = parents[0];
    1241             p = parents[1];
    1242         } else {
    1243             throw new Error("JSXGraph: Can't create tangent with parent types '" +
    1244                             (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    1245                             "\nPossible parent types: [glider], [point,line|curve|circle|conic]");
    1246         }
    1247     } else {
    1248         throw new Error("JSXGraph: Can't create tangent with parent types '" +
    1249                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    1250                         "\nPossible parent types: [glider], [point,line|curve|circle|conic]");
    1251     }
    1252
    1253     if (c.elementClass == JXG.OBJECT_CLASS_LINE) {
    1254         tangent = board.create('line', [c.point1,c.point2], attributes);
    1255     } else if (c.elementClass == JXG.OBJECT_CLASS_CURVE && !(c.type == JXG.OBJECT_TYPE_CONIC)) {
    1256         if (c.visProp.curvetype!='plot') {
    1257             g = c.X;
    1258             f = c.Y;
    1259             tangent = board.create('line', [
    1260                     function(){ return -p.X()*board.D(f)(p.position)+p.Y()*board.D(g)(p.position);},
    1261                     function(){ return board.D(f)(p.position);},
    1262                     function(){ return -board.D(g)(p.position);}
    1263                     ], attributes );
    1264             p.addChild(tangent);
    1265             // this is required for the geogebra reader to display a slope
    1266             tangent.glider = p;
    1267         } else {  // curveType 'plot'
    1268             // equation of the line segment: 0 = y*(x1-x2) + x*(y2-y1) + y1*x2-x1*y2
    1269             tangent = board.create('line', [
    1270                     function(){ i=Math.floor(p.position);
    1271                                 if (i==c.numberPoints-1) i--;
    1272                                 if (i<0) return 1.0;
    1273                                 return c.Y(i)*c.X(i+1)-c.X(i)*c.Y(i+1);},
    1274                     function(){ i=Math.floor(p.position);
    1275                                 if (i==c.numberPoints-1) i--;
    1276                                 if (i<0) return 0.0;
    1277                                 return c.Y(i+1)-c.Y(i);},
    1278                     function(){ i=Math.floor(p.position);
    1279                                 if (i==c.numberPoints-1) i--;
    1280                                 if (i<0) return 0.0;
    1281                                 return c.X(i)-c.X(i+1);}
    1282                     ], attributes );
    1283             p.addChild(tangent);
    1284             // this is required for the geogebra reader to display a slope
    1285             tangent.glider = p;
    1286         }
    1287     } else if (c.type == JXG.OBJECT_TYPE_TURTLE) {
    1288             tangent = board.create('line', [
    1289                     function(){ i=Math.floor(p.position);
    1290                                 for(j=0;j<c.objects.length;j++) {  // run through all curves of this turtle
    1291                                     el = c.objects[j];
    1292                                     if (el.type==JXG.OBJECT_TYPE_CURVE) {
    1293                                         if (i<el.numberPoints) break;
    1294                                         i-=el.numberPoints;
    1295                                     }
    1296                                 }
    1297                                 if (i==el.numberPoints-1) i--;
    1298                                 if (i<0) return 1.0;
    1299                                 return el.Y(i)*el.X(i+1)-el.X(i)*el.Y(i+1);},
    1300                     function(){ i=Math.floor(p.position);
    1301                                 for(j=0;j<c.objects.length;j++) {  // run through all curves of this turtle
    1302                                     el = c.objects[j];
    1303                                     if (el.type==JXG.OBJECT_TYPE_CURVE) {
    1304                                         if (i<el.numberPoints) break;
    1305                                         i-=el.numberPoints;moveTo(funps);
    1306                                     }
    1307                                 }
    1308                                 if (i==el.numberPoints-1) i--;
    1309                                 if (i<0) return 0.0;
    1310                                 return el.Y(i+1)-el.Y(i);},
    1311                     function(){ i=Math.floor(p.position);
    1312                                 for(j=0;j<c.objects.length;j++) {  // run through all curves of this turtle
    1313                                     el = c.objects[j];
    1314                                     if (el.type==JXG.OBJECT_TYPE_CURVE) {
    1315                                         if (i<el.numberPoints) break;
    1316                                         i-=el.numberPoints;
    1317                                     }
    1318                                 }
    1319                                 if (i==el.numberPoints-1) i--;
    1320                                 if (i<0) return 0.0;
    1321                                 return el.X(i)-el.X(i+1);}
    1322                     ], attributes );
    1323             p.addChild(tangent);
    1324             // this is required for the geogebra reader to display a slope
    1325             tangent.glider = p;
    1326     } else if (c.elementClass == JXG.OBJECT_CLASS_CIRCLE || c.type == JXG.OBJECT_TYPE_CONIC) {
    1327         // If p is not on c, the tangent is the polar.
    1328         // This construction should work on conics, too. p has to lie on c.
    1329         tangent = board.create('line', [
    1330                     function(){ return JXG.Math.matVecMult(c.quadraticform,p.coords.usrCoords)[0]; },
    1331                     function(){ return JXG.Math.matVecMult(c.quadraticform,p.coords.usrCoords)[1]; },
    1332                     function(){ return JXG.Math.matVecMult(c.quadraticform,p.coords.usrCoords)[2]; }
    1333                 ], attributes);
    1334
    1335         p.addChild(tangent);
    1336         // this is required for the geogebra reader to display a slope
    1337         tangent.glider = p;
    1338     }
    1339
    1340     tangent.elType = 'tangent';
    1341     tangent.parents = [];
    1342     for (i = 0; i < parents.length; i++) {
    1343         tangent.parents.push(parents[i].id);
    1344     }
    1345
    1346     return tangent;
    1347 };
    1348
    1349 /**
    1350  * Register the element type tangent at JSXGraph
    1351  * @private
    1352  */
    1353 JXG.JSXGraph.registerElement('tangent', JXG.createTangent);
    1354 JXG.JSXGraph.registerElement('polar', JXG.createTangent);
    1355 // vim: et ts=4
    1356