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