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