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