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