      1 /*
      2     Copyright 2008-2017
      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 dual licensed under the GNU LGPL or MIT License.
     13
     14     You can redistribute it and/or modify it under the terms of the
     15
     16       * GNU Lesser General Public License as published by
     17         the Free Software Foundation, either version 3 of the License, or
     18         (at your option) any later version
     19       OR
     20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
     21
     22     JSXGraph is distributed in the hope that it will be useful,
     23     but WITHOUT ANY WARRANTY; without even the implied warranty of
     24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     25     GNU Lesser General Public License for more details.
     26
     27     You should have received a copy of the GNU Lesser General Public License and
     28     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
     29     and <http://opensource.org/licenses/MIT/>.
     30  */
     31
     32
     33 /*global JXG: true, define: true*/
     34 /*jslint nomen: true, plusplus: true*/
     35
     36 /* depends:
     37  jxg
     38  math/math
     39  math/geometry
     40  math/numerics
     41  math/statistics
     42  base/constants
     43  base/coords
     44  base/element
     45  utils/type
     46   elements:
     47    transform
     48    point
     49    ticks
     50  */
     51
     52 /**
     53  * @fileoverview The geometry object Line is defined in this file. Line stores all
     54  * style and functional properties that are required to draw and move a line on
     55  * a board.
     56  */
     57
     58 define([
     59     'jxg', 'math/math', 'math/geometry', 'math/numerics', 'math/statistics', 'base/constants', 'base/coords',
     60     'base/element', 'utils/type', 'base/point'
     61 ], function (JXG, Mat, Geometry, Numerics, Statistics, Const, Coords, GeometryElement, Type, Point) {
     62
     63     "use strict";
     64
     65     /**
     66      * 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
     67      * be intersected with some other geometry elements.
     68      * @class Creates a new basic line object. Do not use this constructor to create a line.
     69      * Use {@link JXG.Board#create} with
     70      * type {@link Line}, {@link Arrow}, or {@link Axis} instead.
     71      * @constructor
     72      * @augments JXG.GeometryElement
     73      * @param {String,JXG.Board} board The board the new line is drawn on.
     74      * @param {Point} p1 Startpoint of the line.
     75      * @param {Point} p2 Endpoint of the line.
     76      * @param {String} id Unique identifier for this object. If null or an empty string is given,
     77      * an unique id will be generated by Board
     78      * @param {String} name Not necessarily unique name. If null or an
     79      * empty string is given, an unique name will be generated.
     80      * @param {Boolean} withLabel construct label, yes/no
     81      * @param {Number} layer display layer [0-9]
     82      * @see JXG.Board#generateName
     83      */
     84     JXG.Line = function (board, p1, p2, attributes) {
     85         this.constructor(board, attributes, Const.OBJECT_TYPE_LINE, Const.OBJECT_CLASS_LINE);
     86
     87         /**
     88          * Startpoint of the line. You really should not set this field directly as it may break JSXGraph's
     89          * udpate system so your construction won't be updated properly.
     90          * @type JXG.Point
     91          */
     92         this.point1 = this.board.select(p1);
     93
     94         /**
     95          * Endpoint of the line. Just like {@link JXG.Line.point1} you shouldn't write this field directly.
     96          * @type JXG.Point
     97          */
     98         this.point2 = this.board.select(p2);
     99
    100         /**
    101          * Array of ticks storing all the ticks on this line. Do not set this field directly and use
    102          * {@link JXG.Line#addTicks} and {@link JXG.Line#removeTicks} to add and remove ticks to and from the line.
    103          * @type Array
    104          * @see JXG.Ticks
    105          */
    106         this.ticks = [];
    107
    108         /**
    109          * Reference of the ticks created automatically when constructing an axis.
    110          * @type JXG.Ticks
    111          * @see JXG.Ticks
    112          */
    113         this.defaultTicks = null;
    114
    115         /**
    116          * If the line is the border of a polygon, the polygon object is stored, otherwise null.
    117          * @type JXG.Polygon
    118          * @default null
    119          * @private
    120          */
    121         this.parentPolygon = null;
    122
    123         /* Register line at board */
    124         this.id = this.board.setId(this, 'L');
    125         this.board.renderer.drawLine(this);
    126         this.board.finalizeAdding(this);
    127
    128         this.elType = 'line';
    129
    130         /* Add arrow as child to defining points */
    131         this.point1.addChild(this);
    132         this.point2.addChild(this);
    133
    134         this.inherits.push(this.point1, this.point2);
    135
    136         this.updateStdform(); // This is needed in the following situation:
    137         // * the line is defined by three coordinates
    138         // * and it will have a glider
    139         // * and board.suspendUpdate() has been called.
    140
    141         // create Label
    142         this.createLabel();
    143
    144         this.methodMap = JXG.deepCopy(this.methodMap, {
    145             point1: 'point1',
    146             point2: 'point2',
    147             getSlope: 'getSlope',
    148             getRise: 'getRise',
    149             getYIntersect: 'getRise',
    150             getAngle: 'getAngle',
    151             L: 'L',
    152             length: 'L',
    153             addTicks: 'addTicks',
    154             removeTicks: 'removeTicks',
    155             removeAllTicks: 'removeAllTicks'
    156         });
    157     };
    158
    159     JXG.Line.prototype = new GeometryElement();
    160
    161     JXG.extend(JXG.Line.prototype, /** @lends JXG.Line.prototype */ {
    162         /**
    163          * Checks whether (x,y) is near the line.
    164          * @param {Number} x Coordinate in x direction, screen coordinates.
    165          * @param {Number} y Coordinate in y direction, screen coordinates.
    166          * @returns {Boolean} True if (x,y) is near the line, False otherwise.
    167          */
    168         hasPoint: function (x, y) {
    169             // Compute the stdform of the line in screen coordinates.
    170             var c = [], s,
    171                 v = [1, x, y],
    172                 vnew,
    173                 p1c, p2c, d, pos, i,
    174                 prec,
    175                 sw = Type.evaluate(this.visProp.strokewidth);
    176
    177             prec = this.board.options.precision.hasPoint + sw * 0.5;
    178
    179             c[0] = this.stdform[0] -
    180                 this.stdform[1] * this.board.origin.scrCoords[1] / this.board.unitX +
    181                 this.stdform[2] * this.board.origin.scrCoords[2] / this.board.unitY;
    182             c[1] = this.stdform[1] / this.board.unitX;
    183             c[2] = this.stdform[2] / (-this.board.unitY);
    184
    185             s = Geometry.distPointLine(v, c);
    186             if (isNaN(s) || s > prec) {
    187                 return false;
    188             }
    189
    190             if (Type.evaluate(this.visProp.straightfirs) &&
    191                     Type.evaluate(this.visProp.straightlast)) {
    192                 return true;
    193             }
    194
    195             // If the line is a ray or segment we have to check if the projected point is between P1 and P2.
    196             p1c = this.point1.coords;
    197             p2c = this.point2.coords;
    198
    199             // Project the point orthogonally onto the line
    200             vnew = [0, c[1], c[2]];
    201             // Orthogonal line to c through v
    202             vnew = Mat.crossProduct(vnew, v);
    203             // Intersect orthogonal line with line
    204             vnew = Mat.crossProduct(vnew, c);
    205
    206             // Normalize the projected point
    207             vnew[1] /= vnew[0];
    208             vnew[2] /= vnew[0];
    209             vnew[0] = 1;
    210
    211             vnew = (new Coords(Const.COORDS_BY_SCREEN, vnew.slice(1), this.board)).usrCoords;
    212             d = p1c.distance(Const.COORDS_BY_USER, p2c);
    213             p1c = p1c.usrCoords.slice(0);
    214             p2c = p2c.usrCoords.slice(0);
    215
    216             // The defining points are identical
    217             if (d < Mat.eps) {
    218                 pos = 0;
    219             } else {
    220                 /*
    221                  * Handle the cases, where one of the defining points is an ideal point.
    222                  * d is set to something close to infinity, namely 1/eps.
    223                  * The ideal point is (temporarily) replaced by a finite point which has
    224                  * distance d from the other point.
    225                  * This is accomplishrd by extracting the x- and y-coordinates (x,y)=:v of the ideal point.
    226                  * v determines the direction of the line. v is normalized, i.e. set to length 1 by deividing through its length.
    227                  * Finally, the new point is the sum of the other point and v*d.
    228                  *
    229                  */
    230
    231                 // At least one point is an ideal point
    232                 if (d === Number.POSITIVE_INFINITY) {
    233                     d = 1 / Mat.eps;
    234
    235                     // The second point is an ideal point
    236                     if (Math.abs(p2c[0]) < Mat.eps) {
    237                         d /= Geometry.distance([0, 0, 0], p2c);
    238                         p2c = [1, p1c[1] + p2c[1] * d, p1c[2] + p2c[2] * d];
    239                     // The first point is an ideal point
    240                     } else {
    241                         d /= Geometry.distance([0, 0, 0], p1c);
    242                         p1c = [1, p2c[1] + p1c[1] * d, p2c[2] + p1c[2] * d];
    243                     }
    244                 }
    245                 i = 1;
    246                 d = p2c[i] - p1c[i];
    247
    248                 if (Math.abs(d) < Mat.eps) {
    249                     i = 2;
    250                     d = p2c[i] - p1c[i];
    251                 }
    252                 pos = (vnew[i] - p1c[i]) / d;
    253             }
    254
    255             if (!Type.evaluate(this.visProp.straightfirst) && pos < 0) {
    256                 return false;
    257             }
    258
    259             return !(!Type.evaluate(this.visProp.straightlast) && pos > 1);
    260
    261         },
    262
    263         // documented in base/element
    264         update: function () {
    265             var funps;
    266
    267             if (!this.needsUpdate) {
    268                 return this;
    269             }
    270
    271             if (this.constrained) {
    272                 if (Type.isFunction(this.funps)) {
    273                     funps = this.funps();
    274                     if (funps && funps.length && funps.length === 2) {
    275                         this.point1 = funps[0];
    276                         this.point2 = funps[1];
    277                     }
    278                 } else {
    279                     if (Type.isFunction(this.funp1)) {
    280                         funps = this.funp1();
    281                         if (Type.isPoint(funps)) {
    282                             this.point1 = funps;
    283                         } else if (funps && funps.length && funps.length === 2) {
    284                             this.point1.setPositionDirectly(Const.COORDS_BY_USER, funps);
    285                         }
    286                     }
    287
    288                     if (Type.isFunction(this.funp2)) {
    289                         funps = this.funp2();
    290                         if (Type.isPoint(funps)) {
    291                             this.point2 = funps;
    292                         } else if (funps && funps.length && funps.length === 2) {
    293                             this.point2.setPositionDirectly(Const.COORDS_BY_USER, funps);
    294                         }
    295                     }
    296                 }
    297             }
    298
    299             this.updateSegmentFixedLength();
    300             this.updateStdform();
    301
    302             if (Type.evaluate(this.visProp.trace)) {
    303                 this.cloneToBackground(true);
    304             }
    305
    306             return this;
    307         },
    308
    309         /**
    310          * Update segments with fixed length and at least one movable point.
    311          * @private
    312          */
    313         updateSegmentFixedLength: function () {
    314             var d, dnew, d1, d2, drag1, drag2, x, y;
    315
    316             if (!this.hasFixedLength) {
    317                 return this;
    318             }
    319
    320             // Compute the actual length of the segment
    321             d = this.point1.Dist(this.point2);
    322             // Determine the length the segment ought to have
    323             dnew = this.fixedLength();
    324             // Distances between the two points and their respective
    325             // position before the update
    326             d1 = this.fixedLengthOldCoords[0].distance(Const.COORDS_BY_USER, this.point1.coords);
    327             d2 = this.fixedLengthOldCoords[1].distance(Const.COORDS_BY_USER, this.point2.coords);
    328
    329             // If the position of the points or the fixed length function has been changed we have to work.
    330             if (d1 > Mat.eps || d2 > Mat.eps || d !== dnew) {
    331                 drag1 = this.point1.isDraggable &&
    332                             (this.point1.type !== Const.OBJECT_TYPE_GLIDER) &&
    333                             !Type.evaluate(this.point1.visProp.fixed);
    334                 drag2 = this.point2.isDraggable &&
    335                             (this.point2.type !== Const.OBJECT_TYPE_GLIDER) &&
    336                             !Type.evaluate(this.point2.visProp.fixed);
    337
    338                 // First case: the two points are different
    339                 // Then we try to adapt the point that was not dragged
    340                 // If this point can not be moved (e.g. because it is a glider)
    341                 // we try move the other point
    342                 if (d > Mat.eps) {
    343                     if ((d1 > d2 && drag2) ||
    344                             (d1 <= d2 && drag2 && !drag1)) {
    345                         this.point2.setPositionDirectly(Const.COORDS_BY_USER, [
    346                             this.point1.X() + (this.point2.X() - this.point1.X()) * dnew / d,
    347                             this.point1.Y() + (this.point2.Y() - this.point1.Y()) * dnew / d
    348                         ]);
    349                         this.point2.fullUpdate();
    350                     } else if ((d1 <= d2 && drag1) ||
    351                             (d1 > d2 && drag1 && !drag2)) {
    352                         this.point1.setPositionDirectly(Const.COORDS_BY_USER, [
    353                             this.point2.X() + (this.point1.X() - this.point2.X()) * dnew / d,
    354                             this.point2.Y() + (this.point1.Y() - this.point2.Y()) * dnew / d
    355                         ]);
    356                         this.point1.fullUpdate();
    357                     }
    358                     // Second case: the two points are identical. In this situation
    359                     // we choose a random direction.
    360                 } else {
    361                     x = Math.random() - 0.5;
    362                     y = Math.random() - 0.5;
    363                     d = Math.sqrt(x * x + y * y);
    364
    365                     if (drag2) {
    366                         this.point2.setPositionDirectly(Const.COORDS_BY_USER, [
    367                             this.point1.X() + x * dnew / d,
    368                             this.point1.Y() + y * dnew / d
    369                         ]);
    370                         this.point2.fullUpdate();
    371                     } else if (drag1) {
    372                         this.point1.setPositionDirectly(Const.COORDS_BY_USER, [
    373                             this.point2.X() + x * dnew / d,
    374                             this.point2.Y() + y * dnew / d
    375                         ]);
    376                         this.point1.fullUpdate();
    377                     }
    378                 }
    379                 // Finally, we save the position of the two points.
    380                 this.fixedLengthOldCoords[0].setCoordinates(Const.COORDS_BY_USER, this.point1.coords.usrCoords);
    381                 this.fixedLengthOldCoords[1].setCoordinates(Const.COORDS_BY_USER, this.point2.coords.usrCoords);
    382             }
    383             return this;
    384         },
    385
    386         /**
    387          * Updates the stdform derived from the parent point positions.
    388          * @private
    389          */
    390         updateStdform: function () {
    391             var v = Mat.crossProduct(this.point1.coords.usrCoords, this.point2.coords.usrCoords);
    392
    393             this.stdform[0] = v[0];
    394             this.stdform[1] = v[1];
    395             this.stdform[2] = v[2];
    396             this.stdform[3] = 0;
    397
    398             this.normalize();
    399         },
    400
    401         /**
    402          * Uses the boards renderer to update the line.
    403          * @private
    404          */
    405         updateRenderer: function () {
    406             var wasReal;
    407
    408             if (!this.needsUpdate) {
    409                 return this;
    410             }
    411
    412             if (this.visPropCalc.visible) {
    413                 wasReal = this.isReal;
    414                 this.isReal = (!isNaN(this.point1.coords.usrCoords[1] + this.point1.coords.usrCoords[2] +
    415                         this.point2.coords.usrCoords[1] + this.point2.coords.usrCoords[2]) &&
    416                         (Mat.innerProduct(this.stdform, this.stdform, 3) >= Mat.eps * Mat.eps));
    417
    418                 if (wasReal && !this.isReal) {
    419                     this.updateVisibility(false);
    420                 }
    421             }
    422
    423             if (this.visPropCalc.visible) {
    424                 this.board.renderer.updateLine(this);
    425             }
    426
    427             /* Update the label if visible. */
    428             if (this.hasLabel && this.visPropCalc.visible && this.label &&
    429                 this.label.visPropCalc.visible && this.isReal) {
    430
    431                 this.label.update();
    432                 this.board.renderer.updateText(this.label);
    433             }
    434
    435             // Update rendNode display
    436             this.setDisplayRendNode();
    437             // if (this.visPropCalc.visible !== this.visPropOld.visible) {
    438             //     this.setDisplayRendNode(this.visPropCalc.visible);
    439             //     if (this.hasLabel) {
    440             //         this.board.renderer.display(this.label, this.label.visPropCalc.visible);
    441             //     }
    442             // }
    443
    444             this.needsUpdate = false;
    445             return this;
    446         },
    447
    448         /**
    449          * Used to generate a polynomial for a point p that lies on this line, i.e. p is collinear to
    450          * {@link JXG.Line#point1} and {@link JXG.Line#point2}.
    451          *
    452          * @param {JXG.Point} p The point for that the polynomial is generated.
    453          * @returns {Array} An array containing the generated polynomial.
    454          * @private
    455          */
    456         generatePolynomial: function (p) {
    457             var u1 = this.point1.symbolic.x,
    458                 u2 = this.point1.symbolic.y,
    459                 v1 = this.point2.symbolic.x,
    460                 v2 = this.point2.symbolic.y,
    461                 w1 = p.symbolic.x,
    462                 w2 = p.symbolic.y;
    463
    464             /*
    465              * The polynomial in this case is determined by three points being collinear:
    466              *
    467              *      U (u1,u2)      W (w1,w2)                V (v1,v2)
    468              *  ----x--------------x------------------------x----------------
    469              *
    470              *  The collinearity condition is
    471              *
    472              *      u2-w2       w2-v2
    473              *     -------  =  -------           (1)
    474              *      u1-w1       w1-v1
    475              *
    476              * Multiplying (1) with denominators and simplifying is
    477              *
    478              *    u2w1 - u2v1 + w2v1 - u1w2 + u1v2 - w1v2 = 0
    479              */
    480
    481             return [['(', u2, ')*(', w1, ')-(', u2, ')*(', v1, ')+(', w2, ')*(', v1, ')-(', u1, ')*(', w2, ')+(', u1, ')*(', v2, ')-(', w1, ')*(', v2, ')'].join('')];
    482         },
    483
    484         /**
    485          * Calculates the y intersect of the line.
    486          * @returns {Number} The y intersect.
    487          */
    488         getRise: function () {
    489             if (Math.abs(this.stdform[2]) >= Mat.eps) {
    490                 return -this.stdform[0] / this.stdform[2];
    491             }
    492
    493             return Infinity;
    494         },
    495
    496         /**
    497          * Calculates the slope of the line.
    498          * @returns {Number} The slope of the line or Infinity if the line is parallel to the y-axis.
    499          */
    500         getSlope: function () {
    501             if (Math.abs(this.stdform[2]) >= Mat.eps) {
    502                 return -this.stdform[1] / this.stdform[2];
    503             }
    504
    505             return Infinity;
    506         },
    507
    508         /**
    509          * Determines the angle between the positive x axis and the line.
    510          * @returns {Number}
    511          */
    512         getAngle: function () {
    513             return Math.atan2(-this.stdform[1], this.stdform[2]);
    514         },
    515
    516         /**
    517          * Determines whether the line is drawn beyond {@link JXG.Line#point1} and
    518          * {@link JXG.Line#point2} and updates the line.
    519          * @param {Boolean} straightFirst True if the Line shall be drawn beyond
    520          * {@link JXG.Line#point1}, false otherwise.
    521          * @param {Boolean} straightLast True if the Line shall be drawn beyond
    522          * {@link JXG.Line#point2}, false otherwise.
    523          * @see #straightFirst
    524          * @see #straightLast
    525          * @private
    526          */
    527         setStraight: function (straightFirst, straightLast) {
    528             this.visProp.straightfirst = straightFirst;
    529             this.visProp.straightlast = straightLast;
    530
    531             this.board.renderer.updateLine(this);
    532             return this;
    533         },
    534
    535         // documented in geometry element
    536         getTextAnchor: function () {
    537             return new Coords(Const.COORDS_BY_USER, [0.5 * (this.point2.X() + this.point1.X()), 0.5 * (this.point2.Y() + this.point1.Y())], this.board);
    538         },
    539
    540         /**
    541          * Adjusts Label coords relative to Anchor. DESCRIPTION
    542          * @private
    543          */
    544         setLabelRelativeCoords: function (relCoords) {
    545             if (Type.exists(this.label)) {
    546                 this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [relCoords[0], -relCoords[1]], this.board);
    547             }
    548         },
    549
    550         // documented in geometry element
    551         getLabelAnchor: function () {
    552             var x, y,
    553                 fs = 0,
    554                 c1 = new Coords(Const.COORDS_BY_USER, this.point1.coords.usrCoords, this.board),
    555                 c2 = new Coords(Const.COORDS_BY_USER, this.point2.coords.usrCoords, this.board),
    556                 ev_sf = Type.evaluate(this.visProp.straightfirst),
    557                 ev_sl = Type.evaluate(this.visProp.straightlast);
    558
    559             if (ev_sf || ev_sl) {
    560                 Geometry.calcStraight(this, c1, c2, 0);
    561             }
    562
    563             c1 = c1.scrCoords;
    564             c2 = c2.scrCoords;
    565
    566             if (!Type.exists(this.label)) {
    567                 return new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], this.board);
    568             }
    569
    570             switch (Type.evaluate(this.label.visProp.position)) {
    571             case 'lft':
    572             case 'llft':
    573             case 'ulft':
    574                 if (c1[1] <= c2[1]) {
    575                     x = c1[1];
    576                     y = c1[2];
    577                 } else {
    578                     x = c2[1];
    579                     y = c2[2];
    580                 }
    581                 break;
    582             case 'rt':
    583             case 'lrt':
    584             case 'urt':
    585                 if (c1[1] > c2[1]) {
    586                     x = c1[1];
    587                     y = c1[2];
    588                 } else {
    589                     x = c2[1];
    590                     y = c2[2];
    591                 }
    592                 break;
    593             default:
    594                 x = 0.5 * (c1[1] + c2[1]);
    595                 y = 0.5 * (c1[2] + c2[2]);
    596             }
    597
    598             // Correct coordinates if the label seems to be outside of canvas.
    599             if (ev_sf || ev_sl) {
    600                 if (Type.exists(this.label)) {  // Does not exist during createLabel
    601                     fs = Type.evaluate(this.label.visProp.fontsize);
    602                 }
    603
    604                 if (Math.abs(x) < Mat.eps) {
    605                     x = 0;
    606                 } else if (this.board.canvasWidth + Mat.eps > x &&
    607                             x > this.board.canvasWidth - fs - Mat.eps) {
    608                     x = this.board.canvasWidth - fs;
    609                 }
    610
    611                 if (Mat.eps + fs > y && y > -Mat.eps) {
    612                     y = fs;
    613                 } else if (this.board.canvasHeight + Mat.eps > y &&
    614                             y > this.board.canvasHeight - fs - Mat.eps) {
    615                     y = this.board.canvasHeight;
    616                 }
    617             }
    618
    619             return new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board);
    620         },
    621
    622         // documented in geometry element
    623         cloneToBackground: function () {
    624             var copy = {}, r, s, er;
    625
    626             copy.id = this.id + 'T' + this.numTraces;
    627             copy.elementClass = Const.OBJECT_CLASS_LINE;
    628             this.numTraces++;
    629             copy.point1 = this.point1;
    630             copy.point2 = this.point2;
    631
    632             copy.stdform = this.stdform;
    633
    634             copy.board = this.board;
    635
    636             copy.visProp = Type.deepCopy(this.visProp, this.visProp.traceattributes, true);
    637             copy.visProp.layer = this.board.options.layer.trace;
    638             Type.clearVisPropOld(copy);
    639
    640             s = this.getSlope();
    641             r = this.getRise();
    642             copy.getSlope = function () {
    643                 return s;
    644             };
    645             copy.getRise = function () {
    646                 return r;
    647             };
    648
    649             er = this.board.renderer.enhancedRendering;
    650             this.board.renderer.enhancedRendering = true;
    651             this.board.renderer.drawLine(copy);
    652             this.board.renderer.enhancedRendering = er;
    653             this.traces[copy.id] = copy.rendNode;
    654
    655             return this;
    656         },
    657
    658         /**
    659          * Add transformations to this line.
    660          * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of
    661          * {@link JXG.Transformation}s.
    662          * @returns {JXG.Line} Reference to this line object.
    663          */
    664         addTransform: function (transform) {
    665             var i,
    666                 list = Type.isArray(transform) ? transform : [transform],
    667                 len = list.length;
    668
    669             for (i = 0; i < len; i++) {
    670                 this.point1.transformations.push(list[i]);
    671                 this.point2.transformations.push(list[i]);
    672             }
    673
    674             return this;
    675         },
    676
    677         // see GeometryElement.js
    678         snapToGrid: function (pos) {
    679             var c1, c2, dc, t, ticks,
    680                 x, y, sX, sY;
    681
    682             if (Type.evaluate(this.visProp.snaptogrid)) {
    683                 if (this.parents.length < 3) {    // Line through two points
    684                     this.point1.handleSnapToGrid(true, true);
    685                     this.point2.handleSnapToGrid(true, true);
    686             } else if (Type.exists(pos)) {       // Free line
    687                     sX = Type.evaluate(this.visProp.snapsizex);
    688                     sY = Type.evaluate(this.visProp.snapsizey);
    689
    690                     c1 = new Coords(Const.COORDS_BY_SCREEN, [pos.Xprev, pos.Yprev], this.board);
    691
    692                     x = c1.usrCoords[1];
    693                     y = c1.usrCoords[2];
    694
    695                     if (sX <= 0 && this.board.defaultAxes && this.board.defaultAxes.x.defaultTicks) {
    696                         ticks = this.board.defaultAxes.x.defaultTicks;
    697                         sX = ticks.ticksDelta * (Type.evaluate(ticks.visProp.minorticks) + 1);
    698                     }
    699                     if (sY <= 0 && this.board.defaultAxes && this.board.defaultAxes.y.defaultTicks) {
    700                         ticks = this.board.defaultAxes.y.defaultTicks;
    701                         sY = ticks.ticksDelta * (Type.evaluate(ticks.visProp.minorticks) + 1);
    702                     }
    703
    704                     // if no valid snap sizes are available, don't change the coords.
    705                     if (sX > 0 && sY > 0) {
    706                         // projectCoordsToLine
    707                         /*
    708                         v = [0, this.stdform[1], this.stdform[2]];
    709                         v = Mat.crossProduct(v, c1.usrCoords);
    710                         c2 = Geometry.meetLineLine(v, this.stdform, 0, this.board);
    711                         */
    712                         c2 = Geometry.projectPointToLine({coords: c1}, this, this.board);
    713
    714                         dc = Statistics.subtract([1, Math.round(x / sX) * sX, Math.round(y / sY) * sY], c2.usrCoords);
    715                         t = this.board.create('transform', dc.slice(1), {type: 'translate'});
    716                         t.applyOnce([this.point1, this.point2]);
    717                     }
    718                 }
    719             } else {
    720                 this.point1.handleSnapToGrid(false, true);
    721                 this.point2.handleSnapToGrid(false, true);
    722             }
    723
    724             return this;
    725         },
    726
    727         // see element.js
    728         snapToPoints: function () {
    729             var forceIt = Type.evaluate(this.visProp.snaptopoints);
    730
    731             if (this.parents.length < 3) {    // Line through two points
    732                 this.point1.handleSnapToPoints(forceIt);
    733                 this.point2.handleSnapToPoints(forceIt);
    734             }
    735
    736             return this;
    737         },
    738
    739         /**
    740          * Treat the line as parametric curve in homogeneous coordinates, where the parameter t runs from 0 to 1.
    741          * First we transform the interval [0,1] to [-1,1].
    742          * If the line has homogeneous coordinates [c,a,b] = stdform[] then the direction of the line is [b,-a].
    743          * 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).
    744          * Let the coordinates of that point be [z, x, y].
    745          * Then, the curve runs linearly from
    746          * [0, b, -a] (t=-1) to [z, x, y] (t=0)
    747          * and
    748          * [z, x, y] (t=0) to [0, -b, a] (t=1)
    749          *
    750          * @param {Number} t Parameter running from 0 to 1.
    751          * @returns {Number} X(t) x-coordinate of the line treated as parametric curve.
    752          * */
    753         X: function (t) {
    754             var x,
    755                 b = this.stdform[2];
    756
    757             x = (Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps) ?
    758                     this.point1.coords.usrCoords[1] :
    759                     this.point2.coords.usrCoords[1];
    760
    761             t = (t - 0.5) * 2;
    762
    763             return (1 - Math.abs(t)) * x - t * b;
    764         },
    765
    766         /**
    767          * Treat the line as parametric curve in homogeneous coordinates.
    768          * See {@link JXG.Line#X} for a detailed description.
    769          * @param {Number} t Parameter running from 0 to 1.
    770          * @returns {Number} Y(t) y-coordinate of the line treated as parametric curve.
    771          */
    772         Y: function (t) {
    773             var y,
    774                 a = this.stdform[1];
    775
    776             y = (Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps) ?
    777                     this.point1.coords.usrCoords[2] :
    778                     this.point2.coords.usrCoords[2];
    779
    780             t = (t - 0.5) * 2;
    781
    782             return (1 - Math.abs(t)) * y + t * a;
    783         },
    784
    785         /**
    786          * Treat the line as parametric curve in homogeneous coordinates.
    787          * See {@link JXG.Line#X} for a detailed description.
    788          *
    789          * @param {Number} t Parameter running from 0 to 1.
    790          * @returns {Number} Z(t) z-coordinate of the line treated as parametric curve.
    791          */
    792         Z: function (t) {
    793             var z = (Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps) ?
    794                     this.point1.coords.usrCoords[0] :
    795                     this.point2.coords.usrCoords[0];
    796
    797             t = (t - 0.5) * 2;
    798
    799             return (1 - Math.abs(t)) * z;
    800         },
    801
    802
    803         /**
    804          * The distance between the two points defining the line.
    805          * @returns {Number}
    806          */
    807         L: function () {
    808             return this.point1.Dist(this.point2);
    809         },
    810
    811         /**
    812          * Treat the element  as a parametric curve
    813          * @private
    814          */
    815         minX: function () {
    816             return 0.0;
    817         },
    818
    819         /**
    820          * Treat the element as parametric curve
    821          * @private
    822          */
    823         maxX: function () {
    824             return 1.0;
    825         },
    826
    827         // documented in geometry element
    828         bounds: function () {
    829             var p1c = this.point1.coords.usrCoords,
    830                 p2c = this.point2.coords.usrCoords;
    831
    832             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])];
    833         },
    834
    835         /**
    836          * Adds ticks to this line. Ticks can be added to any kind of line: line, arrow, and axis.
    837          * @param {JXG.Ticks} ticks Reference to a ticks object which is describing the ticks (color, distance, how many, etc.).
    838          * @returns {String} Id of the ticks object.
    839          */
    840         addTicks: function (ticks) {
    841             if (ticks.id === '' || !Type.exists(ticks.id)) {
    842                 ticks.id = this.id + '_ticks_' + (this.ticks.length + 1);
    843             }
    844
    845             this.board.renderer.drawTicks(ticks);
    846             this.ticks.push(ticks);
    847
    848             return ticks.id;
    849         },
    850
    851         // documented in GeometryElement.js
    852         remove: function () {
    853             this.removeAllTicks();
    854             GeometryElement.prototype.remove.call(this);
    855         },
    856
    857         /**
    858          * Removes all ticks from a line.
    859          */
    860         removeAllTicks: function () {
    861             var t;
    862
    863             for (t = this.ticks.length; t > 0; t--) {
    864                 this.removeTicks(this.ticks[t - 1]);
    865             }
    866
    867             this.ticks = [];
    868             this.board.update();
    869         },
    870
    871         /**
    872          * Removes ticks identified by parameter named tick from this line.
    873          * @param {JXG.Ticks} tick Reference to tick object to remove.
    874          */
    875         removeTicks: function (tick) {
    876             var t, j;
    877
    878             if (Type.exists(this.defaultTicks) && this.defaultTicks === tick) {
    879                 this.defaultTicks = null;
    880             }
    881
    882             for (t = this.ticks.length; t > 0; t--) {
    883                 if (this.ticks[t - 1] === tick) {
    884                     this.board.removeObject(this.ticks[t - 1]);
    885
    886                     if (this.ticks[t - 1].ticks) {
    887                         for (j = 0; j < this.ticks[t - 1].ticks.length; j++) {
    888                             if (Type.exists(this.ticks[t - 1].labels[j])) {
    889                                 this.board.removeObject(this.ticks[t - 1].labels[j]);
    890                             }
    891                         }
    892                     }
    893
    894                     delete this.ticks[t - 1];
    895                     break;
    896                 }
    897             }
    898         },
    899
    900         // hideElement: function () {
    901         //     var i;
    902         //
    903         //     GeometryElement.prototype.hideElement.call(this);
    904         //
    905         //     for (i = 0; i < this.ticks.length; i++) {
    906         //         this.ticks[i].hideElement();
    907         //     }
    908         // },
    909         //
    910         // showElement: function () {
    911         //     var i;
    912         //     GeometryElement.prototype.showElement.call(this);
    913         //
    914         //     for (i = 0; i < this.ticks.length; i++) {
    915         //         this.ticks[i].showElement();
    916         //     }
    917         // }
    918     });
    919
    920     /**
    921      * @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
    922      * a line can be used as an arrow and/or axis.
    923      * @pseudo
    924      * @description
    925      * @name Line
    926      * @augments JXG.Line
    927      * @constructor
    928      * @type JXG.Line
    929      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    930      * @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
    931      * numbers describing the coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
    932      * It is possible to provide a function returning an array or a point, instead of providing an array or a point.
    933      * @param {Number,function_Number,function_Number,function} c,a,b A line can also be created providing three numbers. The line is then described by
    934      * 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.
    935      * @param {function} f This function must return an array containing three numbers forming the line's homogeneous coordinates.
    936      * @example
    937      * // Create a line using point and coordinates/
    938      * // The second point will be fixed and invisible.
    939      * var p1 = board.create('point', [4.5, 2.0]);
    940      * var l1 = board.create('line', [p1, [1.0, 1.0]]);
    941      * </pre><div class="jxgbox" id="c0ae3461-10c4-4d39-b9be-81d74759d122" style="width: 300px; height: 300px;"></div>
    942      * <script type="text/javascript">
    943      *   var glex1_board = JXG.JSXGraph.initBoard('c0ae3461-10c4-4d39-b9be-81d74759d122', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    944      *   var glex1_p1 = glex1_board.create('point', [4.5, 2.0]);
    945      *   var glex1_l1 = glex1_board.create('line', [glex1_p1, [1.0, 1.0]]);
    946      * </script><pre>
    947      * @example
    948      * // Create a line using three coordinates
    949      * var l1 = board.create('line', [1.0, -2.0, 3.0]);
    950      * </pre><div class="jxgbox" id="cf45e462-f964-4ba4-be3a-c9db94e2593f" style="width: 300px; height: 300px;"></div>
    951      * <script type="text/javascript">
    952      *   var glex2_board = JXG.JSXGraph.initBoard('cf45e462-f964-4ba4-be3a-c9db94e2593f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    953      *   var glex2_l1 = glex2_board.create('line', [1.0, -2.0, 3.0]);
    954      * </script><pre>
    955      */
    956     JXG.createLine = function (board, parents, attributes) {
    957         var ps, el, p1, p2, i, attr,
    958             c = [],
    959             constrained = false,
    960             isDraggable;
    961
    962         /**
    963          * The line is defined by two points or coordinates of two points.
    964          * In the latter case, the points are created.
    965          */
    966         if (parents.length === 2) {
    967             // point 1 given by coordinates
    968             if (Type.isArray(parents[0]) && parents[0].length > 1) {
    969                 attr = Type.copyAttributes(attributes, board.options, 'line', 'point1');
    970                 p1 = board.create('point', parents[0], attr);
    971             } else if (Type.isString(parents[0]) || Type.isPoint(parents[0])) {
    972                 p1 =  board.select(parents[0]);
    973             } else if (Type.isFunction(parents[0]) && Type.isPoint(parents[0]())) {
    974                 p1 = parents[0]();
    975                 constrained = true;
    976             } else if (Type.isFunction(parents[0]) && parents[0]().length && parents[0]().length >= 2) {
    977                 attr = Type.copyAttributes(attributes, board.options, 'line', 'point1');
    978                 p1 = Point.createPoint(board, parents[0](), attr);
    979                 constrained = true;
    980             } else {
    981                 throw new Error("JSXGraph: Can't create line with parent types '" +
    982                     (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    983                     "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]");
    984             }
    985
    986             // point 2 given by coordinates
    987             if (Type.isArray(parents[1]) && parents[1].length > 1) {
    988                 attr = Type.copyAttributes(attributes, board.options, 'line', 'point2');
    989                 p2 = board.create('point', parents[1], attr);
    990             } else if (Type.isString(parents[1]) || Type.isPoint(parents[1])) {
    991                 p2 =  board.select(parents[1]);
    992             } else if (Type.isFunction(parents[1]) &&  Type.isPoint(parents[1]()) ) {
    993                 p2 = parents[1]();
    994                 constrained = true;
    995             } else if (Type.isFunction(parents[1]) && parents[1]().length && parents[1]().length >= 2) {
    996                 attr = Type.copyAttributes(attributes, board.options, 'line', 'point2');
    997                 p2 = Point.createPoint(board, parents[1](), attr);
    998                 constrained = true;
    999             } else {
    1000                 throw new Error("JSXGraph: Can't create line with parent types '" +
    1001                     (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    1002                     "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]");
    1003             }
    1004
    1005             attr = Type.copyAttributes(attributes, board.options, 'line');
    1006
    1007             el = new JXG.Line(board, p1, p2, attr);
    1008             if (constrained) {
    1009                 el.constrained = true;
    1010                 el.funp1 = parents[0];
    1011                 el.funp2 = parents[1];
    1012             } else {
    1013                 el.isDraggable = true;
    1014             }
    1015
    1016             //if (!el.constrained) {
    1017             el.setParents([p1.id, p2.id]);
    1018             //}
    1019
    1020          // Line is defined by three homogeneous coordinates.
    1021          // Also in this case points are created.
    1022         } else if (parents.length === 3) {
    1023             // free line
    1024             isDraggable = true;
    1025             for (i = 0; i < 3; i++) {
    1026                 if (Type.isNumber(parents[i])) {
    1027                     // createFunction will just wrap a function around our constant number
    1028                     // that does nothing else but to return that number.
    1029                     c[i] = Type.createFunction(parents[i]);
    1030                 } else if (Type.isFunction(parents[i])) {
    1031                     c[i] = parents[i];
    1032                     isDraggable = false;
    1033                 } else {
    1034                     throw new Error("JSXGraph: Can't create line with parent types '" +
    1035                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." +
    1036                         "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]");
    1037                 }
    1038             }
    1039
    1040             // point 1 is the midpoint between (0,c,-b) and point 2. => point1 is finite.
    1041             attr = Type.copyAttributes(attributes, board.options, 'line', 'point1');
    1042             if (isDraggable) {
    1043                 p1 = board.create('point', [
    1044                     c[2]() * c[2]() + c[1]() * c[1](),
    1045                     c[2]() - c[1]() * c[0]() + c[2](),
    1046                     -c[1]() - c[2]() * c[0]() - c[1]()
    1047                 ], attr);
    1048             } else {
    1049                 p1 = board.create('point', [
    1050                     function () {
    1051                         return (c[2]() * c[2]() + c[1]() * c[1]()) * 0.5;
    1052                     },
    1053                     function () {
    1054                         return (c[2]() - c[1]() * c[0]() + c[2]()) * 0.5;
    1055                     },
    1056                     function () {
    1057                         return (-c[1]() - c[2]() * c[0]() - c[1]()) * 0.5;
    1058                     }], attr);
    1059             }
    1060
    1061             // point 2: (b^2+c^2,-ba+c,-ca-b)
    1062             attr = Type.copyAttributes(attributes, board.options, 'line', 'point2');
    1063             if (isDraggable) {
    1064                 p2 = board.create('point', [
    1065                     c[2]() * c[2]() + c[1]() * c[1](),
    1066                     -c[1]() * c[0]() + c[2](),
    1067                     -c[2]() * c[0]() - c[1]()
    1068                 ], attr);
    1069             } else {
    1070                 p2 = board.create('point', [
    1071                     function () {
    1072                         return c[2]() * c[2]() + c[1]() * c[1]();
    1073                     },
    1074                     function () {
    1075                         return -c[1]() * c[0]() + c[2]();
    1076                     },
    1077                     function () {
    1078                         return -c[2]() * c[0]() - c[1]();
    1079                     }], attr);
    1080             }
    1081
    1082             // If the line will have a glider and board.suspendUpdate() has been called, we
    1083             // need to compute the initial position of the two points p1 and p2.
    1084             p1.prepareUpdate().update();
    1085             p2.prepareUpdate().update();
    1086             attr = Type.copyAttributes(attributes, board.options, 'line');
    1087             el = new JXG.Line(board, p1, p2, attr);
    1088             // Not yet working, because the points are not draggable.
    1089             el.isDraggable = isDraggable;
    1090             el.setParents([p1, p2]);
    1091
    1092         // The parent array contains a function which returns two points.
    1093         } else if (parents.length === 1 && Type.isFunction(parents[0]) && parents[0]().length === 2 &&
    1094                 Type.isPoint(parents[0]()[0]) &&
    1095                 Type.isPoint(parents[0]()[1])) {
    1096             ps = parents[0]();
    1097             attr = Type.copyAttributes(attributes, board.options, 'line');
    1098             el = new JXG.Line(board, ps[0], ps[1], attr);
    1099             el.constrained = true;
    1100             el.funps = parents[0];
    1101             el.setParents(ps);
    1102
    1103         } else if (parents.length === 1 && Type.isFunction(parents[0]) && parents[0]().length === 3 &&
    1104                 Type.isNumber(parents[0]()[0]) &&
    1105                 Type.isNumber(parents[0]()[1]) &&
    1106                 Type.isNumber(parents[0]()[2])) {
    1107             ps = parents[0];
    1108
    1109             attr = Type.copyAttributes(attributes, board.options, 'line', 'point1');
    1110             p1 = board.create('point', [
    1111                 function () {
    1112                     var c = ps();
    1113
    1114                     return [
    1115                         (c[2] * c[2] + c[1] * c[1]) * 0.5,
    1116                         (c[2] - c[1] * c[0] + c[2]) * 0.5,
    1117                         (-c[1] - c[2] * c[0] - c[1]) * 0.5
    1118                     ];
    1119                 }], attr);
    1120
    1121             attr = Type.copyAttributes(attributes, board.options, 'line', 'point2');
    1122             p2 = board.create('point', [
    1123                 function () {
    1124                     var c = ps();
    1125
    1126                     return [
    1127                         c[2] * c[2] + c[1] * c[1],
    1128                         -c[1] * c[0] + c[2],
    1129                         -c[2] * c[0] - c[1]
    1130                     ];
    1131                 }], attr);
    1132
    1133             attr = Type.copyAttributes(attributes, board.options, 'line');
    1134             el = new JXG.Line(board, p1, p2, attr);
    1135
    1136             el.constrained = true;
    1137             el.funps = parents[0];
    1138             el.setParents([p1, p2]);
    1139
    1140         } else {
    1141             throw new Error("JSXGraph: Can't create line with parent types '" +
    1142                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    1143                 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]");
    1144         }
    1145
    1146         return el;
    1147     };
    1148
    1149     JXG.registerElement('line', JXG.createLine);
    1150
    1151     /**
    1152      * @class This element is used to provide a constructor for a segment.
    1153      * It's strictly spoken just a wrapper for element {@link Line} with {@link JXG.Line#straightFirst}
    1154      * and {@link JXG.Line#straightLast} properties set to false. If there is a third variable then the
    1155      * segment has a fixed length (which may be a function, too).
    1156      * @pseudo
    1157      * @description
    1158      * @name Segment
    1159      * @augments JXG.Line
    1160      * @constructor
    1161      * @type JXG.Line
    1162      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1163      * @param {JXG.Point,array_JXG.Point,array} point1,point2 Parent elements can be two elements either of type {@link JXG.Point}
    1164      * or array of numbers describing the
    1165      * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
    1166      * @param {number,function} length (optional) The points are adapted - if possible - such that their distance
    1167      * has a this value.
    1168      * @see Line
    1169      * @example
    1170      * // Create a segment providing two points.
    1171      *   var p1 = board.create('point', [4.5, 2.0]);
    1172      *   var p2 = board.create('point', [1.0, 1.0]);
    1173      *   var l1 = board.create('segment', [p1, p2]);
    1174      * </pre><div class="jxgbox" id="d70e6aac-7c93-4525-a94c-a1820fa38e2f" style="width: 300px; height: 300px;"></div>
    1175      * <script type="text/javascript">
    1176      *   var slex1_board = JXG.JSXGraph.initBoard('d70e6aac-7c93-4525-a94c-a1820fa38e2f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    1177      *   var slex1_p1 = slex1_board.create('point', [4.5, 2.0]);
    1178      *   var slex1_p2 = slex1_board.create('point', [1.0, 1.0]);
    1179      *   var slex1_l1 = slex1_board.create('segment', [slex1_p1, slex1_p2]);
    1180      * </script><pre>
    1181      *
    1182      * @example
    1183      * // Create a segment providing two points.
    1184      *   var p1 = board.create('point', [4.0, 1.0]);
    1185      *   var p2 = board.create('point', [1.0, 1.0]);
    1186      *   var l1 = board.create('segment', [p1, p2]);
    1187      *   var p3 = board.create('point', [4.0, 2.0]);
    1188      *   var p4 = board.create('point', [1.0, 2.0]);
    1189      *   var l2 = board.create('segment', [p3, p4, 3]);
    1190      *   var p5 = board.create('point', [4.0, 3.0]);
    1191      *   var p6 = board.create('point', [1.0, 4.0]);
    1192      *   var l3 = board.create('segment', [p5, p6, function(){ return l1.L();} ]);
    1193      * </pre><div class="jxgbox" id="617336ba-0705-4b2b-a236-c87c28ef25be" style="width: 300px; height: 300px;"></div>
    1194      * <script type="text/javascript">
    1195      *   var slex2_board = JXG.JSXGraph.initBoard('617336ba-0705-4b2b-a236-c87c28ef25be', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    1196      *   var slex2_p1 = slex2_board.create('point', [4.0, 1.0]);
    1197      *   var slex2_p2 = slex2_board.create('point', [1.0, 1.0]);
    1198      *   var slex2_l1 = slex2_board.create('segment', [slex2_p1, slex2_p2]);
    1199      *   var slex2_p3 = slex2_board.create('point', [4.0, 2.0]);
    1200      *   var slex2_p4 = slex2_board.create('point', [1.0, 2.0]);
    1201      *   var slex2_l2 = slex2_board.create('segment', [slex2_p3, slex2_p4, 3]);
    1202      *   var slex2_p5 = slex2_board.create('point', [4.0, 2.0]);
    1203      *   var slex2_p6 = slex2_board.create('point', [1.0, 2.0]);
    1204      *   var slex2_l3 = slex2_board.create('segment', [slex2_p5, slex2_p6, function(){ return slex2_l1.L();}]);
    1205      * </script><pre>
    1206      *
    1207      */
    1208     JXG.createSegment = function (board, parents, attributes) {
    1209         var el, attr;
    1210
    1211         attributes.straightFirst = false;
    1212         attributes.straightLast = false;
    1213         attr = Type.copyAttributes(attributes, board.options, 'segment');
    1214
    1215         el = board.create('line', parents.slice(0, 2), attr);
    1216
    1217         if (parents.length === 3) {
    1218             el.hasFixedLength = true;
    1219
    1220             if (Type.isNumber(parents[2])) {
    1221                 el.fixedLength = function () {
    1222                     return parents[2];
    1223                 };
    1224             } else if (Type.isFunction(parents[2])) {
    1225                 el.fixedLength = parents[2];
    1226             } else {
    1227                 throw new Error("JSXGraph: Can't create segment with third parent type '" +
    1228                     (typeof parents[2]) + "'." +
    1229                     "\nPossible third parent types: number or function");
    1230             }
    1231
    1232             el.getParents = function() {
    1233                 return this.parents.concat(this.fixedLength());
    1234             };
    1235
    1236             el.fixedLengthOldCoords = [];
    1237             el.fixedLengthOldCoords[0] = new Coords(Const.COORDS_BY_USER, el.point1.coords.usrCoords.slice(1, 3), board);
    1238             el.fixedLengthOldCoords[1] = new Coords(Const.COORDS_BY_USER, el.point2.coords.usrCoords.slice(1, 3), board);
    1239         }
    1240
    1241         el.elType = 'segment';
    1242
    1243         return el;
    1244     };
    1245
    1246     JXG.registerElement('segment', JXG.createSegment);
    1247
    1248     /**
    1249      * @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}
    1250      * and {@link JXG.Line#straightLast} properties set to false and {@link JXG.Line#lastArrow} set to true.
    1251      * @pseudo
    1252      * @description
    1253      * @name Arrow
    1254      * @augments JXG.Line
    1255      * @constructor
    1256      * @type JXG.Line
    1257      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1258      * @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
    1259      * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
    1260      * @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
    1261      * of the equation <tt>a*x+b*y+c*z = 0</tt>.
    1262      * @see Line
    1263      * @example
    1264      * // Create an arrow providing two points.
    1265      *   var p1 = board.create('point', [4.5, 2.0]);
    1266      *   var p2 = board.create('point', [1.0, 1.0]);
    1267      *   var l1 = board.create('arrow', [p1, p2]);
    1268      * </pre><div class="jxgbox" id="1d26bd22-7d6d-4018-b164-4c8bc8d22ccf" style="width: 300px; height: 300px;"></div>
    1269      * <script type="text/javascript">
    1270      *   var alex1_board = JXG.JSXGraph.initBoard('1d26bd22-7d6d-4018-b164-4c8bc8d22ccf', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    1271      *   var alex1_p1 = alex1_board.create('point', [4.5, 2.0]);
    1272      *   var alex1_p2 = alex1_board.create('point', [1.0, 1.0]);
    1273      *   var alex1_l1 = alex1_board.create('arrow', [alex1_p1, alex1_p2]);
    1274      * </script><pre>
    1275      */
    1276     JXG.createArrow = function (board, parents, attributes) {
    1277         var el;
    1278
    1279         attributes.firstArrow = false;
    1280         attributes.lastArrow = true;
    1281         el = board.create('line', parents, attributes).setStraight(false, false);
    1282         //el.setArrow(false, true);
    1283         el.type = Const.OBJECT_TYPE_VECTOR;
    1284         el.elType = 'arrow';
    1285
    1286         return el;
    1287     };
    1288
    1289     JXG.registerElement('arrow', JXG.createArrow);
    1290
    1291     /**
    1292      * @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}
    1293      * 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.
    1294      * @pseudo
    1295      * @description
    1296      * @name Axis
    1297      * @augments JXG.Line
    1298      * @constructor
    1299      * @type JXG.Line
    1300      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1301      * @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
    1302      * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
    1303      * @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
    1304      * of the equation <tt>a*x+b*y+c*z = 0</tt>.
    1305      * @example
    1306      * // Create an axis providing two coord pairs.
    1307      *   var l1 = board.create('axis', [[0.0, 1.0], [1.0, 1.3]]);
    1308      * </pre><div class="jxgbox" id="4f414733-624c-42e4-855c-11f5530383ae" style="width: 300px; height: 300px;"></div>
    1309      * <script type="text/javascript">
    1310      *   var axex1_board = JXG.JSXGraph.initBoard('4f414733-624c-42e4-855c-11f5530383ae', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    1311      *   var axex1_l1 = axex1_board.create('axis', [[0.0, 1.0], [1.0, 1.3]]);
    1312      * </script><pre>
    1313      */
    1314     JXG.createAxis = function (board, parents, attributes) {
    1315         var attr, attr_ticks, el, els, dist;
    1316
    1317         // Arrays oder Punkte, mehr brauchen wir nicht.
    1318         if ((Type.isArray(parents[0]) || Type.isPoint(parents[0])) && (Type.isArray(parents[1]) || Type.isPoint(parents[1]))) {
    1319             attr = Type.copyAttributes(attributes, board.options, 'axis');
    1320             el = board.create('line', parents, attr);
    1321             el.type = Const.OBJECT_TYPE_AXIS;
    1322             el.isDraggable = false;
    1323             el.point1.isDraggable = false;
    1324             el.point2.isDraggable = false;
    1325
    1326             for (els in el.ancestors) {
    1327                 if (el.ancestors.hasOwnProperty(els)) {
    1328                     el.ancestors[els].type = Const.OBJECT_TYPE_AXISPOINT;
    1329                 }
    1330             }
    1331
    1332             attr_ticks = Type.copyAttributes(attributes, board.options, 'axis', 'ticks');
    1333             if (Type.exists(attr_ticks.ticksdistance)) {
    1334                 dist = attr_ticks.ticksdistance;
    1335             } else if (Type.isArray(attr_ticks.ticks)) {
    1336                 dist = attr_ticks.ticks;
    1337             } else {
    1338                 dist = 1.0;
    1339             }
    1340
    1341             /**
    1342              * The ticks attached to the axis.
    1343              * @memberOf Axis.prototype
    1344              * @name defaultTicks
    1345              * @type JXG.Ticks
    1346              */
    1347             el.defaultTicks = board.create('ticks', [el, dist], attr_ticks);
    1348             el.defaultTicks.dump = false;
    1349             el.elType = 'axis';
    1350             el.subs = {
    1351                 ticks: el.defaultTicks
    1352             };
    1353             el.inherits.push(el.defaultTicks);
    1354
    1355         } else {
    1356             throw new Error("JSXGraph: Can't create axis with parent types '" +
    1357                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    1358                 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]");
    1359         }
    1360
    1361         return el;
    1362     };
    1363
    1364     JXG.registerElement('axis', JXG.createAxis);
    1365
    1366     /**
    1367      * @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
    1368      * by a glider on a line, circle, or curve and describes the tangent in the glider point on that line, circle, or curve.
    1369      * @pseudo
    1370      * @description
    1371      * @name Tangent
    1372      * @augments JXG.Line
    1373      * @constructor
    1374      * @type JXG.Line
    1375      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1376      * @param {Glider} g A glider on a line, circle, or curve.
    1377      * @example
    1378      * // Create a tangent providing a glider on a function graph
    1379      *   var c1 = board.create('curve', [function(t){return t},function(t){return t*t*t;}]);
    1380      *   var g1 = board.create('glider', [0.6, 1.2, c1]);
    1381      *   var t1 = board.create('tangent', [g1]);
    1382      * </pre><div class="jxgbox" id="7b7233a0-f363-47dd-9df5-4018d0d17a98" style="width: 400px; height: 400px;"></div>
    1383      * <script type="text/javascript">
    1384      *   var tlex1_board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-4018d0d17a98', {boundingbox: [-6, 6, 6, -6], axis: true, showcopyright: false, shownavigation: false});
    1385      *   var tlex1_c1 = tlex1_board.create('curve', [function(t){return t},function(t){return t*t*t;}]);
    1386      *   var tlex1_g1 = tlex1_board.create('glider', [0.6, 1.2, tlex1_c1]);
    1387      *   var tlex1_t1 = tlex1_board.create('tangent', [tlex1_g1]);
    1388      * </script><pre>
    1389      */
    1390     JXG.createTangent = function (board, parents, attributes) {
    1391         var p, c, g, f, j, el, tangent;
    1392
    1393         // One arguments: glider on line, circle or curve
    1394         if (parents.length === 1) {
    1395             p = parents[0];
    1396             c = p.slideObject;
    1397         // Two arguments: (point,F"|conic) or (line|curve|circle|conic,point). // Not yet: curve!
    1398         } else if (parents.length === 2) {
    1399             // In fact, for circles and conics it is the polar
    1400             if (Type.isPoint(parents[0])) {
    1401                 p = parents[0];
    1402                 c = parents[1];
    1403             } else if (Type.isPoint(parents[1])) {
    1404                 c = parents[0];
    1405                 p = parents[1];
    1406             } else {
    1407                 throw new Error("JSXGraph: Can't create tangent with parent types '" +
    1408                     (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    1409                     "\nPossible parent types: [glider], [point,line|curve|circle|conic]");
    1410             }
    1411         } else {
    1412             throw new Error("JSXGraph: Can't create tangent with parent types '" +
    1413                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    1414                 "\nPossible parent types: [glider], [point,line|curve|circle|conic]");
    1415         }
    1416
    1417         if (c.elementClass === Const.OBJECT_CLASS_LINE) {
    1418             tangent = board.create('line', [c.point1, c.point2], attributes);
    1419             tangent.glider = p;
    1420         } else if (c.elementClass === Const.OBJECT_CLASS_CURVE && c.type !== Const.OBJECT_TYPE_CONIC) {
    1421             if (Type.evaluate(c.visProp.curvetype) !== 'plot') {
    1422                 g = c.X;
    1423                 f = c.Y;
    1424                 tangent = board.create('line', [
    1425                     function () {
    1426                         return -p.X() * Numerics.D(f)(p.position) + p.Y() * Numerics.D(g)(p.position);
    1427                     },
    1428                     function () {
    1429                         return Numerics.D(f)(p.position);
    1430                     },
    1431                     function () {
    1432                         return -Numerics.D(g)(p.position);
    1433                     }
    1434                 ], attributes);
    1435                 p.addChild(tangent);
    1436
    1437                 // this is required for the geogebra reader to display a slope
    1438                 tangent.glider = p;
    1439             } else {  // curveType 'plot'
    1440                 // equation of the line segment: 0 = y*(x1-x2) + x*(y2-y1) + y1*x2-x1*y2
    1441                 tangent = board.create('line', [
    1442                     function () {
    1443                         var i = Math.floor(p.position);
    1444
    1445                         if (i === c.numberPoints - 1) {
    1446                             i--;
    1447                         }
    1448
    1449                         if (i < 0) {
    1450                             return 1;
    1451                         }
    1452
    1453                         return c.Y(i) * c.X(i + 1) - c.X(i) * c.Y(i + 1);
    1454                     },
    1455                     function () {
    1456                         var i = Math.floor(p.position);
    1457
    1458                         if (i === c.numberPoints - 1) {
    1459                             i--;
    1460                         }
    1461
    1462                         if (i < 0) {
    1463                             return 0;
    1464                         }
    1465
    1466                         return c.Y(i + 1) - c.Y(i);
    1467                     },
    1468                     function () {
    1469                         var i = Math.floor(p.position);
    1470
    1471                         if (i === c.numberPoints - 1) {
    1472                             i--;
    1473                         }
    1474
    1475                         if (i < 0) {
    1476                             return 0.0;
    1477                         }
    1478
    1479                         return c.X(i) - c.X(i + 1);
    1480                     }], attributes);
    1481
    1482                 p.addChild(tangent);
    1483
    1484                 // this is required for the geogebra reader to display a slope
    1485                 tangent.glider = p;
    1486             }
    1487         } else if (c.type === Const.OBJECT_TYPE_TURTLE) {
    1488             tangent = board.create('line', [
    1489                 function () {
    1490                     var i = Math.floor(p.position);
    1491
    1492                     // run through all curves of this turtle
    1493                     for (j = 0; j < c.objects.length; j++) {
    1494                         el = c.objects[j];
    1495
    1496                         if (el.type === Const.OBJECT_TYPE_CURVE) {
    1497                             if (i < el.numberPoints) {
    1498                                 break;
    1499                             }
    1500
    1501                             i -= el.numberPoints;
    1502                         }
    1503                     }
    1504
    1505                     if (i === el.numberPoints - 1) {
    1506                         i--;
    1507                     }
    1508
    1509                     if (i < 0) {
    1510                         return 1;
    1511                     }
    1512
    1513                     return el.Y(i) * el.X(i + 1) - el.X(i) * el.Y(i + 1);
    1514                 },
    1515                 function () {
    1516                     var i = Math.floor(p.position);
    1517
    1518                     // run through all curves of this turtle
    1519                     for (j = 0; j < c.objects.length; j++) {
    1520                         el = c.objects[j];
    1521
    1522                         if (el.type === Const.OBJECT_TYPE_CURVE) {
    1523                             if (i < el.numberPoints) {
    1524                                 break;
    1525                             }
    1526
    1527                             i -= el.numberPoints;
    1528                         }
    1529                     }
    1530
    1531                     if (i === el.numberPoints - 1) {
    1532                         i--;
    1533                     }
    1534                     if (i < 0) {
    1535                         return 0;
    1536                     }
    1537
    1538                     return el.Y(i + 1) - el.Y(i);
    1539                 },
    1540                 function () {
    1541                     var i = Math.floor(p.position);
    1542
    1543                     // run through all curves of this turtle
    1544                     for (j = 0; j < c.objects.length; j++) {
    1545                         el = c.objects[j];
    1546                         if (el.type === Const.OBJECT_TYPE_CURVE) {
    1547                             if (i < el.numberPoints) {
    1548                                 break;
    1549                             }
    1550                             i -= el.numberPoints;
    1551                         }
    1552                     }
    1553                     if (i === el.numberPoints - 1) {
    1554                         i--;
    1555                     }
    1556
    1557                     if (i < 0) {
    1558                         return 0;
    1559                     }
    1560
    1561                     return el.X(i) - el.X(i + 1);
    1562                 }], attributes);
    1563             p.addChild(tangent);
    1564
    1565             // this is required for the geogebra reader to display a slope
    1566             tangent.glider = p;
    1567         } else if (c.elementClass === Const.OBJECT_CLASS_CIRCLE || c.type === Const.OBJECT_TYPE_CONIC) {
    1568             // If p is not on c, the tangent is the polar.
    1569             // This construction should work on conics, too. p has to lie on c.
    1570             tangent = board.create('line', [
    1571                 function () {
    1572                     return Mat.matVecMult(c.quadraticform, p.coords.usrCoords)[0];
    1573                 },
    1574                 function () {
    1575                     return Mat.matVecMult(c.quadraticform, p.coords.usrCoords)[1];
    1576                 },
    1577                 function () {
    1578                     return Mat.matVecMult(c.quadraticform, p.coords.usrCoords)[2];
    1579                 }], attributes);
    1580
    1581             p.addChild(tangent);
    1582             // this is required for the geogebra reader to display a slope
    1583             tangent.glider = p;
    1584         }
    1585
    1586         if (!Type.exists(tangent)) {
    1587             throw new Error('JSXGraph: Couldn\'t create tangent with the given parents.');
    1588         }
    1589
    1590         tangent.elType = 'tangent';
    1591         tangent.type = Const.OBJECT_TYPE_TANGENT;
    1592         tangent.setParents(parents);
    1593
    1594         return tangent;
    1595     };
    1596
    1597     /**
    1598      * @class This element is used to provide a constructor for the radical axis with respect to two circles with distinct centers.
    1599      * The angular bisector of the polar lines of the circle centers with respect to the other circle is always the radical axis.
    1600      * The radical axis passes through the intersection points when the circles intersect.
    1601      * When a circle about the midpoint of circle centers, passing through the circle centers, intersects the circles, the polar lines pass through those intersection points.
    1602      * @pseudo
    1603      * @description
    1604      * @name RadicalAxis
    1605      * @augments JXG.Line
    1606      * @constructor
    1607      * @type JXG.Line
    1608      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1609      * @param {JXG.Circle} circle Circle one of the two respective circles.
    1610      * @param {JXG.Circle} circle Circle the other of the two respective circles.
    1611      * @example
    1612      * // Create the radical axis line with respect to two circles
    1613      *   var board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-5018d0d17a98', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
    1614      *   var p1 = board.create('point', [2, 3]);
    1615      *   var p2 = board.create('point', [1, 4]);
    1616      *   var c1 = board.create('circle', [p1, p2]);
    1617      *   var p3 = board.create('point', [6, 5]);
    1618      *   var p4 = board.create('point', [8, 6]);
    1619      *   var c2 = board.create('circle', [p3, p4]);
    1620      *   var r1 = board.create('radicalaxis', [c1, c2]);
    1621      * </pre><div class="jxgbox" id='7b7233a0-f363-47dd-9df5-5018d0d17a98' class='jxgbox' style='width:400px; height:400px;'></div>
    1622      * <script type='text/javascript'>
    1623      *   var rlex1_board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-5018d0d17a98', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
    1624      *   var rlex1_p1 = rlex1_board.create('point', [2, 3]);
    1625      *   var rlex1_p2 = rlex1_board.create('point', [1, 4]);
    1626      *   var rlex1_c1 = rlex1_board.create('circle', [rlex1_p1, rlex1_p2]);
    1627      *   var rlex1_p3 = rlex1_board.create('point', [6, 5]);
    1628      *   var rlex1_p4 = rlex1_board.create('point', [8, 6]);
    1629      *   var rlex1_c2 = rlex1_board.create('circle', [rlex1_p3, rlex1_p4]);
    1630      *   var rlex1_r1 = rlex1_board.create('radicalaxis', [rlex1_c1, rlex1_c2]);
    1631      * </script><pre>
    1632      */
    1633     JXG.createRadicalAxis = function (board, parents, attributes) {
    1634         var el, el1, el2;
    1635
    1636         if (parents.length !== 2 ||
    1637                 parents[0].elementClass !== Const.OBJECT_CLASS_CIRCLE ||
    1638                 parents[1].elementClass !== Const.OBJECT_CLASS_CIRCLE) {
    1639             // Failure
    1640             throw new Error("JSXGraph: Can't create 'radical axis' with parent types '" +
    1641                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    1642                 "\nPossible parent type: [circle,circle]");
    1643         }
    1644
    1645         el1 = board.select(parents[0]);
    1646         el2 = board.select(parents[1]);
    1647
    1648         el = board.create('line', [function () {
    1649             var a = el1.stdform,
    1650                 b = el2.stdform;
    1651
    1652             return Mat.matVecMult(Mat.transpose([a.slice(0, 3), b.slice(0, 3)]), [b[3], -a[3]]);
    1653         }], attributes);
    1654
    1655         el.elType = 'radicalaxis';
    1656         el.setParents([el1.id, el2.id]);
    1657
    1658         el1.addChild(el);
    1659         el2.addChild(el);
    1660
    1661         return el;
    1662     };
    1663
    1664     /**
    1665      * @class This element is used to provide a constructor for the polar line of a point with respect to a conic or a circle.
    1666      * @pseudo
    1667      * @description The polar line is the unique reciprocal relationship of a point with respect to a conic.
    1668      * The lines through the intersections of a conic and the polar line of a point with respect to that conic and through that point are tangent to the conic.
    1669      * A point on a conic has the polar line of that point with respect to that conic as the tangent line to that conic at that point.
    1670      * See {@link http://en.wikipedia.org/wiki/Pole_and_polar} for more information on pole and polar.
    1671      * @name PolarLine
    1672      * @augments JXG.Line
    1673      * @constructor
    1674      * @type JXG.Line
    1675      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1676      * @param {JXG.Conic,JXG.Circle_JXG.Point} el1,el2 or
    1677      * @param {JXG.Point_JXG.Conic,JXG.Circle} el1,el2 The result will be the polar line of the point with respect to the conic or the circle.
    1678      * @example
    1679      * // Create the polar line of a point with respect to a conic
    1680      * var p1 = board.create('point', [-1, 2]);
    1681      * var p2 = board.create('point', [ 1, 4]);
    1682      * var p3 = board.create('point', [-1,-2]);
    1683      * var p4 = board.create('point', [ 0, 0]);
    1684      * var p5 = board.create('point', [ 4,-2]);
    1685      * var c1 = board.create('conic',[p1,p2,p3,p4,p5]);
    1686      * var p6 = board.create('point', [-1, 1]);
    1687      * var l1 = board.create('polarline', [c1, p6]);
    1688      * </pre><div class="jxgbox" id='7b7233a0-f363-47dd-9df5-6018d0d17a98' class='jxgbox' style='width:400px; height:400px;'></div>
    1689      * <script type='text/javascript'>
    1690      * var plex1_board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-6018d0d17a98', {boundingbox: [-3, 5, 5, -3], axis: true, showcopyright: false, shownavigation: false});
    1691      * var plex1_p1 = plex1_board.create('point', [-1, 2]);
    1692      * var plex1_p2 = plex1_board.create('point', [ 1, 4]);
    1693      * var plex1_p3 = plex1_board.create('point', [-1,-2]);
    1694      * var plex1_p4 = plex1_board.create('point', [ 0, 0]);
    1695      * var plex1_p5 = plex1_board.create('point', [ 4,-2]);
    1696      * var plex1_c1 = plex1_board.create('conic',[plex1_p1,plex1_p2,plex1_p3,plex1_p4,plex1_p5]);
    1697      * var plex1_p6 = plex1_board.create('point', [-1, 1]);
    1698      * var plex1_l1 = plex1_board.create('polarline', [plex1_c1, plex1_p6]);
    1699      * </script><pre>
    1700      * @example
    1701      * // Create the polar line of a point with respect to a circle.
    1702      * var p1 = board.create('point', [ 1, 1]);
    1703      * var p2 = board.create('point', [ 2, 3]);
    1704      * var c1 = board.create('circle',[p1,p2]);
    1705      * var p3 = board.create('point', [ 6, 6]);
    1706      * var l1 = board.create('polarline', [c1, p3]);
    1707      * </pre><div class="jxgbox" id='7b7233a0-f363-47dd-9df5-7018d0d17a98' class='jxgbox' style='width:400px; height:400px;'></div>
    1708      * <script type='text/javascript'>
    1709      * var plex2_board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-7018d0d17a98', {boundingbox: [-3, 7, 7, -3], axis: true, showcopyright: false, shownavigation: false});
    1710      * var plex2_p1 = plex2_board.create('point', [ 1, 1]);
    1711      * var plex2_p2 = plex2_board.create('point', [ 2, 3]);
    1712      * var plex2_c1 = plex2_board.create('circle',[plex2_p1,plex2_p2]);
    1713      * var plex2_p3 = plex2_board.create('point', [ 6, 6]);
    1714      * var plex2_l1 = plex2_board.create('polarline', [plex2_c1, plex2_p3]);
    1715      * </script><pre>
    1716      */
    1717     JXG.createPolarLine = function (board, parents, attributes) {
    1718         var el, el1, el2,
    1719             firstParentIsConic, secondParentIsConic,
    1720             firstParentIsPoint, secondParentIsPoint;
    1721
    1722         if (parents.length > 1) {
    1723             firstParentIsConic = (parents[0].type === Const.OBJECT_TYPE_CONIC ||
    1724                 parents[0].elementClass === Const.OBJECT_CLASS_CIRCLE);
    1725             secondParentIsConic = (parents[1].type === Const.OBJECT_TYPE_CONIC ||
    1726                 parents[1].elementClass === Const.OBJECT_CLASS_CIRCLE);
    1727
    1728             firstParentIsPoint = (Type.isPoint(parents[0]));
    1729             secondParentIsPoint = (Type.isPoint(parents[1]));
    1730         }
    1731
    1732         if (parents.length !== 2 ||
    1733                 !((firstParentIsConic && secondParentIsPoint) ||
    1734                     (firstParentIsPoint && secondParentIsConic))) {
    1735             // Failure
    1736             throw new Error("JSXGraph: Can't create 'polar line' with parent types '" +
    1737                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    1738                 "\nPossible parent type: [conic|circle,point], [point,conic|circle]");
    1739         }
    1740
    1741         if (secondParentIsPoint) {
    1742             el1 = board.select(parents[0]);
    1743             el2 = board.select(parents[1]);
    1744         } else {
    1745             el1 = board.select(parents[1]);
    1746             el2 = board.select(parents[0]);
    1747         }
    1748
    1749         // Polar lines have been already provided in the tangent element.
    1750         el = board.create('tangent', [el1, el2], attributes);
    1751
    1752         el.elType = 'polarline';
    1753         return el;
    1754     };
    1755
    1756     /**
    1757      * Register the element type tangent at JSXGraph
    1758      * @private
    1759      */
    1760     JXG.registerElement('tangent', JXG.createTangent);
    1761     JXG.registerElement('polar', JXG.createTangent);
    1762     JXG.registerElement('radicalaxis', JXG.createRadicalAxis);
    1763     JXG.registerElement('polarline', JXG.createPolarLine);
    1764
    1765     return {
    1766         Line: JXG.Line,
    1767         createLine: JXG.createLine,
    1768         createTangent: JXG.createTangent,
    1769         createPolar: JXG.createTangent,
    1770         createSegment: JXG.createSegment,
    1771         createAxis: JXG.createAxis,
    1772         createArrow: JXG.createArrow,
    1773         createRadicalAxis: JXG.createRadicalAxis,
    1774         createPolarLine: JXG.createPolarLine
    1775     };
    1776 });
    1777