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