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