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