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