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