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