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