      1 /*
      2     Copyright 2008-2024
      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     Some functionalities in this file were developed as part of a software project
     33     with students. We would like to thank all contributors for their help:
     34
     35     Winter semester 2023/2024:
     36         Matti Kirchbach
     37  */
     38
     39 /*global JXG: true, define: true*/
     40 /*jslint nomen: true, plusplus: true*/
     41
     42 /**
     43  * @fileoverview The geometry object Line is defined in this file. Line stores all
     44  * style and functional properties that are required to draw and move a line on
     45  * a board.
     46  */
     47
     48 import JXG from "../jxg.js";
     49 import Mat from "../math/math.js";
     50 import Geometry from "../math/geometry.js";
     51 import Numerics from "../math/numerics.js";
     52 import Statistics from "../math/statistics.js";
     53 import Const from "./constants.js";
     54 import Coords from "./coords.js";
     55 import GeometryElement from "./element.js";
     56 import Type from "../utils/type.js";
     57
     58 /**
     59  * 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
     60  * be intersected with some other geometry elements.
     61  * @class Creates a new basic line object. Do not use this constructor to create a line.
     62  * Use {@link JXG.Board#create} with
     63  * type {@link Line}, {@link Arrow}, or {@link Axis} instead.
     64  * @constructor
     65  * @augments JXG.GeometryElement
     66  * @param {String|JXG.Board} board The board the new line is drawn on.
     67  * @param {Point} p1 Startpoint of the line.
     68  * @param {Point} p2 Endpoint of the line.
     69  * @param {Object} attributes Javascript object containing attributes like name, id and colors.
     70  */
     71 JXG.Line = function (board, p1, p2, attributes) {
     72     this.constructor(board, attributes, Const.OBJECT_TYPE_LINE, Const.OBJECT_CLASS_LINE);
     73
     74     /**
     75      * Startpoint of the line. You really should not set this field directly as it may break JSXGraph's
     76      * update system so your construction won't be updated properly.
     77      * @type JXG.Point
     78      */
     79     this.point1 = this.board.select(p1);
     80
     81     /**
     82      * Endpoint of the line. Just like {@link JXG.Line.point1} you shouldn't write this field directly.
     83      * @type JXG.Point
     84      */
     85     this.point2 = this.board.select(p2);
     86
     87     /**
     88      * Array of ticks storing all the ticks on this line. Do not set this field directly and use
     89      * {@link JXG.Line#addTicks} and {@link JXG.Line#removeTicks} to add and remove ticks to and from the line.
     90      * @type Array
     91      * @see JXG.Ticks
     92      */
     93     this.ticks = [];
     94
     95     /**
     96      * Reference of the ticks created automatically when constructing an axis.
     97      * @type JXG.Ticks
     98      * @see JXG.Ticks
     99      */
    100     this.defaultTicks = null;
    101
    102     /**
    103      * If the line is the border of a polygon, the polygon object is stored, otherwise null.
    104      * @type JXG.Polygon
    105      * @default null
    106      * @private
    107      */
    108     this.parentPolygon = null;
    109
    110     /* Register line at board */
    111     this.id = this.board.setId(this, "L");
    112     this.board.renderer.drawLine(this);
    113     this.board.finalizeAdding(this);
    114
    115     this.elType = "line";
    116
    117     /* Add line as child to defining points */
    118     if (this.point1._is_new) {
    119         this.addChild(this.point1);
    120         delete this.point1._is_new;
    121     } else {
    122         this.point1.addChild(this);
    123     }
    124     if (this.point2._is_new) {
    125         this.addChild(this.point2);
    126         delete this.point2._is_new;
    127     } else {
    128         this.point2.addChild(this);
    129     }
    130
    131     this.inherits.push(this.point1, this.point2);
    132
    133     this.updateStdform(); // This is needed in the following situation:
    134     // * the line is defined by three coordinates
    135     // * and it will have a glider
    136     // * and board.suspendUpdate() has been called.
    137
    138     // create Label
    139     this.createLabel();
    140
    141     this.methodMap = JXG.deepCopy(this.methodMap, {
    142         point1: "point1",
    143         point2: "point2",
    144         getSlope: "Slope",
    145         Slope: "Slope",
    146         Direction: "Direction",
    147         getRise: "getRise",
    148         Rise: "getRise",
    149         getYIntersect: "getRise",
    150         YIntersect: "getRise",
    151         getAngle: "getAngle",
    152         Angle: "getAngle",
    153         L: "L",
    154         length: "L",
    155         setFixedLength: "setFixedLength",
    156         setStraight: "setStraight"
    157     });
    158 };
    159
    160 JXG.Line.prototype = new GeometryElement();
    161
    162 JXG.extend(
    163     JXG.Line.prototype,
    164     /** @lends JXG.Line.prototype */ {
    165         /**
    166          * Checks whether (x,y) is near the line.
    167          * @param {Number} x Coordinate in x direction, screen coordinates.
    168          * @param {Number} y Coordinate in y direction, screen coordinates.
    169          * @returns {Boolean} True if (x,y) is near the line, False otherwise.
    170          */
    171         hasPoint: function (x, y) {
    172             // Compute the stdform of the line in screen coordinates.
    173             var c = [],
    174                 v = [1, x, y],
    175                 s, vnew, p1c, p2c, d, pos, i, prec, type,
    176                 sw = Type.evaluate(this.visProp.strokewidth);
    177
    178             if (Type.isObject(Type.evaluate(this.visProp.precision))) {
    179                 type = this.board._inputDevice;
    180                 prec = Type.evaluate(this.visProp.precision[type]);
    181             } else {
    182                 // 'inherit'
    183                 prec = this.board.options.precision.hasPoint;
    184             }
    185             prec += sw * 0.5;
    186
    187             c[0] =
    188                 this.stdform[0] -
    189                 (this.stdform[1] * this.board.origin.scrCoords[1]) / this.board.unitX +
    190                 (this.stdform[2] * this.board.origin.scrCoords[2]) / this.board.unitY;
    191             c[1] = this.stdform[1] / this.board.unitX;
    192             c[2] = this.stdform[2] / -this.board.unitY;
    193
    194             s = Geometry.distPointLine(v, c);
    195             if (isNaN(s) || s > prec) {
    196                 return false;
    197             }
    198
    199             if (
    200                 Type.evaluate(this.visProp.straightfirst) &&
    201                 Type.evaluate(this.visProp.straightlast)
    202             ) {
    203                 return true;
    204             }
    205
    206             // If the line is a ray or segment we have to check if the projected point is between P1 and P2.
    207             p1c = this.point1.coords;
    208             p2c = this.point2.coords;
    209
    210             // Project the point orthogonally onto the line
    211             vnew = [0, c[1], c[2]];
    212             // Orthogonal line to c through v
    213             vnew = Mat.crossProduct(vnew, v);
    214             // Intersect orthogonal line with line
    215             vnew = Mat.crossProduct(vnew, c);
    216
    217             // Normalize the projected point
    218             vnew[1] /= vnew[0];
    219             vnew[2] /= vnew[0];
    220             vnew[0] = 1;
    221
    222             vnew = new Coords(Const.COORDS_BY_SCREEN, vnew.slice(1), this.board).usrCoords;
    223             d = p1c.distance(Const.COORDS_BY_USER, p2c);
    224             p1c = p1c.usrCoords.slice(0);
    225             p2c = p2c.usrCoords.slice(0);
    226
    227             // The defining points are identical
    228             if (d < Mat.eps) {
    229                 pos = 0;
    230             } else {
    231                 /*
    232                  * Handle the cases, where one of the defining points is an ideal point.
    233                  * d is set to something close to infinity, namely 1/eps.
    234                  * The ideal point is (temporarily) replaced by a finite point which has
    235                  * distance d from the other point.
    236                  * This is accomplished by extracting the x- and y-coordinates (x,y)=:v of the ideal point.
    237                  * v determines the direction of the line. v is normalized, i.e. set to length 1 by dividing through its length.
    238                  * Finally, the new point is the sum of the other point and v*d.
    239                  *
    240                  */
    241
    242                 // At least one point is an ideal point
    243                 if (d === Number.POSITIVE_INFINITY) {
    244                     d = 1 / Mat.eps;
    245
    246                     // The second point is an ideal point
    247                     if (Math.abs(p2c[0]) < Mat.eps) {
    248                         d /= Geometry.distance([0, 0, 0], p2c);
    249                         p2c = [1, p1c[1] + p2c[1] * d, p1c[2] + p2c[2] * d];
    250                         // The first point is an ideal point
    251                     } else {
    252                         d /= Geometry.distance([0, 0, 0], p1c);
    253                         p1c = [1, p2c[1] + p1c[1] * d, p2c[2] + p1c[2] * d];
    254                     }
    255                 }
    256                 i = 1;
    257                 d = p2c[i] - p1c[i];
    258
    259                 if (Math.abs(d) < Mat.eps) {
    260                     i = 2;
    261                     d = p2c[i] - p1c[i];
    262                 }
    263                 pos = (vnew[i] - p1c[i]) / d;
    264             }
    265
    266             if (!Type.evaluate(this.visProp.straightfirst) && pos < 0) {
    267                 return false;
    268             }
    269
    270             return !(!Type.evaluate(this.visProp.straightlast) && pos > 1);
    271         },
    272
    273         // documented in base/element
    274         update: function () {
    275             var funps;
    276
    277             if (!this.needsUpdate) {
    278                 return this;
    279             }
    280
    281             if (this.constrained) {
    282                 if (Type.isFunction(this.funps)) {
    283                     funps = this.funps();
    284                     if (funps && funps.length && funps.length === 2) {
    285                         this.point1 = funps[0];
    286                         this.point2 = funps[1];
    287                     }
    288                 } else {
    289                     if (Type.isFunction(this.funp1)) {
    290                         funps = this.funp1();
    291                         if (Type.isPoint(funps)) {
    292                             this.point1 = funps;
    293                         } else if (funps && funps.length && funps.length === 2) {
    294                             this.point1.setPositionDirectly(Const.COORDS_BY_USER, funps);
    295                         }
    296                     }
    297
    298                     if (Type.isFunction(this.funp2)) {
    299                         funps = this.funp2();
    300                         if (Type.isPoint(funps)) {
    301                             this.point2 = funps;
    302                         } else if (funps && funps.length && funps.length === 2) {
    303                             this.point2.setPositionDirectly(Const.COORDS_BY_USER, funps);
    304                         }
    305                     }
    306                 }
    307             }
    308
    309             this.updateSegmentFixedLength();
    310             this.updateStdform();
    311
    312             if (Type.evaluate(this.visProp.trace)) {
    313                 this.cloneToBackground(true);
    314             }
    315
    316             return this;
    317         },
    318
    319         /**
    320          * Update segments with fixed length and at least one movable point.
    321          * @private
    322          */
    323         updateSegmentFixedLength: function () {
    324             var d, d_new, d1, d2, drag1, drag2, x, y;
    325
    326             if (!this.hasFixedLength) {
    327                 return this;
    328             }
    329
    330             // Compute the actual length of the segment
    331             d = this.point1.Dist(this.point2);
    332             // Determine the length the segment ought to have
    333             d_new = (Type.evaluate(this.visProp.nonnegativeonly)) ?
    334                 Math.max(0.0, this.fixedLength()) :
    335                 Math.abs(this.fixedLength());
    336
    337             // Distances between the two points and their respective
    338             // position before the update
    339             d1 = this.fixedLengthOldCoords[0].distance(
    340                 Const.COORDS_BY_USER,
    341                 this.point1.coords
    342             );
    343             d2 = this.fixedLengthOldCoords[1].distance(
    344                 Const.COORDS_BY_USER,
    345                 this.point2.coords
    346             );
    347
    348             // If the position of the points or the fixed length function has been changed we have to work.
    349             if (d1 > Mat.eps || d2 > Mat.eps || d !== d_new) {
    350                 drag1 =
    351                     this.point1.isDraggable &&
    352                     this.point1.type !== Const.OBJECT_TYPE_GLIDER &&
    353                     !Type.evaluate(this.point1.visProp.fixed);
    354                 drag2 =
    355                     this.point2.isDraggable &&
    356                     this.point2.type !== Const.OBJECT_TYPE_GLIDER &&
    357                     !Type.evaluate(this.point2.visProp.fixed);
    358
    359                 // First case: the two points are different
    360                 // Then we try to adapt the point that was not dragged
    361                 // If this point can not be moved (e.g. because it is a glider)
    362                 // we try move the other point
    363                 if (d > Mat.eps) {
    364                     if ((d1 > d2 && drag2) || (d1 <= d2 && drag2 && !drag1)) {
    365                         this.point2.setPositionDirectly(Const.COORDS_BY_USER, [
    366                             this.point1.X() + ((this.point2.X() - this.point1.X()) * d_new) / d,
    367                             this.point1.Y() + ((this.point2.Y() - this.point1.Y()) * d_new) / d
    368                         ]);
    369                         this.point2.fullUpdate();
    370                     } else if ((d1 <= d2 && drag1) || (d1 > d2 && drag1 && !drag2)) {
    371                         this.point1.setPositionDirectly(Const.COORDS_BY_USER, [
    372                             this.point2.X() + ((this.point1.X() - this.point2.X()) * d_new) / d,
    373                             this.point2.Y() + ((this.point1.Y() - this.point2.Y()) * d_new) / d
    374                         ]);
    375                         this.point1.fullUpdate();
    376                     }
    377                     // Second case: the two points are identical. In this situation
    378                     // we choose a random direction.
    379                 } else {
    380                     x = Math.random() - 0.5;
    381                     y = Math.random() - 0.5;
    382                     d = Mat.hypot(x, y);
    383
    384                     if (drag2) {
    385                         this.point2.setPositionDirectly(Const.COORDS_BY_USER, [
    386                             this.point1.X() + (x * d_new) / d,
    387                             this.point1.Y() + (y * d_new) / d
    388                         ]);
    389                         this.point2.fullUpdate();
    390                     } else if (drag1) {
    391                         this.point1.setPositionDirectly(Const.COORDS_BY_USER, [
    392                             this.point2.X() + (x * d_new) / d,
    393                             this.point2.Y() + (y * d_new) / d
    394                         ]);
    395                         this.point1.fullUpdate();
    396                     }
    397                 }
    398                 // Finally, we save the position of the two points.
    399                 this.fixedLengthOldCoords[0].setCoordinates(
    400                     Const.COORDS_BY_USER,
    401                     this.point1.coords.usrCoords
    402                 );
    403                 this.fixedLengthOldCoords[1].setCoordinates(
    404                     Const.COORDS_BY_USER,
    405                     this.point2.coords.usrCoords
    406                 );
    407             }
    408
    409             return this;
    410         },
    411
    412         /**
    413          * Updates the stdform derived from the parent point positions.
    414          * @private
    415          */
    416         updateStdform: function () {
    417             var v = Mat.crossProduct(
    418                 this.point1.coords.usrCoords,
    419                 this.point2.coords.usrCoords
    420             );
    421
    422             this.stdform[0] = v[0];
    423             this.stdform[1] = v[1];
    424             this.stdform[2] = v[2];
    425             this.stdform[3] = 0;
    426
    427             this.normalize();
    428         },
    429
    430         /**
    431          * Uses the boards renderer to update the line.
    432          * @private
    433          */
    434         updateRenderer: function () {
    435             //var wasReal;
    436
    437             if (!this.needsUpdate) {
    438                 return this;
    439             }
    440
    441             if (this.visPropCalc.visible) {
    442                 // wasReal = this.isReal;
    443                 this.isReal =
    444                     !isNaN(
    445                         this.point1.coords.usrCoords[1] +
    446                         this.point1.coords.usrCoords[2] +
    447                         this.point2.coords.usrCoords[1] +
    448                         this.point2.coords.usrCoords[2]
    449                     ) && Mat.innerProduct(this.stdform, this.stdform, 3) >= Mat.eps * Mat.eps;
    450
    451                 if (
    452                     //wasReal &&
    453                     !this.isReal
    454                 ) {
    455                     this.updateVisibility(false);
    456                 }
    457             }
    458
    459             if (this.visPropCalc.visible) {
    460                 this.board.renderer.updateLine(this);
    461             }
    462
    463             /* Update the label if visible. */
    464             if (
    465                 this.hasLabel &&
    466                 this.visPropCalc.visible &&
    467                 this.label &&
    468                 this.label.visPropCalc.visible &&
    469                 this.isReal
    470             ) {
    471                 this.label.update();
    472                 this.board.renderer.updateText(this.label);
    473             }
    474
    475             // Update rendNode display
    476             this.setDisplayRendNode();
    477
    478             this.needsUpdate = false;
    479             return this;
    480         },
    481
    482         // /**
    483         //  * Used to generate a polynomial for a point p that lies on this line, i.e. p is collinear to
    484         //  * {@link JXG.Line#point1} and {@link JXG.Line#point2}.
    485         //  *
    486         //  * @param {JXG.Point} p The point for that the polynomial is generated.
    487         //  * @returns {Array} An array containing the generated polynomial.
    488         //  * @private
    489         //  */
    490         generatePolynomial: function (p) {
    491             var u1 = this.point1.symbolic.x,
    492                 u2 = this.point1.symbolic.y,
    493                 v1 = this.point2.symbolic.x,
    494                 v2 = this.point2.symbolic.y,
    495                 w1 = p.symbolic.x,
    496                 w2 = p.symbolic.y;
    497
    498             /*
    499              * The polynomial in this case is determined by three points being collinear:
    500              *
    501              *      U (u1,u2)      W (w1,w2)                V (v1,v2)
    502              *  ----x--------------x------------------------x----------------
    503              *
    504              *  The collinearity condition is
    505              *
    506              *      u2-w2       w2-v2
    507              *     -------  =  -------           (1)
    508              *      u1-w1       w1-v1
    509              *
    510              * Multiplying (1) with denominators and simplifying is
    511              *
    512              *    u2w1 - u2v1 + w2v1 - u1w2 + u1v2 - w1v2 = 0
    513              */
    514
    515             return [
    516                 [
    517                     "(", u2, ")*(", w1, ")-(", u2, ")*(", v1, ")+(", w2, ")*(", v1, ")-(", u1, ")*(", w2, ")+(", u1, ")*(", v2, ")-(", w1, ")*(", v2, ")"
    518                 ].join("")
    519             ];
    520         },
    521
    522         /**
    523          * Calculates the y intersect of the line.
    524          * @returns {Number} The y intersect.
    525          */
    526         getRise: function () {
    527             if (Math.abs(this.stdform[2]) >= Mat.eps) {
    528                 return -this.stdform[0] / this.stdform[2];
    529             }
    530
    531             return Infinity;
    532         },
    533
    534         /**
    535          * Calculates the slope of the line.
    536          * @returns {Number} The slope of the line or Infinity if the line is parallel to the y-axis.
    537          */
    538         Slope: function () {
    539             if (Math.abs(this.stdform[2]) >= Mat.eps) {
    540                 return -this.stdform[1] / this.stdform[2];
    541             }
    542
    543             return Infinity;
    544         },
    545
    546         /**
    547          * Alias for line.Slope
    548          * @returns {Number} The slope of the line or Infinity if the line is parallel to the y-axis.
    549          * @deprecated
    550          * @see #Slope
    551          */
    552         getSlope: function () {
    553             return this.Slope();
    554         },
    555
    556         /**
    557          * Determines the angle between the positive x axis and the line.
    558          * @returns {Number}
    559          */
    560         getAngle: function () {
    561             return Math.atan2(-this.stdform[1], this.stdform[2]);
    562         },
    563
    564         /**
    565          * Returns the direction vector of the line. This is an array of length two
    566          * containing the direction vector as [x, y]. It is defined as
    567          *  <li> the difference of the x- and y-coordinate of the second and first point, in case both points are finite or both points are infinite.
    568          *  <li> [x, y] coordinates of point2, in case only point2 is infinite.
    569          *  <li> [-x, -y] coordinates of point1, in case only point1 is infinite.
    570          * @function
    571          * @returns {Array} of length 2.
    572          */
    573         Direction: function () {
    574             var coords1 = this.point1.coords.usrCoords,
    575                 coords2 = this.point2.coords.usrCoords;
    576
    577             if (coords2[0] === 0 && coords1[0] !== 0) {
    578                 return coords2.slice(1);
    579             }
    580
    581             if (coords1[0] === 0 && coords2[0] !== 0) {
    582                 return [-coords1[1], -coords1[2]];
    583             }
    584
    585             return [
    586                 coords2[1] - coords1[1],
    587                 coords2[2] - coords1[2]
    588             ];
    589         },
    590
    591         /**
    592          * Returns true, if the line is vertical (if the x coordinate of the direction vector is 0).
    593          * @function
    594          * @returns {Boolean}
    595          */
    596         isVertical: function () {
    597             var dir = this.Direction();
    598             return dir[0] === 0 && dir[1] !== 0;
    599         },
    600
    601         /**
    602          * Returns true, if the line is horizontal (if the y coordinate of the direction vector is 0).
    603          * @function
    604          * @returns {Boolean}
    605          */
    606         isHorizontal: function () {
    607             var dir = this.Direction();
    608             return dir[1] === 0 && dir[0] !== 0;
    609         },
    610
    611         /**
    612          * Determines whether the line is drawn beyond {@link JXG.Line#point1} and
    613          * {@link JXG.Line#point2} and updates the line.
    614          * @param {Boolean} straightFirst True if the Line shall be drawn beyond
    615          * {@link JXG.Line#point1}, false otherwise.
    616          * @param {Boolean} straightLast True if the Line shall be drawn beyond
    617          * {@link JXG.Line#point2}, false otherwise.
    618          * @see #straightFirst
    619          * @see #straightLast
    620          * @private
    621          */
    622         setStraight: function (straightFirst, straightLast) {
    623             this.visProp.straightfirst = straightFirst;
    624             this.visProp.straightlast = straightLast;
    625
    626             this.board.renderer.updateLine(this);
    627             return this;
    628         },
    629
    630         // documented in geometry element
    631         getTextAnchor: function () {
    632             return new Coords(
    633                 Const.COORDS_BY_USER,
    634                 [
    635                     0.5 * (this.point2.X() + this.point1.X()),
    636                     0.5 * (this.point2.Y() + this.point1.Y())
    637                 ],
    638                 this.board
    639             );
    640         },
    641
    642         /**
    643          * Adjusts Label coords relative to Anchor. DESCRIPTION
    644          * @private
    645          */
    646         setLabelRelativeCoords: function (relCoords) {
    647             if (Type.exists(this.label)) {
    648                 this.label.relativeCoords = new Coords(
    649                     Const.COORDS_BY_SCREEN,
    650                     [relCoords[0], -relCoords[1]],
    651                     this.board
    652                 );
    653             }
    654         },
    655
    656         // documented in geometry element
    657         getLabelAnchor: function () {
    658             var x, y, pos,
    659                 xy, lbda, dx, dy, d,
    660                 dist = 1.5,
    661                 fs = 0,
    662                 c1 = new Coords(Const.COORDS_BY_USER, this.point1.coords.usrCoords, this.board),
    663                 c2 = new Coords(Const.COORDS_BY_USER, this.point2.coords.usrCoords, this.board),
    664                 ev_sf = Type.evaluate(this.visProp.straightfirst),
    665                 ev_sl = Type.evaluate(this.visProp.straightlast);
    666
    667             if (ev_sf || ev_sl) {
    668                 Geometry.calcStraight(this, c1, c2, 0);
    669             }
    670
    671             c1 = c1.scrCoords;
    672             c2 = c2.scrCoords;
    673
    674             if (!Type.exists(this.label)) {
    675                 return new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], this.board);
    676             }
    677
    678             pos = Type.evaluate(this.label.visProp.position);
    679             if (!Type.isString(pos)) {
    680                 return new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], this.board);
    681             }
    682
    683             if (pos.indexOf('right') < 0 && pos.indexOf('left') < 0) {
    684                 // Old positioning commands
    685                 switch (pos) {
    686                     case 'last':
    687                         x = c2[1];
    688                         y = c2[2];
    689                         break;
    690                     case 'first':
    691                         x = c1[1];
    692                         y = c1[2];
    693                         break;
    694                     case "lft":
    695                     case "llft":
    696                     case "ulft":
    697                         if (c1[1] < c2[1] + Mat.eps) {
    698                             x = c1[1];
    699                             y = c1[2];
    700                         } else {
    701                             x = c2[1];
    702                             y = c2[2];
    703                         }
    704                         break;
    705                     case "rt":
    706                     case "lrt":
    707                     case "urt":
    708                         if (c1[1] > c2[1] + Mat.eps) {
    709                             x = c1[1];
    710                             y = c1[2];
    711                         } else {
    712                             x = c2[1];
    713                             y = c2[2];
    714                         }
    715                         break;
    716                     default:
    717                         x = 0.5 * (c1[1] + c2[1]);
    718                         y = 0.5 * (c1[2] + c2[2]);
    719                 }
    720             } else {
    721                 // New positioning
    722                 xy = Type.parsePosition(pos);
    723                 lbda = Type.parseNumber(xy.pos, 1, 1);
    724
    725                 dx = c2[1] - c1[1];
    726                 dy = c2[2] - c1[2];
    727                 d = Mat.hypot(dx, dy);
    728
    729                 if (xy.pos.indexOf('px') >= 0 ||
    730                     xy.pos.indexOf('fr') >= 0 ||
    731                     xy.pos.indexOf('%') >= 0) {
    732                     // lbda is interpreted in screen coords
    733
    734                     if (xy.pos.indexOf('px') >= 0) {
    735                         // Pixel values are supported
    736                         lbda /= d;
    737                     }
    738
    739                     // Position along the line
    740                     x = c1[1] + lbda * dx;
    741                     y = c1[2] + lbda * dy;
    742                 } else {
    743                     // lbda is given as number or as a number string
    744                     // Then, lbda is interpreted in user coords
    745                     x = c1[1] + lbda * this.board.unitX * dx / d;
    746                     y = c1[2] + lbda * this.board.unitY * dy / d;
    747                 }
    748
    749                 // Position left or right
    750                 if (xy.side === 'left') {
    751                     dx *= -1;
    752                 } else {
    753                     dy *= -1;
    754                 }
    755                 if (Type.exists(this.label)) {
    756                     dist = 0.5 * Type.evaluate(this.label.visProp.distance) / d;
    757                 }
    758                 x += dy * this.label.size[0] * dist;
    759                 y += dx * this.label.size[1] * dist;
    760             }
    761
    762             // Correct coordinates if the label seems to be outside of canvas.
    763             if (ev_sf || ev_sl) {
    764                 if (Type.exists(this.label)) {
    765                     // Does not exist during createLabel
    766                     fs = Type.evaluate(this.label.visProp.fontsize);
    767                 }
    768
    769                 if (Math.abs(x) < Mat.eps) {
    770                     x = fs;
    771                 } else if (
    772                     this.board.canvasWidth + Mat.eps > x &&
    773                     x > this.board.canvasWidth - fs - Mat.eps
    774                 ) {
    775                     x = this.board.canvasWidth - fs;
    776                 }
    777
    778                 if (Mat.eps + fs > y && y > -Mat.eps) {
    779                     y = fs;
    780                 } else if (
    781                     this.board.canvasHeight + Mat.eps > y &&
    782                     y > this.board.canvasHeight - fs - Mat.eps
    783                 ) {
    784                     y = this.board.canvasHeight - fs;
    785                 }
    786             }
    787
    788             return new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board);
    789         },
    790
    791         // documented in geometry element
    792         cloneToBackground: function () {
    793             var copy = {},
    794                 r,
    795                 s,
    796                 er;
    797
    798             copy.id = this.id + "T" + this.numTraces;
    799             copy.elementClass = Const.OBJECT_CLASS_LINE;
    800             this.numTraces++;
    801             copy.point1 = this.point1;
    802             copy.point2 = this.point2;
    803
    804             copy.stdform = this.stdform;
    805
    806             copy.board = this.board;
    807
    808             copy.visProp = Type.deepCopy(this.visProp, this.visProp.traceattributes, true);
    809             copy.visProp.layer = this.board.options.layer.trace;
    810             Type.clearVisPropOld(copy);
    811             copy.visPropCalc = {
    812                 visible: Type.evaluate(copy.visProp.visible)
    813             };
    814
    815             s = this.getSlope();
    816             r = this.getRise();
    817             copy.getSlope = function () {
    818                 return s;
    819             };
    820             copy.getRise = function () {
    821                 return r;
    822             };
    823
    824             er = this.board.renderer.enhancedRendering;
    825             this.board.renderer.enhancedRendering = true;
    826             this.board.renderer.drawLine(copy);
    827             this.board.renderer.enhancedRendering = er;
    828             this.traces[copy.id] = copy.rendNode;
    829
    830             return this;
    831         },
    832
    833         /**
    834          * Add transformations to this line.
    835          * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of
    836          * {@link JXG.Transformation}s.
    837          * @returns {JXG.Line} Reference to this line object.
    838          */
    839         addTransform: function (transform) {
    840             var i,
    841                 list = Type.isArray(transform) ? transform : [transform],
    842                 len = list.length;
    843
    844             for (i = 0; i < len; i++) {
    845                 this.point1.transformations.push(list[i]);
    846                 this.point2.transformations.push(list[i]);
    847             }
    848
    849             // Why not like this?
    850             // The difference is in setting baseElement
    851             // var list = Type.isArray(transform) ? transform : [transform];
    852             // this.point1.addTransform(this, list);
    853             // this.point2.addTransform(this, list);
    854
    855             return this;
    856         },
    857
    858         // see GeometryElement.js
    859         snapToGrid: function (pos) {
    860             var c1, c2, dc, t, ticks, x, y, sX, sY;
    861
    862             if (Type.evaluate(this.visProp.snaptogrid)) {
    863                 if (this.parents.length < 3) {
    864                     // Line through two points
    865                     this.point1.handleSnapToGrid(true, true);
    866                     this.point2.handleSnapToGrid(true, true);
    867                 } else if (Type.exists(pos)) {
    868                     // Free line
    869                     sX = Type.evaluate(this.visProp.snapsizex);
    870                     sY = Type.evaluate(this.visProp.snapsizey);
    871
    872                     c1 = new Coords(Const.COORDS_BY_SCREEN, [pos.Xprev, pos.Yprev], this.board);
    873
    874                     x = c1.usrCoords[1];
    875                     y = c1.usrCoords[2];
    876
    877                     if (
    878                         sX <= 0 &&
    879                         this.board.defaultAxes &&
    880                         this.board.defaultAxes.x.defaultTicks
    881                     ) {
    882                         ticks = this.board.defaultAxes.x.defaultTicks;
    883                         sX = ticks.ticksDelta * (Type.evaluate(ticks.visProp.minorticks) + 1);
    884                     }
    885                     if (
    886                         sY <= 0 &&
    887                         this.board.defaultAxes &&
    888                         this.board.defaultAxes.y.defaultTicks
    889                     ) {
    890                         ticks = this.board.defaultAxes.y.defaultTicks;
    891                         sY = ticks.ticksDelta * (Type.evaluate(ticks.visProp.minorticks) + 1);
    892                     }
    893
    894                     // if no valid snap sizes are available, don't change the coords.
    895                     if (sX > 0 && sY > 0) {
    896                         // projectCoordsToLine
    897                         /*
    898                         v = [0, this.stdform[1], this.stdform[2]];
    899                         v = Mat.crossProduct(v, c1.usrCoords);
    900                         c2 = Geometry.meetLineLine(v, this.stdform, 0, this.board);
    901                         */
    902                         c2 = Geometry.projectPointToLine({ coords: c1 }, this, this.board);
    903
    904                         dc = Statistics.subtract(
    905                             [1, Math.round(x / sX) * sX, Math.round(y / sY) * sY],
    906                             c2.usrCoords
    907                         );
    908                         t = this.board.create("transform", dc.slice(1), {
    909                             type: "translate"
    910                         });
    911                         t.applyOnce([this.point1, this.point2]);
    912                     }
    913                 }
    914             } else {
    915                 this.point1.handleSnapToGrid(false, true);
    916                 this.point2.handleSnapToGrid(false, true);
    917             }
    918
    919             return this;
    920         },
    921
    922         // see element.js
    923         snapToPoints: function () {
    924             var forceIt = Type.evaluate(this.visProp.snaptopoints);
    925
    926             if (this.parents.length < 3) {
    927                 // Line through two points
    928                 this.point1.handleSnapToPoints(forceIt);
    929                 this.point2.handleSnapToPoints(forceIt);
    930             }
    931
    932             return this;
    933         },
    934
    935         /**
    936          * Treat the line as parametric curve in homogeneous coordinates, where the parameter t runs from 0 to 1.
    937          * First we transform the interval [0,1] to [-1,1].
    938          * If the line has homogeneous coordinates [c, a, b] = stdform[] then the direction of the line is [b, -a].
    939          * Now, we take one finite point that defines the line, i.e. we take either point1 or point2
    940          * (in case the line is not the ideal line).
    941          * Let the coordinates of that point be [z, x, y].
    942          * Then, the curve runs linearly from
    943          * [0, b, -a] (t=-1) to [z, x, y] (t=0)
    944          * and
    945          * [z, x, y] (t=0) to [0, -b, a] (t=1)
    946          *
    947          * @param {Number} t Parameter running from 0 to 1.
    948          * @returns {Number} X(t) x-coordinate of the line treated as parametric curve.
    949          * */
    950         X: function (t) {
    951             var x,
    952                 b = this.stdform[2];
    953
    954             x =
    955                 Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps
    956                     ? this.point1.coords.usrCoords[1]
    957                     : this.point2.coords.usrCoords[1];
    958
    959             t = (t - 0.5) * 2;
    960
    961             return (1 - Math.abs(t)) * x - t * b;
    962         },
    963
    964         /**
    965          * Treat the line as parametric curve in homogeneous coordinates.
    966          * See {@link JXG.Line#X} for a detailed description.
    967          * @param {Number} t Parameter running from 0 to 1.
    968          * @returns {Number} Y(t) y-coordinate of the line treated as parametric curve.
    969          */
    970         Y: function (t) {
    971             var y,
    972                 a = this.stdform[1];
    973
    974             y =
    975                 Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps
    976                     ? this.point1.coords.usrCoords[2]
    977                     : this.point2.coords.usrCoords[2];
    978
    979             t = (t - 0.5) * 2;
    980
    981             return (1 - Math.abs(t)) * y + t * a;
    982         },
    983
    984         /**
    985          * Treat the line as parametric curve in homogeneous coordinates.
    986          * See {@link JXG.Line#X} for a detailed description.
    987          *
    988          * @param {Number} t Parameter running from 0 to 1.
    989          * @returns {Number} Z(t) z-coordinate of the line treated as parametric curve.
    990          */
    991         Z: function (t) {
    992             var z =
    993                 Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps
    994                     ? this.point1.coords.usrCoords[0]
    995                     : this.point2.coords.usrCoords[0];
    996
    997             t = (t - 0.5) * 2;
    998
    999             return (1 - Math.abs(t)) * z;
    1000         },
    1001
    1002         /**
    1003          * The distance between the two points defining the line.
    1004          * @returns {Number}
    1005          */
    1006         L: function () {
    1007             return this.point1.Dist(this.point2);
    1008         },
    1009
    1010         /**
    1011          * Set a new fixed length, then update the board.
    1012          * @param {String|Number|function} l A string, function or number describing the new length.
    1013          * @returns {JXG.Line} Reference to this line
    1014          */
    1015         setFixedLength: function (l) {
    1016             if (!this.hasFixedLength) {
    1017                 return this;
    1018             }
    1019
    1020             this.fixedLength = Type.createFunction(l, this.board);
    1021             this.hasFixedLength = true;
    1022             this.addParentsFromJCFunctions([this.fixedLength]);
    1023             this.board.update();
    1024
    1025             return this;
    1026         },
    1027
    1028         /**
    1029          * Treat the element  as a parametric curve
    1030          * @private
    1031          */
    1032         minX: function () {
    1033             return 0.0;
    1034         },
    1035
    1036         /**
    1037          * Treat the element as parametric curve
    1038          * @private
    1039          */
    1040         maxX: function () {
    1041             return 1.0;
    1042         },
    1043
    1044         // documented in geometry element
    1045         bounds: function () {
    1046             var p1c = this.point1.coords.usrCoords,
    1047                 p2c = this.point2.coords.usrCoords;
    1048
    1049             return [
    1050                 Math.min(p1c[1], p2c[1]),
    1051                 Math.max(p1c[2], p2c[2]),
    1052                 Math.max(p1c[1], p2c[1]),
    1053                 Math.min(p1c[2], p2c[2])
    1054             ];
    1055         },
    1056
    1057         // documented in GeometryElement.js
    1058         remove: function () {
    1059             this.removeAllTicks();
    1060             GeometryElement.prototype.remove.call(this);
    1061         }
    1062
    1063         // hideElement: function () {
    1064         //     var i;
    1065         //
    1066         //     GeometryElement.prototype.hideElement.call(this);
    1067         //
    1068         //     for (i = 0; i < this.ticks.length; i++) {
    1069         //         this.ticks[i].hideElement();
    1070         //     }
    1071         // },
    1072         //
    1073         // showElement: function () {
    1074         //     var i;
    1075         //     GeometryElement.prototype.showElement.call(this);
    1076         //
    1077         //     for (i = 0; i < this.ticks.length; i++) {
    1078         //         this.ticks[i].showElement();
    1079         //     }
    1080         // }
    1081
    1082     }
    1083 );
    1084
    1085 /**
    1086  * @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
    1087  * a line can be used as an arrow and/or axis.
    1088  * @pseudo
    1089  * @name Line
    1090  * @augments JXG.Line
    1091  * @constructor
    1092  * @type JXG.Line
    1093  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1094  * @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
    1095  * numbers describing the coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
    1096  * It is possible to provide a function returning an array or a point, instead of providing an array or a point.
    1097  * @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
    1098  * 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.
    1099  * It is possible to provide three functions returning numbers, too.
    1100  * @param {function} f This function must return an array containing three numbers forming the line's homogeneous coordinates.
    1101  * <p>
    1102  * Additionally, a line can be created by providing a line and a transformation (or an array of transformations).
    1103  * Then, the result is a line which is the transformation of the supplied line.
    1104  * @example
    1105  * // Create a line using point and coordinates/
    1106  * // The second point will be fixed and invisible.
    1107  * var p1 = board.create('point', [4.5, 2.0]);
    1108  * var l1 = board.create('line', [p1, [1.0, 1.0]]);
    1109  * </pre><div class="jxgbox" id="JXGc0ae3461-10c4-4d39-b9be-81d74759d122" style="width: 300px; height: 300px;"></div>
    1110  * <script type="text/javascript">
    1111  *   var glex1_board = JXG.JSXGraph.initBoard('JXGc0ae3461-10c4-4d39-b9be-81d74759d122', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    1112  *   var glex1_p1 = glex1_board.create('point', [4.5, 2.0]);
    1113  *   var glex1_l1 = glex1_board.create('line', [glex1_p1, [1.0, 1.0]]);
    1114  * </script><pre>
    1115  * @example
    1116  * // Create a line using three coordinates
    1117  * var l1 = board.create('line', [1.0, -2.0, 3.0]);
    1118  * </pre><div class="jxgbox" id="JXGcf45e462-f964-4ba4-be3a-c9db94e2593f" style="width: 300px; height: 300px;"></div>
    1119  * <script type="text/javascript">
    1120  *   var glex2_board = JXG.JSXGraph.initBoard('JXGcf45e462-f964-4ba4-be3a-c9db94e2593f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    1121  *   var glex2_l1 = glex2_board.create('line', [1.0, -2.0, 3.0]);
    1122  * </script><pre>
    1123  * @example
    1124  *         // Create a line (l2) as reflection of another line (l1)
    1125  *         // reflection line
    1126  *         var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'});
    1127  *         var reflect = board.create('transform', [li], {type: 'reflect'});
    1128  *
    1129  *         var l1 = board.create('line', [1,-5,1]);
    1130  *         var l2 = board.create('line', [l1, reflect]);
    1131  *
    1132  * </pre><div id="JXGJXGa00d7dd6-d38c-11e7-93b3-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1133  * <script type="text/javascript">
    1134  *     (function() {
    1135  *         var board = JXG.JSXGraph.initBoard('JXGJXGa00d7dd6-d38c-11e7-93b3-901b0e1b8723',
    1136  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    1137  *             // reflection line
    1138  *             var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'});
    1139  *             var reflect = board.create('transform', [li], {type: 'reflect'});
    1140  *
    1141  *             var l1 = board.create('line', [1,-5,1]);
    1142  *             var l2 = board.create('line', [l1, reflect]);
    1143  *     })();
    1144  *
    1145  * </script><pre>
    1146  *
    1147  * @example
    1148  * var t = board.create('transform', [2, 1.5], {type: 'scale'});
    1149  * var l1 = board.create('line', [1, -5, 1]);
    1150  * var l2 = board.create('line', [l1, t]);
    1151  *
    1152  * </pre><div id="d16d5b58-6338-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1153  * <script type="text/javascript">
    1154  *     (function() {
    1155  *         var board = JXG.JSXGraph.initBoard('d16d5b58-6338-11e8-9fb9-901b0e1b8723',
    1156  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    1157  *     var t = board.create('transform', [2, 1.5], {type: 'scale'});
    1158  *     var l1 = board.create('line', [1, -5, 1]);
    1159  *     var l2 = board.create('line', [l1, t]);
    1160  *
    1161  *     })();
    1162  *
    1163  * </script><pre>
    1164  *
    1165  * @example
    1166  * //create line between two points
    1167  * var p1 = board.create('point', [0,0]);
    1168  * var p2 = board.create('point', [2,2]);
    1169  * var l1 = board.create('line', [p1,p2], {straightFirst:false, straightLast:false});
    1170  * </pre><div id="d21d5b58-6338-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1171  * <script type="text/javascript">
    1172  *     (function() {
    1173  *         var board = JXG.JSXGraph.initBoard('d21d5b58-6338-11e8-9fb9-901b0e1b8723',
    1174  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    1175  *             var ex5p1 = board.create('point', [0,0]);
    1176  *             var ex5p2 = board.create('point', [2,2]);
    1177  *             var ex5l1 = board.create('line', [ex5p1,ex5p2], {straightFirst:false, straightLast:false});
    1178  *     })();
    1179  *
    1180  * </script><pre>
    1181  */
    1182 JXG.createLine = function (board, parents, attributes) {
    1183     var ps, el, p1, p2, i, attr,
    1184         c = [],
    1185         doTransform = false,
    1186         constrained = false,
    1187         isDraggable;
    1188
    1189     if (parents.length === 2) {
    1190         // The line is defined by two points or coordinates of two points.
    1191         // In the latter case, the points are created.
    1192         attr = Type.copyAttributes(attributes, board.options, "line", "point1");
    1193         if (Type.isArray(parents[0]) && parents[0].length > 1) {
    1194             p1 = board.create("point", parents[0], attr);
    1195         } else if (Type.isString(parents[0]) || Type.isPoint(parents[0])) {
    1196             p1 = board.select(parents[0]);
    1197         } else if (Type.isFunction(parents[0]) && Type.isPoint(parents[0]())) {
    1198             p1 = parents[0]();
    1199             constrained = true;
    1200         } else if (
    1201             Type.isFunction(parents[0]) &&
    1202             parents[0]().length &&
    1203             parents[0]().length >= 2
    1204         ) {
    1205             p1 = JXG.createPoint(board, parents[0](), attr);
    1206             constrained = true;
    1207         } else if (Type.isObject(parents[0]) && Type.isTransformationOrArray(parents[1])) {
    1208             doTransform = true;
    1209             p1 = board.create("point", [parents[0].point1, parents[1]], attr);
    1210         } else {
    1211             throw new Error(
    1212                 "JSXGraph: Can't create line with parent types '" +
    1213                 typeof parents[0] +
    1214                 "' and '" +
    1215                 typeof parents[1] +
    1216                 "'." +
    1217                 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"
    1218             );
    1219         }
    1220
    1221         // point 2 given by coordinates
    1222         attr = Type.copyAttributes(attributes, board.options, "line", "point2");
    1223         if (doTransform) {
    1224             p2 = board.create("point", [parents[0].point2, parents[1]], attr);
    1225         } else if (Type.isArray(parents[1]) && parents[1].length > 1) {
    1226             p2 = board.create("point", parents[1], attr);
    1227         } else if (Type.isString(parents[1]) || Type.isPoint(parents[1])) {
    1228             p2 = board.select(parents[1]);
    1229         } else if (Type.isFunction(parents[1]) && Type.isPoint(parents[1]())) {
    1230             p2 = parents[1]();
    1231             constrained = true;
    1232         } else if (
    1233             Type.isFunction(parents[1]) &&
    1234             parents[1]().length &&
    1235             parents[1]().length >= 2
    1236         ) {
    1237             p2 = JXG.createPoint(board, parents[1](), attr);
    1238             constrained = true;
    1239         } else {
    1240             throw new Error(
    1241                 "JSXGraph: Can't create line with parent types '" +
    1242                 typeof parents[0] +
    1243                 "' and '" +
    1244                 typeof parents[1] +
    1245                 "'." +
    1246                 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"
    1247             );
    1248         }
    1249
    1250         attr = Type.copyAttributes(attributes, board.options, "line");
    1251         el = new JXG.Line(board, p1, p2, attr);
    1252
    1253         if (constrained) {
    1254             el.constrained = true;
    1255             el.funp1 = parents[0];
    1256             el.funp2 = parents[1];
    1257         } else if (!doTransform) {
    1258             el.isDraggable = true;
    1259         }
    1260
    1261         //if (!el.constrained) {
    1262         el.setParents([p1.id, p2.id]);
    1263         //}
    1264
    1265     } else if (parents.length === 3) {
    1266         // Free line:
    1267         // Line is defined by three homogeneous coordinates.
    1268         // Also in this case points are created.
    1269         isDraggable = true;
    1270         for (i = 0; i < 3; i++) {
    1271             if (Type.isNumber(parents[i])) {
    1272                 // createFunction will just wrap a function around our constant number
    1273                 // that does nothing else but to return that number.
    1274                 c[i] = Type.createFunction(parents[i]);
    1275             } else if (Type.isFunction(parents[i])) {
    1276                 c[i] = parents[i];
    1277                 isDraggable = false;
    1278             } else {
    1279                 throw new Error(
    1280                     "JSXGraph: Can't create line with parent types '" +
    1281                     typeof parents[0] +
    1282                     "' and '" +
    1283                     typeof parents[1] +
    1284                     "' and '" +
    1285                     typeof parents[2] +
    1286                     "'." +
    1287                     "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"
    1288                 );
    1289             }
    1290         }
    1291
    1292         // point 1 is the midpoint between (0, c, -b) and point 2. => point1 is finite.
    1293         attr = Type.copyAttributes(attributes, board.options, "line", "point1");
    1294         if (isDraggable) {
    1295             p1 = board.create("point", [
    1296                 c[2]() * c[2]() + c[1]() * c[1](),
    1297                 c[2]() - c[1]() * c[0]() + c[2](),
    1298                 -c[1]() - c[2]() * c[0]() - c[1]()
    1299             ], attr);
    1300         } else {
    1301             p1 = board.create("point", [
    1302                 function () {
    1303                     return (c[2]() * c[2]() + c[1]() * c[1]()) * 0.5;
    1304                 },
    1305                 function () {
    1306                     return (c[2]() - c[1]() * c[0]() + c[2]()) * 0.5;
    1307                 },
    1308                 function () {
    1309                     return (-c[1]() - c[2]() * c[0]() - c[1]()) * 0.5;
    1310                 }
    1311             ], attr);
    1312         }
    1313
    1314         // point 2: (b^2+c^2,-ba+c,-ca-b)
    1315         attr = Type.copyAttributes(attributes, board.options, "line", "point2");
    1316         if (isDraggable) {
    1317             p2 = board.create("point", [
    1318                 c[2]() * c[2]() + c[1]() * c[1](),
    1319                 -c[1]() * c[0]() + c[2](),
    1320                 -c[2]() * c[0]() - c[1]()
    1321             ], attr);
    1322         } else {
    1323             p2 = board.create("point", [
    1324                 function () {
    1325                     return c[2]() * c[2]() + c[1]() * c[1]();
    1326                 },
    1327                 function () {
    1328                     return -c[1]() * c[0]() + c[2]();
    1329                 },
    1330                 function () {
    1331                     return -c[2]() * c[0]() - c[1]();
    1332                 }
    1333             ], attr);
    1334         }
    1335
    1336         // If the line will have a glider and board.suspendUpdate() has been called, we
    1337         // need to compute the initial position of the two points p1 and p2.
    1338         p1.prepareUpdate().update();
    1339         p2.prepareUpdate().update();
    1340         attr = Type.copyAttributes(attributes, board.options, "line");
    1341         el = new JXG.Line(board, p1, p2, attr);
    1342         // Not yet working, because the points are not draggable.
    1343         el.isDraggable = isDraggable;
    1344         el.setParents([p1, p2]);
    1345
    1346     } else if (
    1347         // The parent array contains a function which returns two points.
    1348         parents.length === 1 &&
    1349         Type.isFunction(parents[0]) &&
    1350         parents[0]().length === 2 &&
    1351         Type.isPoint(parents[0]()[0]) &&
    1352         Type.isPoint(parents[0]()[1])
    1353     ) {
    1354         ps = parents[0]();
    1355         attr = Type.copyAttributes(attributes, board.options, "line");
    1356         el = new JXG.Line(board, ps[0], ps[1], attr);
    1357         el.constrained = true;
    1358         el.funps = parents[0];
    1359         el.setParents(ps);
    1360     } else if (
    1361         parents.length === 1 &&
    1362         Type.isFunction(parents[0]) &&
    1363         parents[0]().length === 3 &&
    1364         Type.isNumber(parents[0]()[0]) &&
    1365         Type.isNumber(parents[0]()[1]) &&
    1366         Type.isNumber(parents[0]()[2])
    1367     ) {
    1368         ps = parents[0];
    1369
    1370         attr = Type.copyAttributes(attributes, board.options, "line", "point1");
    1371         p1 = board.create("point", [
    1372             function () {
    1373                 var c = ps();
    1374
    1375                 return [
    1376                     (c[2] * c[2] + c[1] * c[1]) * 0.5,
    1377                     (c[2] - c[1] * c[0] + c[2]) * 0.5,
    1378                     (-c[1] - c[2] * c[0] - c[1]) * 0.5
    1379                 ];
    1380             }
    1381         ], attr);
    1382
    1383         attr = Type.copyAttributes(attributes, board.options, "line", "point2");
    1384         p2 = board.create("point", [
    1385             function () {
    1386                 var c = ps();
    1387
    1388                 return [
    1389                     c[2] * c[2] + c[1] * c[1],
    1390                     -c[1] * c[0] + c[2],
    1391                     -c[2] * c[0] - c[1]
    1392                 ];
    1393             }
    1394         ], attr);
    1395
    1396         attr = Type.copyAttributes(attributes, board.options, "line");
    1397         el = new JXG.Line(board, p1, p2, attr);
    1398
    1399         el.constrained = true;
    1400         el.funps = parents[0];
    1401         el.setParents([p1, p2]);
    1402     } else {
    1403         throw new Error(
    1404             "JSXGraph: Can't create line with parent types '" +
    1405             typeof parents[0] +
    1406             "' and '" +
    1407             typeof parents[1] +
    1408             "'." +
    1409             "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"
    1410         );
    1411     }
    1412
    1413     return el;
    1414 };
    1415
    1416 JXG.registerElement("line", JXG.createLine);
    1417
    1418 /**
    1419  * @class This element is used to provide a constructor for a segment.
    1420  * It's strictly spoken just a wrapper for element {@link Line} with {@link Line#straightFirst}
    1421  * and {@link Line#straightLast} properties set to false. If there is a third variable then the
    1422  * segment has a fixed length (which may be a function, too) determined by the absolute value of
    1423  * that number.
    1424  * @pseudo
    1425  * @name Segment
    1426  * @augments JXG.Line
    1427  * @constructor
    1428  * @type JXG.Line
    1429  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1430  * @param {JXG.Point,array_JXG.Point,array} point1,point2 Parent elements can be two elements either of type {@link JXG.Point}
    1431  * or array of numbers describing the
    1432  * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
    1433  * @param {number,function} [length] The points are adapted - if possible - such that their distance
    1434  * is equal to the absolute value of this number.
    1435  * @see Line
    1436  * @example
    1437  * // Create a segment providing two points.
    1438  *   var p1 = board.create('point', [4.5, 2.0]);
    1439  *   var p2 = board.create('point', [1.0, 1.0]);
    1440  *   var l1 = board.create('segment', [p1, p2]);
    1441  * </pre><div class="jxgbox" id="JXGd70e6aac-7c93-4525-a94c-a1820fa38e2f" style="width: 300px; height: 300px;"></div>
    1442  * <script type="text/javascript">
    1443  *   var slex1_board = JXG.JSXGraph.initBoard('JXGd70e6aac-7c93-4525-a94c-a1820fa38e2f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    1444  *   var slex1_p1 = slex1_board.create('point', [4.5, 2.0]);
    1445  *   var slex1_p2 = slex1_board.create('point', [1.0, 1.0]);
    1446  *   var slex1_l1 = slex1_board.create('segment', [slex1_p1, slex1_p2]);
    1447  * </script><pre>
    1448  *
    1449  * @example
    1450  * // Create a segment providing two points.
    1451  *   var p1 = board.create('point', [4.0, 1.0]);
    1452  *   var p2 = board.create('point', [1.0, 1.0]);
    1453  *   // AB
    1454  *   var l1 = board.create('segment', [p1, p2]);
    1455  *   var p3 = board.create('point', [4.0, 2.0]);
    1456  *   var p4 = board.create('point', [1.0, 2.0]);
    1457  *   // CD
    1458  *   var l2 = board.create('segment', [p3, p4, 3]); // Fixed length
    1459  *   var p5 = board.create('point', [4.0, 3.0]);
    1460  *   var p6 = board.create('point', [1.0, 4.0]);
    1461  *   // EF
    1462  *   var l3 = board.create('segment', [p5, p6, function(){ return l1.L();} ]); // Fixed, but dependent length
    1463  * </pre><div class="jxgbox" id="JXG617336ba-0705-4b2b-a236-c87c28ef25be" style="width: 300px; height: 300px;"></div>
    1464  * <script type="text/javascript">
    1465  *   var slex2_board = JXG.JSXGraph.initBoard('JXG617336ba-0705-4b2b-a236-c87c28ef25be', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    1466  *   var slex2_p1 = slex2_board.create('point', [4.0, 1.0]);
    1467  *   var slex2_p2 = slex2_board.create('point', [1.0, 1.0]);
    1468  *   var slex2_l1 = slex2_board.create('segment', [slex2_p1, slex2_p2]);
    1469  *   var slex2_p3 = slex2_board.create('point', [4.0, 2.0]);
    1470  *   var slex2_p4 = slex2_board.create('point', [1.0, 2.0]);
    1471  *   var slex2_l2 = slex2_board.create('segment', [slex2_p3, slex2_p4, 3]);
    1472  *   var slex2_p5 = slex2_board.create('point', [4.0, 2.0]);
    1473  *   var slex2_p6 = slex2_board.create('point', [1.0, 2.0]);
    1474  *   var slex2_l3 = slex2_board.create('segment', [slex2_p5, slex2_p6, function(){ return slex2_l1.L();}]);
    1475  * </script><pre>
    1476  *
    1477  */
    1478 JXG.createSegment = function (board, parents, attributes) {
    1479     var el, attr;
    1480
    1481     attributes.straightFirst = false;
    1482     attributes.straightLast = false;
    1483     attr = Type.copyAttributes(attributes, board.options, "segment");
    1484
    1485     el = board.create("line", parents.slice(0, 2), attr);
    1486
    1487     if (parents.length === 3) {
    1488         try {
    1489             el.hasFixedLength = true;
    1490             el.fixedLengthOldCoords = [];
    1491             el.fixedLengthOldCoords[0] = new Coords(
    1492                 Const.COORDS_BY_USER,
    1493                 el.point1.coords.usrCoords.slice(1, 3),
    1494                 board
    1495             );
    1496             el.fixedLengthOldCoords[1] = new Coords(
    1497                 Const.COORDS_BY_USER,
    1498                 el.point2.coords.usrCoords.slice(1, 3),
    1499                 board
    1500             );
    1501
    1502             el.setFixedLength(parents[2]);
    1503         } catch (err) {
    1504             throw new Error(
    1505                 "JSXGraph: Can't create segment with third parent type '" +
    1506                 typeof parents[2] +
    1507                 "'." +
    1508                 "\nPossible third parent types: number or function"
    1509             );
    1510         }
    1511         // if (Type.isNumber(parents[2])) {
    1512         //     el.fixedLength = function () {
    1513         //         return parents[2];
    1514         //     };
    1515         // } else if (Type.isFunction(parents[2])) {
    1516         //     el.fixedLength = Type.createFunction(parents[2], this.board);
    1517         // } else {
    1518         //     throw new Error(
    1519         //         "JSXGraph: Can't create segment with third parent type '" +
    1520         //             typeof parents[2] +
    1521         //             "'." +
    1522         //             "\nPossible third parent types: number or function"
    1523         //     );
    1524         // }
    1525
    1526         el.getParents = function () {
    1527             return this.parents.concat(this.fixedLength());
    1528         };
    1529
    1530     }
    1531
    1532     el.elType = "segment";
    1533
    1534     return el;
    1535 };
    1536
    1537 JXG.registerElement("segment", JXG.createSegment);
    1538
    1539 /**
    1540  * @class This element is used to provide a constructor for arrow, which is just a wrapper for element
    1541  * {@link Line} with {@link Line#straightFirst}
    1542  * and {@link Line#straightLast} properties set to false and {@link Line#lastArrow} set to true.
    1543  * @pseudo
    1544  * @name Arrow
    1545  * @augments JXG.Line
    1546  * @constructor
    1547  * @type JXG.Line
    1548  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1549  * @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
    1550  * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
    1551  * @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
    1552  * of the equation <tt>a*x+b*y+c*z = 0</tt>.
    1553  * @see Line
    1554  * @example
    1555  * // Create an arrow providing two points.
    1556  *   var p1 = board.create('point', [4.5, 2.0]);
    1557  *   var p2 = board.create('point', [1.0, 1.0]);
    1558  *   var l1 = board.create('arrow', [p1, p2]);
    1559  * </pre><div class="jxgbox" id="JXG1d26bd22-7d6d-4018-b164-4c8bc8d22ccf" style="width: 300px; height: 300px;"></div>
    1560  * <script type="text/javascript">
    1561  *   var alex1_board = JXG.JSXGraph.initBoard('JXG1d26bd22-7d6d-4018-b164-4c8bc8d22ccf', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    1562  *   var alex1_p1 = alex1_board.create('point', [4.5, 2.0]);
    1563  *   var alex1_p2 = alex1_board.create('point', [1.0, 1.0]);
    1564  *   var alex1_l1 = alex1_board.create('arrow', [alex1_p1, alex1_p2]);
    1565  * </script><pre>
    1566  */
    1567 JXG.createArrow = function (board, parents, attributes) {
    1568     var el, attr;
    1569
    1570     attributes.straightFirst = false;
    1571     attributes.straightLast = false;
    1572     attr = Type.copyAttributes(attributes, board.options, "arrow");
    1573     el = board.create("line", parents, attr);
    1574     //el.setArrow(false, true);
    1575     el.type = Const.OBJECT_TYPE_VECTOR;
    1576     el.elType = "arrow";
    1577
    1578     return el;
    1579 };
    1580
    1581 JXG.registerElement("arrow", JXG.createArrow);
    1582
    1583 /**
    1584  * @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}
    1585  * and {@link Line#straightLast} properties set to true. Additionally {@link Line#lastArrow} is set to true and default {@link Ticks} will be created.
    1586  * @pseudo
    1587  * @name Axis
    1588  * @augments JXG.Line
    1589  * @constructor
    1590  * @type JXG.Line
    1591  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1592  * @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
    1593  * coordinates of a point. In the latter case, the point will be constructed automatically as a fixed invisible point.
    1594  * @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
    1595  * of the equation <tt>a*x+b*y+c*z = 0</tt>.
    1596  * @example
    1597  * // Create an axis providing two coords pairs.
    1598  *   var l1 = board.create('axis', [[0.0, 1.0], [1.0, 1.3]]);
    1599  * </pre><div class="jxgbox" id="JXG4f414733-624c-42e4-855c-11f5530383ae" style="width: 300px; height: 300px;"></div>
    1600  * <script type="text/javascript">
    1601  *   var axex1_board = JXG.JSXGraph.initBoard('JXG4f414733-624c-42e4-855c-11f5530383ae', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
    1602  *   var axex1_l1 = axex1_board.create('axis', [[0.0, 1.0], [1.0, 1.3]]);
    1603  * </script><pre>
    1604  * @example
    1605  *  // Create ticks labels as fractions
    1606  *  board.create('axis', [[0,1], [1,1]], {
    1607  *      ticks: {
    1608  *          label: {
    1609  *              toFraction: true,
    1610  *              useMathjax: false,
    1611  *              anchorX: 'middle',
    1612  *              offset: [0, -10]
    1613  *          }
    1614  *      }
    1615  *  });
    1616  *
    1617  *
    1618  * </pre><div id="JXG34174cc4-0050-4ab4-af69-e91365d0666f" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1619  * <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" id="MathJax-script"></script>
    1620  * <script type="text/javascript">
    1621  *     (function() {
    1622  *         var board = JXG.JSXGraph.initBoard('JXG34174cc4-0050-4ab4-af69-e91365d0666f',
    1623  *             {boundingbox: [-1.2, 2.3, 1.2, -2.3], axis: true, showcopyright: false, shownavigation: false});
    1624  *             board.create('axis', [[0,1], [1,1]], {
    1625  *                 ticks: {
    1626  *                     label: {
    1627  *                         toFraction: true,
    1628  *                         useMathjax: false,
    1629  *                         anchorX: 'middle',
    1630  *                         offset: [0, -10]
    1631  *                     }
    1632  *                 }
    1633  *             });
    1634  *
    1635  *
    1636  *     })();
    1637  *
    1638  * </script><pre>
    1639  *
    1640  */
    1641 JXG.createAxis = function (board, parents, attributes) {
    1642     var axis, attr,
    1643         ancestor, ticksDist;
    1644
    1645     // Create line
    1646     attr = Type.copyAttributes(attributes, board.options, "axis");
    1647     try {
    1648         axis = board.create("line", parents, attr);
    1649     } catch (err) {
    1650         throw new Error(
    1651             "JSXGraph: Can't create axis with parent types '" +
    1652             typeof parents[0] +
    1653             "' and '" +
    1654             typeof parents[1] +
    1655             "'." +
    1656             "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]"
    1657         );
    1658     }
    1659
    1660     axis.type = Const.OBJECT_TYPE_AXIS;
    1661     axis.isDraggable = false;
    1662     axis.point1.isDraggable = false;
    1663     axis.point2.isDraggable = false;
    1664
    1665     // Save usrCoords of points
    1666     axis._point1UsrCoordsOrg = axis.point1.coords.usrCoords.slice();
    1667     axis._point2UsrCoordsOrg = axis.point2.coords.usrCoords.slice();
    1668
    1669     for (ancestor in axis.ancestors) {
    1670         if (axis.ancestors.hasOwnProperty(ancestor)) {
    1671             axis.ancestors[ancestor].type = Const.OBJECT_TYPE_AXISPOINT;
    1672         }
    1673     }
    1674
    1675     // Create ticks
    1676     // attrTicks = attr.ticks;
    1677     if (Type.exists(attr.ticks.ticksdistance)) {
    1678         ticksDist = attr.ticks.ticksdistance;
    1679     } else if (Type.isArray(attr.ticks.ticks)) {
    1680         ticksDist = attr.ticks.ticks;
    1681     } else {
    1682         ticksDist = 1.0;
    1683     }
    1684
    1685     /**
    1686      * The ticks attached to the axis.
    1687      * @memberOf Axis.prototype
    1688      * @name defaultTicks
    1689      * @type JXG.Ticks
    1690      */
    1691     axis.defaultTicks = board.create("ticks", [axis, ticksDist], attr.ticks);
    1692     axis.defaultTicks.dump = false;
    1693     axis.elType = "axis";
    1694     axis.subs = {
    1695         ticks: axis.defaultTicks
    1696     };
    1697     axis.inherits.push(axis.defaultTicks);
    1698
    1699     axis.update = function () {
    1700         var bbox,
    1701             position, i,
    1702             direction, horizontal, vertical,
    1703             ticksAutoPos, ticksAutoPosThres, dist,
    1704             anchor, left, right,
    1705             distUsr,
    1706             newPosP1, newPosP2,
    1707             locationOrg,
    1708             visLabel, anchr, off;
    1709
    1710         bbox = this.board.getBoundingBox();
    1711         position = Type.evaluate(this.visProp.position);
    1712         direction = this.Direction();
    1713         horizontal = this.isHorizontal();
    1714         vertical = this.isVertical();
    1715         ticksAutoPos = Type.evaluate(this.visProp.ticksautopos);
    1716         ticksAutoPosThres = Type.evaluate(this.visProp.ticksautoposthreshold);
    1717
    1718         if (horizontal) {
    1719             ticksAutoPosThres = Type.parseNumber(ticksAutoPosThres, Math.abs(bbox[1] - bbox[3]), 1 / this.board.unitX) * this.board.unitX;
    1720         } else if (vertical) {
    1721             ticksAutoPosThres = Type.parseNumber(ticksAutoPosThres, Math.abs(bbox[1] - bbox[3]), 1 / this.board.unitY) * this.board.unitY;
    1722         } else {
    1723             ticksAutoPosThres = Type.parseNumber(ticksAutoPosThres, 1, 1);
    1724         }
    1725
    1726         anchor = Type.evaluate(this.visProp.anchor);
    1727         left = anchor.indexOf('left') > -1;
    1728         right = anchor.indexOf('right') > -1;
    1729
    1730         distUsr = Type.evaluate(this.visProp.anchordist);
    1731         if (horizontal) {
    1732             distUsr = Type.parseNumber(distUsr, Math.abs(bbox[1] - bbox[3]), 1 / this.board.unitX);
    1733         } else if (vertical) {
    1734             distUsr = Type.parseNumber(distUsr, Math.abs(bbox[0] - bbox[2]), 1 / this.board.unitY);
    1735         } else {
    1736             distUsr = 0;
    1737         }
    1738
    1739         locationOrg = this.board.getPointLoc(this._point1UsrCoordsOrg, distUsr);
    1740
    1741         // Set position of axis
    1742         newPosP1 = this.point1.coords.usrCoords.slice();
    1743         newPosP2 = this.point2.coords.usrCoords.slice();
    1744
    1745         if (position === 'static' || (!vertical && !horizontal)) {
    1746             // Do nothing
    1747
    1748         } else if (position === 'fixed') {
    1749             if (horizontal) { // direction[1] === 0
    1750                 if ((direction[0] > 0 && right) || (direction[0] < 0 && left)) {
    1751                     newPosP1[2] = bbox[3] + distUsr;
    1752                     newPosP2[2] = bbox[3] + distUsr;
    1753                 } else if ((direction[0] > 0 && left) || (direction[0] < 0 && right)) {
    1754                     newPosP1[2] = bbox[1] - distUsr;
    1755                     newPosP2[2] = bbox[1] - distUsr;
    1756
    1757                 } else {
    1758                     newPosP1 = this._point1UsrCoordsOrg.slice();
    1759                     newPosP2 = this._point2UsrCoordsOrg.slice();
    1760                 }
    1761             }
    1762             if (vertical) { // direction[0] === 0
    1763                 if ((direction[1] > 0 && left) || (direction[1] < 0 && right)) {
    1764                     newPosP1[1] = bbox[0] + distUsr;
    1765                     newPosP2[1] = bbox[0] + distUsr;
    1766
    1767                 } else if ((direction[1] > 0 && right) || (direction[1] < 0 && left)) {
    1768                     newPosP1[1] = bbox[2] - distUsr;
    1769                     newPosP2[1] = bbox[2] - distUsr;
    1770
    1771                 } else {
    1772                     newPosP1 = this._point1UsrCoordsOrg.slice();
    1773                     newPosP2 = this._point2UsrCoordsOrg.slice();
    1774                 }
    1775             }
    1776
    1777         } else if (position === 'sticky') {
    1778             if (horizontal) { // direction[1] === 0
    1779                 if (locationOrg[1] < 0 && ((direction[0] > 0 && right) || (direction[0] < 0 && left))) {
    1780                     newPosP1[2] = bbox[3] + distUsr;
    1781                     newPosP2[2] = bbox[3] + distUsr;
    1782
    1783                 } else if (locationOrg[1] > 0 && ((direction[0] > 0 && left) || (direction[0] < 0 && right))) {
    1784                     newPosP1[2] = bbox[1] - distUsr;
    1785                     newPosP2[2] = bbox[1] - distUsr;
    1786
    1787                 } else {
    1788                     newPosP1 = this._point1UsrCoordsOrg.slice();
    1789                     newPosP2 = this._point2UsrCoordsOrg.slice();
    1790                 }
    1791             }
    1792             if (vertical) { // direction[0] === 0
    1793                 if (locationOrg[0] < 0 && ((direction[1] > 0 && left) || (direction[1] < 0 && right))) {
    1794                     newPosP1[1] = bbox[0] + distUsr;
    1795                     newPosP2[1] = bbox[0] + distUsr;
    1796
    1797                 } else if (locationOrg[0] > 0 && ((direction[1] > 0 && right) || (direction[1] < 0 && left))) {
    1798                     newPosP1[1] = bbox[2] - distUsr;
    1799                     newPosP2[1] = bbox[2] - distUsr;
    1800
    1801                 } else {
    1802                     newPosP1 = this._point1UsrCoordsOrg.slice();
    1803                     newPosP2 = this._point2UsrCoordsOrg.slice();
    1804                 }
    1805             }
    1806         }
    1807
    1808         this.point1.setPositionDirectly(JXG.COORDS_BY_USER, newPosP1);
    1809         this.point2.setPositionDirectly(JXG.COORDS_BY_USER, newPosP2);
    1810
    1811         // Set position of tick labels
    1812         if (Type.exists(this.defaultTicks)) {
    1813             visLabel = this.defaultTicks.visProp.label;
    1814             if (ticksAutoPos && (horizontal || vertical)) {
    1815
    1816                 if (!Type.exists(visLabel._anchorx_org)) {
    1817                     visLabel._anchorx_org = Type.def(visLabel.anchorx, this.board.options.text.anchorX);
    1818                 }
    1819                 if (!Type.exists(visLabel._anchory_org)) {
    1820                     visLabel._anchory_org = Type.def(visLabel.anchory, this.board.options.text.anchorY);
    1821                 }
    1822                 if (!Type.exists(visLabel._offset_org)) {
    1823                     visLabel._offset_org = visLabel.offset.slice();
    1824                 }
    1825
    1826                 off = visLabel.offset;
    1827                 if (horizontal) {
    1828                     dist = axis.point1.coords.scrCoords[2] - (this.board.canvasHeight * 0.5);
    1829
    1830                     anchr = visLabel.anchory;
    1831
    1832                     // The last position of the labels is stored in visLabel._side
    1833                     if (dist < 0 && Math.abs(dist) > ticksAutoPosThres) {
    1834                         // Put labels on top of the line
    1835                         if (visLabel._side === 'bottom') {
    1836                             // Switch position
    1837                             if (visLabel.anchory === 'top') {
    1838                                 anchr = 'bottom';
    1839                             }
    1840                             off[1] *= -1;
    1841                             visLabel._side = 'top';
    1842                         }
    1843
    1844                     } else if (dist > 0 && Math.abs(dist) > ticksAutoPosThres) {
    1845                         // Put labels below the line
    1846                         if (visLabel._side === 'top') {
    1847                             // Switch position
    1848                             if (visLabel.anchory === 'bottom') {
    1849                                 anchr = 'top';
    1850                             }
    1851                             off[1] *= -1;
    1852                             visLabel._side = 'bottom';
    1853                         }
    1854
    1855                     } else {
    1856                         // Put to original position
    1857                         anchr = visLabel._anchory_org;
    1858                         off = visLabel._offset_org.slice();
    1859
    1860                         if (anchr === 'top') {
    1861                             visLabel._side = 'bottom';
    1862                         } else if (anchr === 'bottom') {
    1863                             visLabel._side = 'top';
    1864                         } else if (off[1] < 0) {
    1865                             visLabel._side = 'bottom';
    1866                         } else {
    1867                             visLabel._side = 'top';
    1868                         }
    1869                     }
    1870
    1871                     for (i = 0; i < axis.defaultTicks.labels.length; i++) {
    1872                         this.defaultTicks.labels[i].visProp.anchory = anchr;
    1873                     }
    1874                     visLabel.anchory = anchr;
    1875
    1876                 } else if (vertical) {
    1877                     dist = axis.point1.coords.scrCoords[1] - (this.board.canvasWidth * 0.5);
    1878
    1879                     if (dist < 0 && Math.abs(dist) > ticksAutoPosThres) {
    1880                         // Put labels to the left of the line
    1881                         if (visLabel._side === 'right') {
    1882                             // Switch position
    1883                             if (visLabel.anchorx === 'left') {
    1884                                 anchr = 'right';
    1885                             }
    1886                             off[0] *= -1;
    1887                             visLabel._side = 'left';
    1888                         }
    1889
    1890                     } else if (dist > 0 && Math.abs(dist) > ticksAutoPosThres) {
    1891                         // Put labels to the right of the line
    1892                         if (visLabel._side === 'left') {
    1893                             // Switch position
    1894                             if (visLabel.anchorx === 'right') {
    1895                                 anchr = 'left';
    1896                             }
    1897                             off[0] *= -1;
    1898                             visLabel._side = 'right';
    1899                         }
    1900
    1901                     } else {
    1902                         // Put to original position
    1903                         anchr = visLabel._anchorx_org;
    1904                         off = visLabel._offset_org.slice();
    1905
    1906                         if (anchr === 'left') {
    1907                             visLabel._side = 'right';
    1908                         } else if (anchr === 'right') {
    1909                             visLabel._side = 'left';
    1910                         } else if (off[0] < 0) {
    1911                             visLabel._side = 'left';
    1912                         } else {
    1913                             visLabel._side = 'right';
    1914                         }
    1915                     }
    1916
    1917                     for (i = 0; i < axis.defaultTicks.labels.length; i++) {
    1918                         this.defaultTicks.labels[i].visProp.anchorx = anchr;
    1919                     }
    1920                     visLabel.anchorx = anchr;
    1921                 }
    1922                 visLabel.offset = off;
    1923
    1924             } else {
    1925                 delete visLabel._anchorx_org;
    1926                 delete visLabel._anchory_org;
    1927                 delete visLabel._offset_org;
    1928             }
    1929             this.defaultTicks.needsUpdate = true;
    1930         }
    1931
    1932         JXG.Line.prototype.update.call(this);
    1933
    1934         return this;
    1935     };
    1936
    1937     return axis;
    1938 };
    1939
    1940 JXG.registerElement("axis", JXG.createAxis);
    1941
    1942 /**
    1943  * @class With the element tangent the slope of a line, circle, conic, turtle, or curve in a certain point can be visualized. A tangent is always constructed
    1944  * by a point on a line, circle, or curve and describes the tangent in the point on that line, circle, or curve.
    1945  * <p>
    1946  * If the point is not on the object (line, circle, conic, curve, turtle) the output depends on the type of the object.
    1947  * For conics and circles, the polar line will be constructed. For function graphs,
    1948  * the tangent of the vertical projection of the point to the function graph is constructed. For all other objects, the tangent
    1949  * in the orthogonal projection of the point to the object will be constructed.
    1950  * @pseudo
    1951  * @name Tangent
    1952  * @augments JXG.Line
    1953  * @constructor
    1954  * @type JXG.Line
    1955  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1956  * @param {Glider} g A glider on a line, circle, or curve.
    1957  * @param {JXG.GeometryElement} [c] Optional element for which the tangent is constructed
    1958  * @example
    1959  * // Create a tangent providing a glider on a function graph
    1960  *   var c1 = board.create('curve', [function(t){return t},function(t){return t*t*t;}]);
    1961  *   var g1 = board.create('glider', [0.6, 1.2, c1]);
    1962  *   var t1 = board.create('tangent', [g1]);
    1963  * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-4018d0d17a98" style="width: 400px; height: 400px;"></div>
    1964  * <script type="text/javascript">
    1965  *   var tlex1_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-4018d0d17a98', {boundingbox: [-6, 6, 6, -6], axis: true, showcopyright: false, shownavigation: false});
    1966  *   var tlex1_c1 = tlex1_board.create('curve', [function(t){return t},function(t){return t*t*t;}]);
    1967  *   var tlex1_g1 = tlex1_board.create('glider', [0.6, 1.2, tlex1_c1]);
    1968  *   var tlex1_t1 = tlex1_board.create('tangent', [tlex1_g1]);
    1969  * </script><pre>
    1970  */
    1971 JXG.createTangent = function (board, parents, attributes) {
    1972     var p, c, j, el, tangent, attr,
    1973         getCurveTangentDir,
    1974         res, isTransformed,
    1975         slides = [];
    1976
    1977     if (parents.length === 1) {
    1978         // One argument: glider on line, circle or curve
    1979         p = parents[0];
    1980         c = p.slideObject;
    1981
    1982     } else if (parents.length === 2) {
    1983         // Two arguments: (point,line|curve|circle|conic) or (line|curve|circle|conic,point).
    1984         // In fact, for circles and conics it is the polar
    1985         if (Type.isPoint(parents[0])) {
    1986             p = parents[0];
    1987             c = parents[1];
    1988         } else if (Type.isPoint(parents[1])) {
    1989             c = parents[0];
    1990             p = parents[1];
    1991         } else {
    1992             throw new Error(
    1993                 "JSXGraph: Can't create tangent with parent types '" +
    1994                 typeof parents[0] +
    1995                 "' and '" +
    1996                 typeof parents[1] +
    1997                 "'." +
    1998                 "\nPossible parent types: [glider|point], [point,line|curve|circle|conic]"
    1999             );
    2000         }
    2001     } else {
    2002         throw new Error(
    2003             "JSXGraph: Can't create tangent with parent types '" +
    2004             typeof parents[0] +
    2005             "' and '" +
    2006             typeof parents[1] +
    2007             "'." +
    2008             "\nPossible parent types: [glider|point], [point,line|curve|circle|conic]"
    2009         );
    2010     }
    2011
    2012     attr = Type.copyAttributes(attributes, board.options, 'tangent');
    2013     if (c.elementClass === Const.OBJECT_CLASS_LINE) {
    2014         tangent = board.create("line", [c.point1, c.point2], attr);
    2015         tangent.glider = p;
    2016     } else if (
    2017         c.elementClass === Const.OBJECT_CLASS_CURVE &&
    2018         c.type !== Const.OBJECT_TYPE_CONIC
    2019     ) {
    2020         res = c.getTransformationSource();
    2021         isTransformed = res[0];
    2022         if (isTransformed) {
    2023             // Curve is result of a transformation
    2024             // We recursively collect all curves from which
    2025             // the curve is transformed.
    2026             slides.push(c);
    2027             while (res[0] && Type.exists(res[1]._transformationSource)) {
    2028                 slides.push(res[1]);
    2029                 res = res[1].getTransformationSource();
    2030             }
    2031         }
    2032
    2033         if (Type.evaluate(c.visProp.curvetype) !== "plot" || isTransformed) {
    2034             // Functiongraph or parametric curve or
    2035             // transformed curve thereof.
    2036             tangent = board.create(
    2037                 "line",
    2038                 [
    2039                     function () {
    2040                         var g = c.X,
    2041                             f = c.Y,
    2042                             df, dg,
    2043                             li, i, c_org, invMat, po,
    2044                             t;
    2045
    2046                         if (p.type === Const.OBJECT_TYPE_GLIDER) {
    2047                             t = p.position;
    2048                         } else if (Type.evaluate(c.visProp.curvetype) === 'functiongraph') {
    2049                             t = p.X();
    2050                         } else {
    2051                             t = Geometry.projectPointToCurve(p, c, board)[1];
    2052                         }
    2053
    2054                         // po are the coordinates of the point
    2055                         // on the "original" curve. That is the curve or
    2056                         // the original curve which is transformed (maybe multiple times)
    2057                         // to this curve.
    2058                         // t is the position of the point on the "original" curve
    2059                         po = p.Coords(true);
    2060                         if (isTransformed) {
    2061                             c_org = slides[slides.length - 1]._transformationSource;
    2062                             g = c_org.X;
    2063                             f = c_org.Y;
    2064                             for (i = 0; i < slides.length; i++) {
    2065                                 slides[i].updateTransformMatrix();
    2066                                 invMat = Mat.inverse(slides[i].transformMat);
    2067                                 po = Mat.matVecMult(invMat, po);
    2068                             }
    2069
    2070                             if (p.type !== Const.OBJECT_TYPE_GLIDER) {
    2071                                 po[1] /= po[0];
    2072                                 po[2] /= po[0];
    2073                                 po[0] /= po[0];
    2074                                 t = Geometry.projectCoordsToCurve(po[1], po[2], 0, c_org, board)[1];
    2075                             }
    2076                         }
    2077
    2078                         // li are the coordinates of the line on the "original" curve
    2079                         df = Numerics.D(f)(t);
    2080                         dg = Numerics.D(g)(t);
    2081                         li = [
    2082                             -po[1] * df + po[2] * dg,
    2083                             po[0] * df,
    2084                             -po[0] * dg
    2085                         ];
    2086
    2087                         if (isTransformed) {
    2088                             // Transform the line to the transformed curve
    2089                             for (i = slides.length - 1; i >= 0; i--) {
    2090                                 invMat = Mat.transpose(Mat.inverse(slides[i].transformMat));
    2091                                 li = Mat.matVecMult(invMat, li);
    2092                             }
    2093                         }
    2094
    2095                         return li;
    2096                     }
    2097                 ],
    2098                 attr
    2099             );
    2100
    2101             p.addChild(tangent);
    2102             // this is required for the geogebra reader to display a slope
    2103             tangent.glider = p;
    2104         } else {
    2105             // curveType 'plot': discrete data
    2106             /**
    2107              * @ignore
    2108              *
    2109              * In case of bezierDegree == 1:
    2110              * Find two points p1, p2 enclosing the glider.
    2111              * Then the equation of the line segment is: 0 = y*(x1-x2) + x*(y2-y1) + y1*x2-x1*y2,
    2112              * which is the cross product of p1 and p2.
    2113              *
    2114              * In case of bezierDegree === 3:
    2115              * The slope dy / dx of the tangent is determined. Then the
    2116              * tangent is computed as cross product between
    2117              * the glider p and [1, p.X() + dx, p.Y() + dy]
    2118              *
    2119              */
    2120             getCurveTangentDir = function (position, c, num) {
    2121                 var i = Math.floor(position),
    2122                     p1, p2, t, A, B, C, D, dx, dy, d,
    2123                     points, le;
    2124
    2125                 if (c.bezierDegree === 1) {
    2126                     if (i === c.numberPoints - 1) {
    2127                         i--;
    2128                     }
    2129                 } else if (c.bezierDegree === 3) {
    2130                     // i is start of the Bezier segment
    2131                     // t is the position in the Bezier segment
    2132                     if (c.elType === 'sector') {
    2133                         points = c.points.slice(3, c.numberPoints - 3);
    2134                         le = points.length;
    2135                     } else {
    2136                         points = c.points;
    2137                         le = points.length;
    2138                     }
    2139                     i = Math.floor((position * (le - 1)) / 3) * 3;
    2140                     t = (position * (le - 1) - i) / 3;
    2141                     if (i >= le - 1) {
    2142                         i = le - 4;
    2143                         t = 1;
    2144                     }
    2145                 } else {
    2146                     return 0;
    2147                 }
    2148
    2149                 if (i < 0) {
    2150                     return 1;
    2151                 }
    2152
    2153                 // The curve points are transformed (if there is a transformation)
    2154                 // c.X(i) is not transformed.
    2155                 if (c.bezierDegree === 1) {
    2156                     p1 = c.points[i].usrCoords;
    2157                     p2 = c.points[i + 1].usrCoords;
    2158                 } else {
    2159                     A = points[i].usrCoords;
    2160                     B = points[i + 1].usrCoords;
    2161                     C = points[i + 2].usrCoords;
    2162                     D = points[i + 3].usrCoords;
    2163                     dx = (1 - t) * (1 - t) * (B[1] - A[1]) +
    2164                         2 * (1 - t) * t * (C[1] - B[1]) +
    2165                         t * t * (D[1] - C[1]);
    2166                     dy = (1 - t) * (1 - t) * (B[2] - A[2]) +
    2167                         2 * (1 - t) * t * (C[2] - B[2]) +
    2168                         t * t * (D[2] - C[2]);
    2169                     d = Mat.hypot(dx, dy);
    2170                     dx /= d;
    2171                     dy /= d;
    2172                     p1 = p.coords.usrCoords;
    2173                     p2 = [1, p1[1] + dx, p1[2] + dy];
    2174                 }
    2175
    2176                 switch (num) {
    2177                     case 0:
    2178                         return p1[2] * p2[1] - p1[1] * p2[2];
    2179                     case 1:
    2180                         return p2[2] - p1[2];
    2181                     case 2:
    2182                         return p1[1] - p2[1];
    2183                     default:
    2184                         return [
    2185                             p1[2] * p2[1] - p1[1] * p2[2],
    2186                             p2[2] - p1[2],
    2187                             p1[1] - p2[1]
    2188                         ];
    2189                 }
    2190             };
    2191
    2192             tangent = board.create(
    2193                 "line",
    2194                 [
    2195                     function () {
    2196                         var t;
    2197
    2198                         if (p.type === Const.OBJECT_TYPE_GLIDER) {
    2199                             t = p.position;
    2200                         } else {
    2201                             t = Geometry.projectPointToCurve(p, c, board)[1];
    2202                         }
    2203
    2204                         return getCurveTangentDir(t, c);
    2205                     }
    2206                 ],
    2207                 attr
    2208             );
    2209
    2210             p.addChild(tangent);
    2211             // this is required for the geogebra reader to display a slope
    2212             tangent.glider = p;
    2213         }
    2214     } else if (c.type === Const.OBJECT_TYPE_TURTLE) {
    2215         tangent = board.create(
    2216             "line",
    2217             [
    2218                 function () {
    2219                     var i, t;
    2220                     if (p.type === Const.OBJECT_TYPE_GLIDER) {
    2221                         t = p.position;
    2222                     } else {
    2223                         t = Geometry.projectPointToTurtle(p, c, board)[1];
    2224                     }
    2225
    2226                     i = Math.floor(t);
    2227
    2228                     // run through all curves of this turtle
    2229                     for (j = 0; j < c.objects.length; j++) {
    2230                         el = c.objects[j];
    2231
    2232                         if (el.type === Const.OBJECT_TYPE_CURVE) {
    2233                             if (i < el.numberPoints) {
    2234                                 break;
    2235                             }
    2236
    2237                             i -= el.numberPoints;
    2238                         }
    2239                     }
    2240
    2241                     if (i === el.numberPoints - 1) {
    2242                         i--;
    2243                     }
    2244
    2245                     if (i < 0) {
    2246                         return [1, 0, 0];
    2247                     }
    2248
    2249                     return [
    2250                         el.Y(i) * el.X(i + 1) - el.X(i) * el.Y(i + 1),
    2251                         el.Y(i + 1) - el.Y(i),
    2252                         el.X(i) - el.X(i + 1)
    2253                     ];
    2254                 }
    2255             ],
    2256             attr
    2257         );
    2258         p.addChild(tangent);
    2259
    2260         // this is required for the geogebra reader to display a slope
    2261         tangent.glider = p;
    2262     } else if (
    2263         c.elementClass === Const.OBJECT_CLASS_CIRCLE ||
    2264         c.type === Const.OBJECT_TYPE_CONIC
    2265     ) {
    2266         // If p is not on c, the tangent is the polar.
    2267         // This construction should work on conics, too. p has to lie on c.
    2268         tangent = board.create(
    2269             "line",
    2270             [
    2271                 function () {
    2272                     return Mat.matVecMult(c.quadraticform, p.coords.usrCoords);
    2273                 }
    2274             ],
    2275             attr
    2276         );
    2277
    2278         p.addChild(tangent);
    2279         // this is required for the geogebra reader to display a slope
    2280         tangent.glider = p;
    2281     }
    2282
    2283     if (!Type.exists(tangent)) {
    2284         throw new Error("JSXGraph: Couldn't create tangent with the given parents.");
    2285     }
    2286
    2287     tangent.elType = "tangent";
    2288     tangent.type = Const.OBJECT_TYPE_TANGENT;
    2289     tangent.setParents(parents);
    2290
    2291     return tangent;
    2292 };
    2293
    2294 /**
    2295  * @class Constructs a normal.
    2296  * @pseudo
    2297  * @description A normal is a line through a given point on an element of type line, circle, curve, or turtle and orthogonal to that object.
    2298  * @constructor
    2299  * @name Normal
    2300  * @type JXG.Line
    2301  * @augments JXG.Line
    2302  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
    2303  * @param {JXG.Line,JXG.Circle,JXG.Curve,JXG.Turtle_JXG.Point} o,p The constructed line contains p which lies on the object and is orthogonal
    2304  * to the tangent to the object in the given point.
    2305  * @param {Glider} p Works like above, however the object is given by {@link JXG.CoordsElement#slideObject}.
    2306  * @example
    2307  * // Create a normal to a circle.
    2308  * var p1 = board.create('point', [2.0, 2.0]);
    2309  * var p2 = board.create('point', [3.0, 2.0]);
    2310  * var c1 = board.create('circle', [p1, p2]);
    2311  *
    2312  * var norm1 = board.create('normal', [c1, p2]);
    2313  * </pre><div class="jxgbox" id="JXG4154753d-3d29-40fb-a860-0b08aa4f3743" style="width: 400px; height: 400px;"></div>
    2314  * <script type="text/javascript">
    2315  *   var nlex1_board = JXG.JSXGraph.initBoard('JXG4154753d-3d29-40fb-a860-0b08aa4f3743', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
    2316  *   var nlex1_p1 = nlex1_board.create('point', [2.0, 2.0]);
    2317  *   var nlex1_p2 = nlex1_board.create('point', [3.0, 2.0]);
    2318  *   var nlex1_c1 = nlex1_board.create('circle', [nlex1_p1, nlex1_p2]);
    2319  *
    2320  *   // var nlex1_p3 = nlex1_board.create('point', [1.0, 2.0]);
    2321  *   var nlex1_norm1 = nlex1_board.create('normal', [nlex1_c1, nlex1_p2]);
    2322  * </script><pre>
    2323  */
    2324 JXG.createNormal = function (board, parents, attributes) {
    2325     var p, c, l, i, attr, pp, attrp,
    2326         getCurveNormalDir,
    2327         res, isTransformed,
    2328         slides = [];
    2329
    2330     for (i = 0; i < parents.length; ++i) {
    2331         parents[i] = board.select(parents[i]);
    2332     }
    2333     // One arguments: glider on line, circle or curve
    2334     if (parents.length === 1) {
    2335         p = parents[0];
    2336         c = p.slideObject;
    2337         // Two arguments: (point,line), (point,circle), (line,point) or (circle,point)
    2338     } else if (parents.length === 2) {
    2339         if (Type.isPointType(board, parents[0])) {
    2340             p = Type.providePoints(board, [parents[0]], attributes, "point")[0];
    2341             c = parents[1];
    2342         } else if (Type.isPointType(board, parents[1])) {
    2343             c = parents[0];
    2344             p = Type.providePoints(board, [parents[1]], attributes, "point")[0];
    2345         } else {
    2346             throw new Error(
    2347                 "JSXGraph: Can't create normal with parent types '" +
    2348                 typeof parents[0] +
    2349                 "' and '" +
    2350                 typeof parents[1] +
    2351                 "'." +
    2352                 "\nPossible parent types: [point,line], [point,circle], [glider]"
    2353             );
    2354         }
    2355     } else {
    2356         throw new Error(
    2357             "JSXGraph: Can't create normal with parent types '" +
    2358             typeof parents[0] +
    2359             "' and '" +
    2360             typeof parents[1] +
    2361             "'." +
    2362             "\nPossible parent types: [point,line], [point,circle], [glider]"
    2363         );
    2364     }
    2365
    2366     attr = Type.copyAttributes(attributes, board.options, "normal");
    2367     if (c.elementClass === Const.OBJECT_CLASS_LINE) {
    2368         // Private point
    2369         attrp = Type.copyAttributes(attributes, board.options, "normal", "point");
    2370         pp = board.create(
    2371             "point",
    2372             [
    2373                 function () {
    2374                     var p = Mat.crossProduct([1, 0, 0], c.stdform);
    2375                     return [p[0], -p[2], p[1]];
    2376                 }
    2377             ],
    2378             attrp
    2379         );
    2380         pp.isDraggable = true;
    2381
    2382         l = board.create("line", [p, pp], attr);
    2383
    2384         /**
    2385          * A helper point used to create a normal to a {@link JXG.Line} object. For normals to circles or curves this
    2386          * element is <tt>undefined</tt>.
    2387          * @type JXG.Point
    2388          * @name point
    2389          * @memberOf Normal.prototype
    2390          */
    2391         l.point = pp;
    2392         l.subs = {
    2393             point: pp
    2394         };
    2395         l.inherits.push(pp);
    2396     } else if (c.elementClass === Const.OBJECT_CLASS_CIRCLE) {
    2397         l = board.create("line", [c.midpoint, p], attr);
    2398     } else if (c.elementClass === Const.OBJECT_CLASS_CURVE) {
    2399         res = c.getTransformationSource();
    2400         isTransformed = res[0];
    2401         if (isTransformed) {
    2402             // Curve is result of a transformation
    2403             // We recursively collect all curves from which
    2404             // the curve is transformed.
    2405             slides.push(c);
    2406             while (res[0] && Type.exists(res[1]._transformationSource)) {
    2407                 slides.push(res[1]);
    2408                 res = res[1].getTransformationSource();
    2409             }
    2410         }
    2411
    2412         if (Type.evaluate(c.visProp.curvetype) !== "plot" || isTransformed) {
    2413             // Functiongraph or parametric curve or
    2414             // transformed curve thereof.
    2415             l = board.create(
    2416                 "line",
    2417                 [
    2418                     function () {
    2419                         var g = c.X,
    2420                             f = c.Y,
    2421                             df, dg,
    2422                             li, i, c_org, invMat, po,
    2423                             t;
    2424
    2425                         if (p.type === Const.OBJECT_TYPE_GLIDER) {
    2426                             t = p.position;
    2427                         } else if (Type.evaluate(c.visProp.curvetype) === 'functiongraph') {
    2428                             t = p.X();
    2429                         } else {
    2430                             t = Geometry.projectPointToCurve(p, c, board)[1];
    2431                         }
    2432
    2433                         // po are the coordinates of the point
    2434                         // on the "original" curve. That is the curve or
    2435                         // the original curve which is transformed (maybe multiple times)
    2436                         // to this curve.
    2437                         // t is the position of the point on the "original" curve
    2438                         po = p.Coords(true);
    2439                         if (isTransformed) {
    2440                             c_org = slides[slides.length - 1]._transformationSource;
    2441                             g = c_org.X;
    2442                             f = c_org.Y;
    2443                             for (i = 0; i < slides.length; i++) {
    2444                                 slides[i].updateTransformMatrix();
    2445                                 invMat = Mat.inverse(slides[i].transformMat);
    2446                                 po = Mat.matVecMult(invMat, po);
    2447                             }
    2448
    2449                             if (p.type !== Const.OBJECT_TYPE_GLIDER) {
    2450                                 po[1] /= po[0];
    2451                                 po[2] /= po[0];
    2452                                 po[0] /= po[0];
    2453                                 t = Geometry.projectCoordsToCurve(po[1], po[2], 0, c_org, board)[1];
    2454                             }
    2455                         }
    2456
    2457                         df = Numerics.D(f)(t);
    2458                         dg = Numerics.D(g)(t);
    2459                         li = [
    2460                             -po[1] * dg - po[2] * df,
    2461                             po[0] * dg,
    2462                             po[0] * df
    2463                         ];
    2464
    2465                         if (isTransformed) {
    2466                             // Transform the line to the transformed curve
    2467                             for (i = slides.length - 1; i >= 0; i--) {
    2468                                 invMat = Mat.transpose(Mat.inverse(slides[i].transformMat));
    2469                                 li = Mat.matVecMult(invMat, li);
    2470                             }
    2471                         }
    2472
    2473                         return li;
    2474                     }
    2475                 ],
    2476                 attr
    2477             );
    2478         } else {
    2479             // curveType 'plot': discrete data
    2480             getCurveNormalDir = function (position, c, num) {
    2481                 var i = Math.floor(position),
    2482                     lbda,
    2483                     p1, p2, t, A, B, C, D, dx, dy, d,
    2484                     li, p_org, pp,
    2485                     points, le;
    2486
    2487
    2488                 if (c.bezierDegree === 1) {
    2489                     if (i === c.numberPoints - 1) {
    2490                         i--;
    2491                     }
    2492                     t = position;
    2493                 } else if (c.bezierDegree === 3) {
    2494                     // i is start of the Bezier segment
    2495                     // t is the position in the Bezier segment
    2496                     if (c.elType === 'sector') {
    2497                         points = c.points.slice(3, c.numberPoints - 3);
    2498                         le = points.length;
    2499                     } else {
    2500                         points = c.points;
    2501                         le = points.length;
    2502                     }
    2503                     i = Math.floor((position * (le - 1)) / 3) * 3;
    2504                     t = (position * (le - 1) - i) / 3;
    2505                     if (i >= le - 1) {
    2506                         i = le - 4;
    2507                         t = 1;
    2508                     }
    2509                 } else {
    2510                     return 0;
    2511                 }
    2512
    2513                 if (i < 0) {
    2514                     return 1;
    2515                 }
    2516
    2517                 lbda = t - i;
    2518                 if (c.bezierDegree === 1) {
    2519                     p1 = c.points[i].usrCoords;
    2520                     p2 = c.points[i + 1].usrCoords;
    2521                     p_org = [
    2522                         p1[0] + lbda * (p2[0] - p1[0]),
    2523                         p1[1] + lbda * (p2[1] - p1[1]),
    2524                         p1[2] + lbda * (p2[2] - p1[2])
    2525                     ];
    2526                     li = Mat.crossProduct(p1, p2);
    2527                     pp = Mat.crossProduct([1, 0, 0], li);
    2528                     pp = [pp[0], -pp[2], pp[1]];
    2529                     li = Mat.crossProduct(p_org, pp);
    2530
    2531                 } else {
    2532                     A = points[i].usrCoords;
    2533                     B = points[i + 1].usrCoords;
    2534                     C = points[i + 2].usrCoords;
    2535                     D = points[i + 3].usrCoords;
    2536                     dx =
    2537                         (1 - t) * (1 - t) * (B[1] - A[1]) +
    2538                         2 * (1 - t) * t * (C[1] - B[1]) +
    2539                         t * t * (D[1] - C[1]);
    2540                     dy =
    2541                         (1 - t) * (1 - t) * (B[2] - A[2]) +
    2542                         2 * (1 - t) * t * (C[2] - B[2]) +
    2543                         t * t * (D[2] - C[2]);
    2544                     d = Mat.hypot(dx, dy);
    2545                     dx /= d;
    2546                     dy /= d;
    2547                     p1 = p.coords.usrCoords;
    2548                     p2 = [1, p1[1] - dy, p1[2] + dx];
    2549
    2550                     li = [
    2551                         p1[2] * p2[1] - p1[1] * p2[2],
    2552                         p2[2] - p1[2],
    2553                         p1[1] - p2[1]
    2554                     ];
    2555                 }
    2556
    2557                 switch (num) {
    2558                     case 0:
    2559                         return li[0];
    2560                     case 1:
    2561                         return li[1];
    2562                     case 2:
    2563                         return li[2];
    2564                     default:
    2565                         return li;
    2566                 }
    2567             };
    2568
    2569             l = board.create(
    2570                 "line",
    2571                 [
    2572                     function () {
    2573                         var t;
    2574
    2575                         if (p.type === Const.OBJECT_TYPE_GLIDER) {
    2576                             t = p.position;
    2577                         } else {
    2578                             t = Geometry.projectPointToCurve(p, c, board)[1];
    2579                         }
    2580
    2581                         return getCurveNormalDir(t, c);
    2582                     }
    2583                 ],
    2584                 attr
    2585             );
    2586             p.addChild(l);
    2587             l.glider = p;
    2588         }
    2589     } else if (c.type === Const.OBJECT_TYPE_TURTLE) {
    2590         l = board.create(
    2591             "line",
    2592             [
    2593                 function () {
    2594                     var el,
    2595                         j,
    2596                         i = Math.floor(p.position),
    2597                         lbda = p.position - i;
    2598
    2599                     // run through all curves of this turtle
    2600                     for (j = 0; j < c.objects.length; j++) {
    2601                         el = c.objects[j];
    2602
    2603                         if (el.type === Const.OBJECT_TYPE_CURVE) {
    2604                             if (i < el.numberPoints) {
    2605                                 break;
    2606                             }
    2607
    2608                             i -= el.numberPoints;
    2609                         }
    2610                     }
    2611
    2612                     if (i === el.numberPoints - 1) {
    2613                         i -= 1;
    2614                         lbda = 1;
    2615                     }
    2616
    2617                     if (i < 0) {
    2618                         return 1;
    2619                     }
    2620
    2621                     return (
    2622                         (el.Y(i) + lbda * (el.Y(i + 1) - el.Y(i))) * (el.Y(i) - el.Y(i + 1)) -
    2623                         (el.X(i) + lbda * (el.X(i + 1) - el.X(i))) * (el.X(i + 1) - el.X(i))
    2624                     );
    2625                 },
    2626                 function () {
    2627                     var el,
    2628                         j,
    2629                         i = Math.floor(p.position);
    2630
    2631                     // run through all curves of this turtle
    2632                     for (j = 0; j < c.objects.length; j++) {
    2633                         el = c.objects[j];
    2634                         if (el.type === Const.OBJECT_TYPE_CURVE) {
    2635                             if (i < el.numberPoints) {
    2636                                 break;
    2637                             }
    2638
    2639                             i -= el.numberPoints;
    2640                         }
    2641                     }
    2642
    2643                     if (i === el.numberPoints - 1) {
    2644                         i -= 1;
    2645                     }
    2646
    2647                     if (i < 0) {
    2648                         return 0;
    2649                     }
    2650
    2651                     return el.X(i + 1) - el.X(i);
    2652                 },
    2653                 function () {
    2654                     var el,
    2655                         j,
    2656                         i = Math.floor(p.position);
    2657
    2658                     // run through all curves of this turtle
    2659                     for (j = 0; j < c.objects.length; j++) {
    2660                         el = c.objects[j];
    2661                         if (el.type === Const.OBJECT_TYPE_CURVE) {
    2662                             if (i < el.numberPoints) {
    2663                                 break;
    2664                             }
    2665
    2666                             i -= el.numberPoints;
    2667                         }
    2668                     }
    2669
    2670                     if (i === el.numberPoints - 1) {
    2671                         i -= 1;
    2672                     }
    2673
    2674                     if (i < 0) {
    2675                         return 0;
    2676                     }
    2677
    2678                     return el.Y(i + 1) - el.Y(i);
    2679                 }
    2680             ],
    2681             attr
    2682         );
    2683     } else {
    2684         throw new Error(
    2685             "JSXGraph: Can't create normal with parent types '" +
    2686             typeof parents[0] +
    2687             "' and '" +
    2688             typeof parents[1] +
    2689             "'." +
    2690             "\nPossible parent types: [point,line], [point,circle], [glider]"
    2691         );
    2692     }
    2693
    2694     l.elType = "normal";
    2695     l.setParents(parents);
    2696
    2697     if (Type.exists(p._is_new)) {
    2698         l.addChild(p);
    2699         delete p._is_new;
    2700     } else {
    2701         p.addChild(l);
    2702     }
    2703     c.addChild(l);
    2704
    2705     return l;
    2706 };
    2707
    2708 /**
    2709  * @class This element is used to provide a constructor for the radical axis with respect to two circles with distinct centers.
    2710  * The angular bisector of the polar lines of the circle centers with respect to the other circle is always the radical axis.
    2711  * The radical axis passes through the intersection points when the circles intersect.
    2712  * 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.
    2713  * @pseudo
    2714  * @name RadicalAxis
    2715  * @augments JXG.Line
    2716  * @constructor
    2717  * @type JXG.Line
    2718  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    2719  * @param {JXG.Circle} circle Circle one of the two respective circles.
    2720  * @param {JXG.Circle} circle Circle the other of the two respective circles.
    2721  * @example
    2722  * // Create the radical axis line with respect to two circles
    2723  *   var board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-5018d0d17a98', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
    2724  *   var p1 = board.create('point', [2, 3]);
    2725  *   var p2 = board.create('point', [1, 4]);
    2726  *   var c1 = board.create('circle', [p1, p2]);
    2727  *   var p3 = board.create('point', [6, 5]);
    2728  *   var p4 = board.create('point', [8, 6]);
    2729  *   var c2 = board.create('circle', [p3, p4]);
    2730  *   var r1 = board.create('radicalaxis', [c1, c2]);
    2731  * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-5018d0d17a98" class="jxgbox" style="width:400px; height:400px;"></div>
    2732  * <script type='text/javascript'>
    2733  *   var rlex1_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-5018d0d17a98', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
    2734  *   var rlex1_p1 = rlex1_board.create('point', [2, 3]);
    2735  *   var rlex1_p2 = rlex1_board.create('point', [1, 4]);
    2736  *   var rlex1_c1 = rlex1_board.create('circle', [rlex1_p1, rlex1_p2]);
    2737  *   var rlex1_p3 = rlex1_board.create('point', [6, 5]);
    2738  *   var rlex1_p4 = rlex1_board.create('point', [8, 6]);
    2739  *   var rlex1_c2 = rlex1_board.create('circle', [rlex1_p3, rlex1_p4]);
    2740  *   var rlex1_r1 = rlex1_board.create('radicalaxis', [rlex1_c1, rlex1_c2]);
    2741  * </script><pre>
    2742  */
    2743 JXG.createRadicalAxis = function (board, parents, attributes) {
    2744     var el, el1, el2;
    2745
    2746     if (
    2747         parents.length !== 2 ||
    2748         parents[0].elementClass !== Const.OBJECT_CLASS_CIRCLE ||
    2749         parents[1].elementClass !== Const.OBJECT_CLASS_CIRCLE
    2750     ) {
    2751         // Failure
    2752         throw new Error(
    2753             "JSXGraph: Can't create 'radical axis' with parent types '" +
    2754             typeof parents[0] +
    2755             "' and '" +
    2756             typeof parents[1] +
    2757             "'." +
    2758             "\nPossible parent type: [circle,circle]"
    2759         );
    2760     }
    2761
    2762     el1 = board.select(parents[0]);
    2763     el2 = board.select(parents[1]);
    2764
    2765     el = board.create(
    2766         "line",
    2767         [
    2768             function () {
    2769                 var a = el1.stdform,
    2770                     b = el2.stdform;
    2771
    2772                 return Mat.matVecMult(Mat.transpose([a.slice(0, 3), b.slice(0, 3)]), [
    2773                     b[3],
    2774                     -a[3]
    2775                 ]);
    2776             }
    2777         ],
    2778         attributes
    2779     );
    2780
    2781     el.elType = "radicalaxis";
    2782     el.setParents([el1.id, el2.id]);
    2783
    2784     el1.addChild(el);
    2785     el2.addChild(el);
    2786
    2787     return el;
    2788 };
    2789
    2790 /**
    2791  * @class This element is used to provide a constructor for the polar line of a point with respect to a conic or a circle.
    2792  * @pseudo
    2793  * @description The polar line is the unique reciprocal relationship of a point with respect to a conic.
    2794  * 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.
    2795  * 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.
    2796  * See {@link https://en.wikipedia.org/wiki/Pole_and_polar} for more information on pole and polar.
    2797  * @name PolarLine
    2798  * @augments JXG.Line
    2799  * @constructor
    2800  * @type JXG.Line
    2801  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    2802  * @param {JXG.Conic,JXG.Circle_JXG.Point} el1,el2 or
    2803  * @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.
    2804  * @example
    2805  * // Create the polar line of a point with respect to a conic
    2806  * var p1 = board.create('point', [-1, 2]);
    2807  * var p2 = board.create('point', [ 1, 4]);
    2808  * var p3 = board.create('point', [-1,-2]);
    2809  * var p4 = board.create('point', [ 0, 0]);
    2810  * var p5 = board.create('point', [ 4,-2]);
    2811  * var c1 = board.create('conic',[p1,p2,p3,p4,p5]);
    2812  * var p6 = board.create('point', [-1, 1]);
    2813  * var l1 = board.create('polarline', [c1, p6]);
    2814  * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-6018d0d17a98" class="jxgbox" style="width:400px; height:400px;"></div>
    2815  * <script type='text/javascript'>
    2816  * var plex1_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-6018d0d17a98', {boundingbox: [-3, 5, 5, -3], axis: true, showcopyright: false, shownavigation: false});
    2817  * var plex1_p1 = plex1_board.create('point', [-1, 2]);
    2818  * var plex1_p2 = plex1_board.create('point', [ 1, 4]);
    2819  * var plex1_p3 = plex1_board.create('point', [-1,-2]);
    2820  * var plex1_p4 = plex1_board.create('point', [ 0, 0]);
    2821  * var plex1_p5 = plex1_board.create('point', [ 4,-2]);
    2822  * var plex1_c1 = plex1_board.create('conic',[plex1_p1,plex1_p2,plex1_p3,plex1_p4,plex1_p5]);
    2823  * var plex1_p6 = plex1_board.create('point', [-1, 1]);
    2824  * var plex1_l1 = plex1_board.create('polarline', [plex1_c1, plex1_p6]);
    2825  * </script><pre>
    2826  * @example
    2827  * // Create the polar line of a point with respect to a circle.
    2828  * var p1 = board.create('point', [ 1, 1]);
    2829  * var p2 = board.create('point', [ 2, 3]);
    2830  * var c1 = board.create('circle',[p1,p2]);
    2831  * var p3 = board.create('point', [ 6, 6]);
    2832  * var l1 = board.create('polarline', [c1, p3]);
    2833  * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-7018d0d17a98" class="jxgbox" style="width:400px; height:400px;"></div>
    2834  * <script type='text/javascript'>
    2835  * var plex2_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-7018d0d17a98', {boundingbox: [-3, 7, 7, -3], axis: true, showcopyright: false, shownavigation: false});
    2836  * var plex2_p1 = plex2_board.create('point', [ 1, 1]);
    2837  * var plex2_p2 = plex2_board.create('point', [ 2, 3]);
    2838  * var plex2_c1 = plex2_board.create('circle',[plex2_p1,plex2_p2]);
    2839  * var plex2_p3 = plex2_board.create('point', [ 6, 6]);
    2840  * var plex2_l1 = plex2_board.create('polarline', [plex2_c1, plex2_p3]);
    2841  * </script><pre>
    2842  */
    2843 JXG.createPolarLine = function (board, parents, attributes) {
    2844     var el,
    2845         el1,
    2846         el2,
    2847         firstParentIsConic,
    2848         secondParentIsConic,
    2849         firstParentIsPoint,
    2850         secondParentIsPoint;
    2851
    2852     if (parents.length > 1) {
    2853         firstParentIsConic =
    2854             parents[0].type === Const.OBJECT_TYPE_CONIC ||
    2855             parents[0].elementClass === Const.OBJECT_CLASS_CIRCLE;
    2856         secondParentIsConic =
    2857             parents[1].type === Const.OBJECT_TYPE_CONIC ||
    2858             parents[1].elementClass === Const.OBJECT_CLASS_CIRCLE;
    2859
    2860         firstParentIsPoint = Type.isPoint(parents[0]);
    2861         secondParentIsPoint = Type.isPoint(parents[1]);
    2862     }
    2863
    2864     if (
    2865         parents.length !== 2 ||
    2866         !(
    2867             (firstParentIsConic && secondParentIsPoint) ||
    2868             (firstParentIsPoint && secondParentIsConic)
    2869         )
    2870     ) {
    2871         // Failure
    2872         throw new Error(
    2873             "JSXGraph: Can't create 'polar line' with parent types '" +
    2874             typeof parents[0] +
    2875             "' and '" +
    2876             typeof parents[1] +
    2877             "'." +
    2878             "\nPossible parent type: [conic|circle,point], [point,conic|circle]"
    2879         );
    2880     }
    2881
    2882     if (secondParentIsPoint) {
    2883         el1 = board.select(parents[0]);
    2884         el2 = board.select(parents[1]);
    2885     } else {
    2886         el1 = board.select(parents[1]);
    2887         el2 = board.select(parents[0]);
    2888     }
    2889
    2890     // Polar lines have been already provided in the tangent element.
    2891     el = board.create("tangent", [el1, el2], attributes);
    2892
    2893     el.elType = "polarline";
    2894     return el;
    2895 };
    2896
    2897 /**
    2898  *
    2899  * @class This element is used to provide a constructor for the tangent through a point to a conic or a circle.
    2900  * @pseudo
    2901  * @description Construct the tangent line through a point to a conic or a circle. There will be either two, one or no
    2902  * such tangent, depending if the point is outside of the conic, on the conic, or inside of the conic.
    2903  * Similar to the intersection of a line with a circle, the specific tangent can be chosen with a third (optional) parameter
    2904  * <i>number</i>.
    2905  * <p>
    2906  * Attention: from a technical point of view, the point from which the tangent to the conic/circle is constructed is not an element of
    2907  * the tangent line.
    2908  * @name TangentTo
    2909  * @augments JXG.Line
    2910  * @constructor
    2911  * @type JXG.Line
    2912  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    2913  * @param {JXG.Conic,JXG.Circle_JXG.Point_Number} conic,point,[number=0] The result will be the tangent line through
    2914  * the point with respect to the conic or circle.
    2915  *
    2916  * @example
    2917  *  var c = board.create('circle', [[3, 0], [3, 4]]);
    2918  *  var p = board.create('point', [0, 6]);
    2919  *  var t0 = board.create('tangentto', [c, p, 0], { color: 'black', polar: {visible: true}, point: {visible: true} });
    2920  *  var t1 = board.create('tangentto', [c, p, 1], { color: 'black' });
    2921  *
    2922  * </pre><div id="JXGd4b359c7-3a29-44c3-a19d-d51b42a00c8b" class="jxgbox" style="width: 300px; height: 300px;"></div>
    2923  * <script type="text/javascript">
    2924  *     (function() {
    2925  *         var board = JXG.JSXGraph.initBoard('JXGd4b359c7-3a29-44c3-a19d-d51b42a00c8b',
    2926  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    2927  *             var c = board.create('circle', [[3, 0], [3, 4]]);
    2928  *             var p = board.create('point', [0, 6]);
    2929  *             var t0 = board.create('tangentto', [c, p, 0], { color: 'black', polar: {visible: true}, point: {visible: true} });
    2930  *             var t1 = board.create('tangentto', [c, p, 1], { color: 'black' });
    2931  *
    2932  *     })();
    2933  *
    2934  * </script><pre>
    2935  *
    2936  * @example
    2937  *  var p = board.create('point', [0, 6]);
    2938  *  var ell = board.create('ellipse', [[-5, 1], [-2, -1], [-3, 2]]);
    2939  *  var t0 = board.create('tangentto', [ell, p, 0]);
    2940  *  var t1 = board.create('tangentto', [ell, p, 1]);
    2941  *
    2942  * </pre><div id="JXG6e625663-1c3e-4e08-a9df-574972a374e8" class="jxgbox" style="width: 300px; height: 300px;"></div>
    2943  * <script type="text/javascript">
    2944  *     (function() {
    2945  *         var board = JXG.JSXGraph.initBoard('JXG6e625663-1c3e-4e08-a9df-574972a374e8',
    2946  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    2947  *             var p = board.create('point', [0, 6]);
    2948  *             var ell = board.create('ellipse', [[-5, 1], [-2, -1], [-3, 2]]);
    2949  *             var t0 = board.create('tangentto', [ell, p, 0]);
    2950  *             var t1 = board.create('tangentto', [ell, p, 1]);
    2951  *
    2952  *     })();
    2953  *
    2954  * </script><pre>
    2955  *
    2956  */
    2957 JXG.createTangentTo = function (board, parents, attributes) {
    2958     var el, attr,
    2959         conic, pointFrom, num,
    2960         intersect, polar;
    2961
    2962     conic = board.select(parents[0]);
    2963     pointFrom = Type.providePoints(board, parents[1], attributes, 'point')[0];
    2964     num = Type.def(parents[2], 0);
    2965
    2966     if (
    2967         (conic.type !== Const.OBJECT_TYPE_CIRCLE && conic.type !== Const.OBJECT_TYPE_CONIC) ||
    2968         (pointFrom.elementClass !== Const.OBJECT_CLASS_POINT)
    2969     ) {
    2970         throw new Error(
    2971             "JSXGraph: Can't create tangentto with parent types '" +
    2972             typeof parents[0] +
    2973             "' and '" +
    2974             typeof parents[1] +
    2975             "' and '" +
    2976             typeof parents[2] +
    2977             "'." +
    2978             "\nPossible parent types: [circle|conic,point,number]"
    2979         );
    2980     }
    2981
    2982     attr = Type.copyAttributes(attributes, board.options, 'tangentto');
    2983     // A direct analytic geometry approach would be in
    2984     // Richter-Gebert: Perspectives on projective geometry, 11.3
    2985     polar = board.create('polar', [conic, pointFrom], attr.polar);
    2986     intersect = board.create('intersection', [polar, conic, num], attr.point);
    2987
    2988     el = board.create('tangent', [conic, intersect], attr);
    2989
    2990     /**
    2991      * The intersection point of the conic/circle with the polar line of the tangentto construction.
    2992      * @memberOf TangentTo.prototype
    2993      * @name point
    2994      * @type JXG.Point
    2995      */
    2996     el.point = intersect;
    2997
    2998     /**
    2999      * The polar line of the tangentto construction.
    3000      * @memberOf TangentTo.prototype
    3001      * @name polar
    3002      * @type JXG.Line
    3003      */
    3004     el.polar = polar;
    3005
    3006     el.elType = 'tangentto';
    3007
    3008     return el;
    3009 };
    3010
    3011 /**
    3012  * Register the element type tangent at JSXGraph
    3013  * @private
    3014  */
    3015 JXG.registerElement("tangent", JXG.createTangent);
    3016 JXG.registerElement("normal", JXG.createNormal);
    3017 JXG.registerElement('tangentto', JXG.createTangentTo);
    3018 JXG.registerElement("polar", JXG.createTangent);
    3019 JXG.registerElement("radicalaxis", JXG.createRadicalAxis);
    3020 JXG.registerElement("polarline", JXG.createPolarLine);
    3021
    3022 export default JXG.Line;
    3023 // export default {
    3024 //     Line: JXG.Line,
    3025 //     createLine: JXG.createLine,
    3026 //     createTangent: JXG.createTangent,
    3027 //     createPolar: JXG.createTangent,
    3028 //     createSegment: JXG.createSegment,
    3029 //     createAxis: JXG.createAxis,
    3030 //     createArrow: JXG.createArrow,
    3031 //     createRadicalAxis: JXG.createRadicalAxis,
    3032 //     createPolarLine: JXG.createPolarLine
    3033 // };
    3034