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