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