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, id, name, withLabel, layer) {
 51     /* Call the constructor of GeometryElement */
 52     this.constructor();
 53 
 54    /**
 55      * Sets type of GeometryElement, value is OBJECT_TYPE_LINE.
 56      * @constant
 57      * @type int
 58      * @default JXG#OBJECT_TYPE_LINE
 59      * @private
 60      */
 61     this.type = JXG.OBJECT_TYPE_LINE;
 62 
 63     /**
 64      * Class of element, value is OBJECT_CLASS_LINE;
 65      * @type int
 66      * @constant
 67      * @default JXG#OBJECT_CLASS_LINE
 68      * @private
 69      */
 70     this.elementClass = JXG.OBJECT_CLASS_LINE;
 71 
 72     this.init(board, id, name);
 73 
 74     /**
 75      * Set the display layer.
 76      */
 77     if (layer == null) layer = board.options.layer['line'];
 78     this.layer = layer;
 79     
 80     /**
 81      * Startpoint of the line. You really should not set this field directly as it may break JSXGraph's
 82      * udpate system so your construction won't be updated properly.
 83      * @type JXG.Point
 84      */
 85     this.point1 = JXG.getReference(this.board, p1);
 86 
 87     /**
 88      * Endpoint of the line. Just like {@link #point1} you shouldn't write this field directly.
 89      * @type JXG.Point
 90      */
 91     this.point2 = JXG.getReference(this.board, p2);
 92 
 93     /**
 94      * This is just for the hasPoint() method.
 95      * @type int
 96      * @private
 97      */
 98     //this.r = this.board.options.precision.hasPoint;
 99 
100     /* Wurde in GeometryElement schon dokumentiert. */
101     this.visProp['fillColor'] = this.board.options.line.fillColor;
102     this.visProp['highlightFillColor'] = this.board.options.line.highlightFillColor;
103     this.visProp['strokeColor'] = this.board.options.line.strokeColor;
104     this.visProp['highlightStrokeColor'] = this.board.options.line.highlightStrokeColor;
105 
106     /**
107      * Determines if a line is drawn beyond {@link #point1}.
108      * @name JXG.Line#straightFirst
109      * @type boolean
110      * @default JXG.Options.line#straightFirst
111      * @field
112      * @see JXG.Line#straightLast
113      */
114     this.visProp['straightFirst'] = this.board.options.line.straightFirst;
115 
116     /**
117      * Determines if a line is drawn beyond {@link #point2}.
118      * @name JXG.Line#straightLast
119      * @type boolean
120      * @default JXG.Options.line#straightLast
121      * @field
122      * @see JXG.Line#straightFirst
123      */
124     this.visProp['straightLast'] = this.board.options.line.straightLast;
125 
126     /* Already documented in JXG.GeometryElement */
127     this.visProp['visible'] = true;
128 
129     /**
130      * Determines if a line has an arrow at {@link #point1}.
131      * @name JXG.Line#firstArrow
132      * @type boolean
133      * @default JXG.Options.line#firstArrow
134      * @field
135      * @see JXG.Line#lastArrow
136      */
137     this.visProp['firstArrow'] = this.board.options.line.firstArrow;
138 
139     /**
140      * Determines if a line has an arrow at {@link #point1}.
141      * @name JXG.Line#lastArrow
142      * @type boolean
143      * @default JXG.Options.line#lastArrow
144      * @field
145      * @see JXG.Line#firstArrow
146      */
147     this.visProp['lastArrow'] = this.board.options.line.lastArrow;
148 
149     /**
150      * Array of ticks storing all the ticks on this line. Do not set this field directly and use
151      * {@link #addTicks} and {@link #removeTicks} to add and remove ticks to and from the line.
152      * @type array
153      * @see JXG.Ticks
154      */
155     this.ticks = [];
156 
157     /**
158      * Reference of the ticks created automatically when constructing an axis.
159      * @type JXG.Ticks
160      * @see JXG.Ticks
161      */
162     this.defaultTicks = null;
163 
164     /**
165     * If the line is the border of a polygon, the polygon object is stored, otherwise null.
166     * @type JXG.Polygon
167     * @default null
168     * @private
169     */
170     this.parentPolygon = null;
171 
172     /**
173     * Label offsets from label anchor
174     * @type array
175      * @default JXG.Options.line#labelOffsets
176     * @private
177     */
178     //this.labelOffsets = [].concat(this.board.options.line.labelOffsets);
179     this.labelOffsets = [].concat(this.board.options.line.labelOffsets);
180     //make sure we have absolute values
181     this.labelOffsets[0] = Math.abs(this.labelOffsets[0]);
182     this.labelOffsets[1] = Math.abs(this.labelOffsets[1]);
183 
184     // create Label
185     this.createLabel(withLabel);
186 
187     /* Register line at board */
188     this.id = this.board.setId(this, 'L');
189     this.board.renderer.drawLine(this);
190     this.board.finalizeAdding(this);
191 
192     /* Add arrow as child to defining points */
193     this.point1.addChild(this);
194     this.point2.addChild(this);
195     this.needsUpdate = true; this.update();
196 };
197 
198 JXG.Line.prototype = new JXG.GeometryElement;
199 
200 /**
201  * Checks whether (x,y) is near the line.
202  * @param {int} x Coordinate in x direction, screen coordinates.
203  * @param {int} y Coordinate in y direction, screen coordinates.
204  * @return {boolean} True if (x,y) is near the line, False otherwise.
205  */
206  JXG.Line.prototype.hasPoint = function (x, y) {
207     // Compute the stdform of the line in screen coordinates.
208     var c = [], s,
209         v = [1,x,y],
210         vnew = [],
211         mu, i, coords, p1Scr, p2Scr, distP1P, distP2P, distP1P2;
212 
213     c[0] = this.stdform[0] -
214                 this.stdform[1]*this.board.origin.scrCoords[1]/this.board.stretchX+
215                 this.stdform[2]*this.board.origin.scrCoords[2]/this.board.stretchY;
216     c[1] = this.stdform[1]/this.board.stretchX;
217     c[2] = this.stdform[2]/(-this.board.stretchY);
218 
219     // Project the point orthogonally onto the line 
220     var vnew = [0,c[1],c[2]];
221     vnew = JXG.Math.crossProduct(vnew,v); // Orthogonal line to c through v
222     vnew = JXG.Math.crossProduct(vnew,c); // Intersect orthogonal line with line
223 
224     // Normalize the projected point
225     vnew[1] /= vnew[0];
226     vnew[2] /= vnew[0];
227     vnew[0] = 1.0;
228     
229     // The point is too far away from the line
230     // dist(v,vnew)^2 projective
231     //if (JXG.Math.Geometry.distance(v,vnew)>this.board.options.precision.hasPoint) {
232     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]);
233     if (isNaN(s) || s>this.board.options.precision.hasPoint*this.board.options.precision.hasPoint) {
234         return false;
235     }
236 
237     if(this.visProp['straightFirst'] && this.visProp['straightLast']) {
238         return true;
239     } else { // If the line is a ray or segment we have to check if the projected point is "inside" P1 and P2.
240 /*
241         coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [vnew[1],vnew[2]], this.board);
242         p1Scr = this.point1.coords.scrCoords;
243         p2Scr = this.point2.coords.scrCoords;
244         distP1P = coords.distance(JXG.COORDS_BY_SCREEN, this.point1.coords);
245         distP2P = coords.distance(JXG.COORDS_BY_SCREEN, this.point2.coords);
246         distP1P2 = this.point1.coords.distance(JXG.COORDS_BY_SCREEN, this.point2.coords);
247 */
248         p1Scr = this.point1.coords.scrCoords;
249         p2Scr = this.point2.coords.scrCoords;
250         distP1P2 = (p2Scr[1]-p1Scr[1])*(p2Scr[1]-p1Scr[1])+(p2Scr[2]-p1Scr[2])*(p2Scr[2]-p1Scr[2]);  // dist(p1,p2)^2 affine
251         distP1P = (vnew[1]-p1Scr[1])*(vnew[1]-p1Scr[1])+(vnew[2]-p1Scr[2])*(vnew[2]-p1Scr[2]);       // dist(vnew,p1)^2 affine
252         distP2P = (vnew[1]-p2Scr[1])*(vnew[1]-p2Scr[1])+(vnew[2]-p2Scr[2])*(vnew[2]-p2Scr[2]);       // dist(vnew,p2)^2 affine
253 
254         if((distP1P > distP1P2) || (distP2P > distP1P2)) { // Check if P(x|y) is not between  P1 and P2
255             if(distP1P < distP2P) { // P liegt auf der Seite von P1
256                 if(!this.visProp['straightFirst']) {
257                     return false;
258                 }
259             } else { // P liegt auf der Seite von P2
260                 if(!this.visProp['straightLast']) {
261                     return false;
262                 }
263             }
264         }
265         return true;
266     }
267 };
268 
269 /**
270  * TODO description. maybe. already documented in geometryelement?
271  * @private
272  */
273 JXG.Line.prototype.update = function() {
274     var i, funps;
275 
276     if(this.constrained) {
277     	if(typeof this.funps != 'undefined') {
278     		funps = this.funps();
279     		this.point1 = funps[0];
280     		this.point2 = funps[1];
281     	} else {
282             this.point1 = this.funp1();
283             this.point2 = this.funp2();
284     	}
285     }
286 
287     if (this.needsUpdate) {
288         if (true || !this.board.geonextCompatibilityMode) {
289             this.updateStdform();
290         }
291         /*
292         // This is now done in Ticks.updateRenderer()
293         for(i=0; i<this.ticks.length; i++) {
294             // i don't know why we need this, but if we don't check it, an error will be reported
295             // when the origin is moved. it seems like this.ticks.length is lying.
296             if(typeof this.ticks[i] != 'undefined')
297                 this.ticks[i].calculateTicksCoordinates();
298         }
299         */
300     }
301     if(this.traced) {
302         this.cloneToBackground(true);
303     }
304 };
305 
306 /**
307  * TODO description. already documented in geometryelement?
308  * @private
309  */
310 JXG.Line.prototype.updateStdform = function() {
311    /*
312     var nx = -(this.point2.coords.usrCoords[2]-this.point1.coords.usrCoords[2]);
313     var ny =  this.point2.coords.usrCoords[1]-this.point1.coords.usrCoords[1];
314     var c = -(nx*this.point1.coords.usrCoords[1]+ny*this.point1.coords.usrCoords[2]);
315 
316     this.stdform[0] = c;
317     this.stdform[1] = nx;
318     this.stdform[2] = ny;
319     */
320     var v = JXG.Math.crossProduct(this.point1.coords.usrCoords,this.point2.coords.usrCoords);
321     this.stdform[0] = v[0];
322     this.stdform[1] = v[1];
323     this.stdform[2] = v[2];
324     this.stdform[3] = 0;
325     this.normalize();
326 };
327 
328 /**
329  * Uses the boards renderer to update the line.
330  * @private
331  */
332  JXG.Line.prototype.updateRenderer = function () {
333     var wasReal, i;
334     if (this.needsUpdate && this.visProp['visible']) {
335         wasReal = this.isReal;
336         this.isReal = (isNaN(this.point1.coords.usrCoords[1]+this.point1.coords.usrCoords[2]+this.point2.coords.usrCoords[1]+this.point2.coords.usrCoords[2]))?false:true;
337         if (this.isReal) {
338             if (wasReal!=this.isReal) {
339                 this.board.renderer.show(this);
340                 if(this.hasLabel && this.label.content.visProp['visible']) this.board.renderer.show(this.label.content);
341             }
342             this.board.renderer.updateLine(this);
343 /*            
344             if (this.board.options.renderer == 'canvas') { 
345                 for (i=0;i<this.ticks.length;i++)                   // This is necessary for the CanvasRenderer
346                     this.ticks[i].prepareUpdate().updateRenderer(); // No idea, why other Renderer work without it.
347             }
348 */            
349         } else {
350             if (wasReal!=this.isReal) {
351                 this.board.renderer.hide(this);
352                 if(this.hasLabel && this.label.content.visProp['visible']) this.board.renderer.hide(this.label.content);
353             }
354         }
355 
356         //this.board.renderer.updateLine(this); // Why should we need this?
357         this.needsUpdate = false;
358     }
359 
360     /* Update the label if visible. */
361     if(this.hasLabel && this.label.content.visProp['visible'] && this.isReal) {
362         //this.label.setCoordinates(this.coords);
363         this.label.content.update();
364         //this.board.renderer.updateLabel(this.label);
365         this.board.renderer.updateText(this.label.content);
366     }
367 };
368 
369 /**
370  * Used to generate a polynomial for a point p that lies on this line, i.e. p is collinear to {@link #point1}
371  * and {@link #point2}.
372  * @param p The point for that the polynomial is generated.
373  * @return An array containing the generated polynomial.
374  * @private
375  */
376 JXG.Line.prototype.generatePolynomial = function (/** JXG.Point */ p) /** array */{
377     var u1 = this.point1.symbolic.x,
378         u2 = this.point1.symbolic.y,
379         v1 = this.point2.symbolic.x,
380         v2 = this.point2.symbolic.y,
381         w1 = p.symbolic.x,
382         w2 = p.symbolic.y;
383 
384     /*
385      * The polynomial in this case is determined by three points being collinear:
386      *
387      *      U (u1,u2)      W (w1,w2)                V (v1,v2)
388      *  ----x--------------x------------------------x----------------
389      *
390      *  The collinearity condition is
391      *
392      *      u2-w2       w2-v2
393      *     -------  =  -------           (1)
394      *      u1-w1       w1-v1
395      *
396      * Multiplying (1) with denominators and simplifying is
397      *
398      *    u2w1 - u2v1 + w2v1 - u1w2 + u1v2 - w1v2 = 0
399      */
400 
401     return [['(',u2,')*(',w1,')-(',u2,')*(',v1,')+(',w2,')*(',v1,')-(',u1,')*(',w2,')+(',u1,')*(',v2,')-(',w1,')*(',v2,')'].join('')];
402 };
403 
404 /**
405  * Calculates the rise of the line.
406  * @type float
407  * @return The rise of the line.
408  */
409 JXG.Line.prototype.getRise = function () {
410     if (Math.abs(this.stdform[2])>=JXG.Math.eps) {
411         return -this.stdform[0]/this.stdform[2];
412     } else {
413         return Infinity;
414     }
415 };
416 
417 /**
418  * Calculates the slope of the line.
419  * @type float
420  * @return The slope of the line or Infinity if the line is parallel to the y-axis.
421  */
422 JXG.Line.prototype.getSlope = function () {
423     if (Math.abs(this.stdform[2])>=JXG.Math.eps) {
424         return -this.stdform[1]/this.stdform[2];
425     } else {
426         return Infinity;
427     }
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  JXG.Line.prototype.setStraight = function (straightFirst, straightLast) {
439     this.visProp['straightFirst'] = straightFirst;
440     this.visProp['straightLast'] = straightLast;
441 
442     this.board.renderer.updateLine(this);
443 };
444 
445 /**
446  * Determines whether the line has arrows at start or end of the line. Is stored in visProp['firstArrow'] and visProp['lastArrow']
447  * @param {boolean} firstArrow True if there is an arrow at the start of the line, false otherwise.
448  * @param {boolean} lastArrow True if there is an arrow at the end of the line, false otherwise.
449  * @private
450  */
451 /*
452 JXG.Line.prototype.setArrow = function (firstArrow, lastArrow) {
453      this.visProp['firstArrow'] = firstArrow;
454      this.visProp['lastArrow'] = lastArrow;
455 
456      this.board.renderer.updateLine(this);
457 };
458 */
459 
460 /**
461  * Calculates TextAnchor. DESCRIPTION
462  * @type JXG.Coords
463  * @return Text anchor coordinates as JXG.Coords object.
464  * @private
465  */
466 JXG.Line.prototype.getTextAnchor = function() {
467     return new JXG.Coords(JXG.COORDS_BY_USER, [this.point1.X()+0.5*(this.point2.X() - this.point1.X()),this.point1.Y() +0.5*(this.point2.Y() - this.point1.Y())],this.board);
468 };
469 
470 /**
471  * Adjusts Label coords relative to Anchor. DESCRIPTION
472  * @private
473  */
474 JXG.Line.prototype.setLabelRelativeCoords = function(relCoords) {
475     if (typeof this.label.content!='undefined') { 
476         this.label.content.relativeCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [relCoords[0],-relCoords[1]],this.board);
477     }
478 }
479 
480 /**
481  * Calculates LabelAnchor. DESCRIPTION
482  * @type JXG.Coords
483  * @return Text anchor coordinates as JXG.Coords object.
484  * @private
485  */
486 JXG.Line.prototype.getLabelAnchor = function() {
487     var coords,screenCoords1,screenCoords2,
488         relCoords, slope, xoffset = this.labelOffsets[0], yoffset = this.labelOffsets[1];
489 
490     if(!this.visProp['straightFirst'] && !this.visProp['straightLast']) {
491         this.setLabelRelativeCoords(this.labelOffsets);
492         return new JXG.Coords(JXG.COORDS_BY_USER, [this.point2.X()-0.5*(this.point2.X() - this.point1.X()),this.point2.Y()-0.5*(this.point2.Y() - this.point1.Y())],this.board);
493     }
494     else {
495         screenCoords1 = new JXG.Coords(JXG.COORDS_BY_USER, this.point1.coords.usrCoords, this.board);
496         screenCoords2 = new JXG.Coords(JXG.COORDS_BY_USER, this.point2.coords.usrCoords, this.board);
497         this.board.renderer.calcStraight(this, screenCoords1, screenCoords2);
498 
499         if(this.visProp['straightFirst']) {
500             coords = screenCoords1;
501         }
502         else {
503             coords = screenCoords2;
504         }
505         // Hack
506         if(this.label.content != null) {
507             relCoords = [0,0];
508             slope = this.getSlope();
509             if(coords.scrCoords[2]==0) {
510                 if(slope == Infinity) {
511                     relCoords = [xoffset,-yoffset];
512                 }
513                 else if(slope >= 0) {
514                     relCoords = [xoffset,-yoffset];
515                 }
516                 else {
517                     relCoords = [-xoffset,-yoffset];
518                 }
519             }
520             else if(coords.scrCoords[2]==this.board.canvasHeight) {
521                 if(slope == Infinity) {
522                     relCoords = [xoffset,yoffset];
523                 }
524                 else if(slope >= 0) {
525                     relCoords = [-xoffset,yoffset];
526                 }
527                 else {
528                     relCoords = [xoffset,yoffset];
529                 }
530             }
531             if(coords.scrCoords[1]==0) {
532                 if(slope == Infinity) {
533                     relCoords = [xoffset,yoffset]; // ??
534                 }
535                 else if(slope >= 0) {
536                     relCoords = [xoffset,-yoffset];
537                 }
538                 else {
539                     relCoords = [xoffset,yoffset];
540                 }
541             }
542             else if(coords.scrCoords[1]==this.board.canvasWidth) {
543                 if(slope == Infinity) {
544                     relCoords = [-xoffset,yoffset]; // ??
545                 }
546                 else if(slope >= 0) {
547                     relCoords = [-xoffset,yoffset];
548                 }
549                 else {
550                     relCoords = [-xoffset,-yoffset];
551                 }
552             }
553             this.setLabelRelativeCoords(relCoords);
554         }
555         return coords;
556     }
557 };
558 
559 /**
560  * Clone the element to the background to leave a trail of it on the board.
561  * @param {boolean} addToTrace Not used.
562  */
563 JXG.Line.prototype.cloneToBackground = function(addToTrace) {
564     var copy = {}, r, s, er;
565 
566     copy.id = this.id + 'T' + this.numTraces;
567     copy.elementClass = JXG.OBJECT_CLASS_LINE;
568     this.numTraces++;
569     copy.point1 = this.point1;
570     copy.point2 = this.point2;
571 
572     copy.stdform = this.stdform;
573 
574     copy.board = {};
575     copy.board.unitX = this.board.unitX;
576     copy.board.unitY = this.board.unitY;
577     copy.board.zoomX = this.board.zoomX;
578     copy.board.zoomY = this.board.zoomY;
579     copy.board.stretchX = this.board.stretchX;
580     copy.board.stretchY = this.board.stretchY;
581     copy.board.origin = this.board.origin;
582     copy.board.canvasHeight = this.board.canvasHeight;
583     copy.board.canvasWidth = this.board.canvasWidth;
584     copy.board.dimension = this.board.dimension;
585     //copy.board.algebra = this.board.algebra;
586 
587     copy.visProp = this.visProp;
588     JXG.clearVisPropOld(copy);
589 
590     s = this.getSlope();
591     r = this.getRise();
592     copy.getSlope = function() { return s; };
593     copy.getRise = function() { return r; };
594 
595     er = this.board.renderer.enhancedRendering;
596     this.board.renderer.enhancedRendering = true;
597     this.board.renderer.drawLine(copy);
598     this.board.renderer.enhancedRendering = er;
599     this.traces[copy.id] = copy.rendNode; //this.board.renderer.getElementById(copy.id);
600 
601     delete copy;
602 
603 /*
604     var id = this.id + 'T' + this.numTraces;
605     this.traces[id] = this.board.renderer.cloneSubTree(this,id,'lines');
606     this.numTraces++;
607 */
608 };
609 
610 /**
611  * DESCRIPTION
612  * @param transform A {@link #JXG.Transformation} object or an array of it.
613  */
614 JXG.Line.prototype.addTransform = function (/** JXG.Transformation,array */ transform) {
615     var list, i;
616     if (JXG.isArray(transform)) {
617         list = transform;
618     } else {
619         list = [transform];
620     }
621     for (i=0;i<list.length;i++) {
622         this.point1.transformations.push(list[i]);
623         this.point2.transformations.push(list[i]);
624     }
625 };
626 
627 /**
628  * TODO DESCRIPTION. What is this method for? -- michael
629  * @param method TYPE & DESCRIPTION. UNUSED.
630  * @param x TYPE & DESCRIPTION
631  * @param y TYPE & DESCRIPTION
632  */
633 JXG.Line.prototype.setPosition = function (method, x, y) {
634     //var oldCoords = this.coords;
635     //if(this.group.length != 0) {
636     // AW: Do we need this for lines?
637         // this.coords = new JXG.Coords(method, [x,y], this.board);
638         // this.group[this.group.length-1].dX = this.coords.scrCoords[1] - oldCoords.scrCoords[1];
639         // this.group[this.group.length-1].dY = this.coords.scrCoords[2] - oldCoords.scrCoords[2];
640         // this.group[this.group.length-1].update(this);
641     //} else {
642         var t = this.board.create('transform',[x,y],{type:'translate'});
643         if (this.point1.transformations.length>0 && this.point1.transformations[this.point1.transformations.length-1].isNumericMatrix) {
644             this.point1.transformations[this.point1.transformations.length-1].melt(t);
645         } else {
646             this.point1.addTransform(this.point1,t);
647         }
648         if (this.point2.transformations.length>0 && this.point2.transformations[this.point2.transformations.length-1].isNumericMatrix) {
649             this.point2.transformations[this.point2.transformations.length-1].melt(t);
650         } else {
651             this.point2.addTransform(this.point2,t);
652         }
653         //this.addTransform(t);
654         //this.update();
655     //}
656 };
657 
658 /**
659  * Treat the line as parametric curve in homogeneuous coordinates.
660  * <pre>x = 1 * sin(theta)*cos(phi)
661  * y = 1 * sin(theta)*sin(phi)
662  * z = 1 * sin(theta)</pre>
663  * and the line is the set of solutions of <tt>a*x+b*y+c*z = 0</tt>.
664  * It follows:
665  * <pre>sin(theta)*(a*cos(phi)+b*sin(phi))+c*cos(theta) = 0</pre>
666  * Define:
667  * <pre>  A = (a*cos(phi)+b*sin(phi))
668  *   B = c</pre>
669  * Then
670  * <pre>cos(theta) = A/sqrt(A*A+B*B)
671  * sin(theta) = -B/sqrt(A*A+B*B)</pre>
672  * and <tt>X(phi) = x</tt> from above.
673  * phi runs from 0 to 1
674  * @type float
675  * @return X(phi) TODO description
676  */
677 JXG.Line.prototype.X = function (phi) {
678     var a = this.stdform[1],
679         b = this.stdform[2],
680         c = this.stdform[0],
681         A, B, sq, sinTheta, cosTheta;
682     phi *= Math.PI;
683     A = a*Math.cos(phi)+b*Math.sin(phi);
684     B = c;
685     sq = Math.sqrt(A*A+B*B);
686     sinTheta = -B/sq;
687     cosTheta = A/sq;
688     if (Math.abs(cosTheta)<JXG.Math.eps) { cosTheta = 1.0; }
689     return sinTheta*Math.cos(phi)/cosTheta;
690 };
691 
692 /**
693  * Treat the line as parametric curve in homogeneous coordinates. See {@link #X} for a detailed description.
694  * @type float
695  * @return Y(phi) TODO description
696  */
697 JXG.Line.prototype.Y = function (phi) {
698     var a = this.stdform[1],
699         b = this.stdform[2],
700         c = this.stdform[0],
701         A, B, sq, sinTheta, cosTheta;
702     phi *= Math.PI;
703     A = a*Math.cos(phi)+b*Math.sin(phi);
704     B = c;
705     sq = Math.sqrt(A*A+B*B);
706     sinTheta = -B/sq;
707     cosTheta = A/sq;
708     if (Math.abs(cosTheta)<JXG.Math.eps) { cosTheta = 1.0; }
709     return sinTheta*Math.sin(phi)/cosTheta;
710 };
711 
712 /**
713  * Treat the line as parametric curve in homogeneous coordinates. See {@link #X} for a detailed description.
714  * @type float
715  * @return Z(phi) TODO description
716  */
717 JXG.Line.prototype.Z = function (phi) {
718     var a = this.stdform[1],
719         b = this.stdform[2],
720         c = this.stdform[0],
721         A, B, sq, cosTheta;
722     phi *= Math.PI;
723     A = a*Math.cos(phi)+b*Math.sin(phi);
724     B = c;
725     sq = Math.sqrt(A*A+B*B);
726     cosTheta = A/sq;
727     if (Math.abs(cosTheta)>=JXG.Math.eps) {
728         return 1.0;
729     } else {
730         return 0.0;
731     }
732 };
733 
734 /**
735  * TODO circle?!? --michael
736  * private or public? --michael
737  * Treat the circle as parametric curve:
738  * t runs from 0 to 1
739  * @private
740  */
741 JXG.Line.prototype.minX = function () {
742     return 0.0;
743 };
744 
745 /**
746  * TODO circle?!? --michael
747  * private or public? --michael
748  * Treat the circle as parametric curve:
749  * t runs from 0 to 1
750  * @private
751  */
752 JXG.Line.prototype.maxX = function () {
753     return 1.0;
754 };
755 
756 /**
757  * Adds ticks to this line. Ticks can be added to any kind of line: line, arrow, and axis.
758  * @param {JXG.Ticks} ticks Reference to a ticks object which is describing the ticks (color, distance, how many, etc.).
759  * @type String
760  * @return Id of the ticks object.
761  */
762 JXG.Line.prototype.addTicks = function(ticks) {
763     if(ticks.id == '' || typeof ticks.id == 'undefined')
764         ticks.id = this.id + '_ticks_' + (this.ticks.length+1);
765 
766     this.board.renderer.drawTicks(ticks);
767     this.ticks.push(ticks);
768 
769     this.ticks[this.ticks.length-1].updateRenderer();
770 
771     return ticks.id;
772 };
773 
774 /**
775  * Removes all ticks from a line.
776  */
777 JXG.Line.prototype.removeAllTicks = function() {
778     var t;
779     for(t=this.ticks.length; t>0; t--) {
780         this.board.renderer.remove(this.ticks[t-1].rendNode);
781     }
782     this.ticks = new Array();
783 };
784 
785 /**
786  * Removes ticks identified by parameter named tick from this line.
787  * @param {JXG.Ticks} tick Reference to tick object to remove.
788  */
789 JXG.Line.prototype.removeTicks = function(tick) {
790     var t, j;
791     if(this.defaultTicks != null && this.defaultTicks == tick) {
792         this.defaultTicks = null;
793     }
794 
795     for(t=this.ticks.length; t>0; t--) {
796         if(this.ticks[t-1] == tick) {
797             this.board.renderer.remove(this.ticks[t-1].rendNode);
798 
799             for(j=0; j<this.ticks[t-1].ticks.length; j++) {
800                 if(this.ticks[t-1].labels[j] != null)
801                     if (this.ticks[t-1].labels[j].show) this.board.renderer.remove(this.ticks[t-1].labels[j].rendNode);
802             }
803             delete(this.ticks[t-1]);
804         }
805     }
806 };
807 
808 /**
809  * @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
810  * a line can be used as an arrow and/or axis.
811  * @pseudo
812  * @description
813  * @name Line
814  * @augments JXG.Line
815  * @constructor
816  * @type JXG.Line
817  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
818  * @param {JXG.Point,array_JXG.Point,array} point1,point2 Parent elements can be two elements either of type {@link JXG.Point} or array of
819  * numbers describing the coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
820  * @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
821  * of the equation <tt>a*x+b*y+c*z = 0</tt>.
822  * @example
823  * // Create a line using point and coordinates/
824  * // The second point will be fixed and invisible.
825  * var p1 = board.create('point', [4.5, 2.0]);
826  * var l1 = board.create('line', [p1, [1.0, 1.0]]);
827  * </pre><div id="c0ae3461-10c4-4d39-b9be-81d74759d122" style="width: 300px; height: 300px;"></div>
828  * <script type="text/javascript">
829  *   var glex1_board = JXG.JSXGraph.initBoard('c0ae3461-10c4-4d39-b9be-81d74759d122', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
830  *   var glex1_p1 = glex1_board.create('point', [4.5, 2.0]);
831  *   var glex1_l1 = glex1_board.create('line', [glex1_p1, [1.0, 1.0]]);
832  * </script><pre>
833  * @example
834  * // Create a point using three coordinates
835  * var l1 = board.create('line', [1.0, -2.0, 3.0]);
836  * </pre><div id="cf45e462-f964-4ba4-be3a-c9db94e2593f" style="width: 300px; height: 300px;"></div>
837  * <script type="text/javascript">
838  *   var glex2_board = JXG.JSXGraph.initBoard('cf45e462-f964-4ba4-be3a-c9db94e2593f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
839  *   var glex2_l1 = glex2_board.create('line', [1.0, -2.0, 3.0]);
840  * </script><pre>
841  */
842 JXG.createLine = function(board, parents, atts) {
843     var el, p1, p2, i,
844         c = [];
845         
846     atts = JXG.checkAttributes(atts,{
847         withLabel:JXG.readOption(board.options,'line','withLabel'),
848         layer:null,
849         labelOffsets:JXG.readOption(board.options,'line','labelOffsets')
850     });
851 
852     var constrained = false;
853     if (parents.length == 2) { // The line is defined by two points (or coordinates of two points)
854         if (parents[0].length>1) { // point 1 given by coordinates
855             p1 = board.create('point', parents[0], {visible:false,fixed:true});
856         } else if (parents[0].elementClass == JXG.OBJECT_CLASS_POINT) {
857             p1 =  JXG.getReference(board,parents[0]);
858         } else if ((typeof parents[0] == 'function') && (parents[0]().elementClass == JXG.OBJECT_CLASS_POINT)) {
859             p1 = parents[0]();
860             constrained = true;
861         } else
862             throw new Error("JSXGraph: Can't create line with parent types '" + 
863                             (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
864                             "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]");
865 
866         if (parents[1].length>1) { // point 2 given by coordinates
867             p2 = board.create('point', parents[1], {visible:false,fixed:true});
868         } else if (parents[1].elementClass == JXG.OBJECT_CLASS_POINT) {
869             p2 =  JXG.getReference(board,parents[1]);
870         } else if ((typeof parents[1] == 'function') && (parents[1]().elementClass == JXG.OBJECT_CLASS_POINT)) {
871             p2 = parents[1]();
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         el = new JXG.Line(board, p1.id, p2.id, atts['id'], atts['name'],atts['withLabel'],atts['layer']);
878         if(constrained) {
879         	el.constrained = true;
880         	el.funp1 = parents[0];
881         	el.funp2 = parents[1];
882         }
883     }
884     else if (parents.length==3) {  // Line is defined by three coordinates
885         // free line
886         for (i=0;i<3;i++) {
887             if (typeof parents[i]=='number') {
888                 c[i] = function(z){ return function() { return z; }; }(parents[i]);
889             } else if (typeof parents[i]=='function') {
890                 c[i] = parents[i];
891             } else {
892                 throw new Error("JSXGraph: Can't create line with parent types '" + 
893                                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2])+ "'." +
894                                 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]");
895             }
896         }
897         // point 1: (0,c,-b)
898         /*
899         p1 = board.create('point',[
900                 function() { return 0.0;},
901                 function() { return c[2]();},
902                 function() { return -c[1]();}],{visible:false,name:' '});
903         */
904         // New version: point1 is the midpoint between (0,c,-b) and point 2. => point1 is finite.
905         p1 = board.create('point',[
906                 function() { return (0.0 + c[2]()*c[2]()+c[1]()*c[1]())*0.5;},
907                 function() { return (c[2]() - c[1]()*c[0]()+c[2]())*0.5;},
908                 function() { return (-c[1]() - c[2]()*c[0]()-c[1]())*0.5;}],{visible:false,name:' '});
909         // point 2: (b^2+c^2,-ba+c,-ca-b)
910         p2 = board.create('point',[
911                 function() { return c[2]()*c[2]()+c[1]()*c[1]();},
912                 function() { return -c[1]()*c[0]()+c[2]();},
913                 function() { return -c[2]()*c[0]()-c[1]();}],{visible:false,name:' '});
914         el = new JXG.Line(board, p1.id, p2.id, atts['id'], atts['name'],atts['withLabel']);
915     }
916     else if ((parents.length==1) && (typeof parents[0] == 'function') && (parents[0]().length == 2) &&
917     		 (parents[0]()[0].elementClass == JXG.OBJECT_CLASS_POINT) && (parents[0]()[1].elementClass == JXG.OBJECT_CLASS_POINT)) {
918     	var ps = parents[0]();
919         el = new JXG.Line(board, ps[0].id, ps[1].id, atts['id'], atts['name'],atts['withLabel'],atts['layer']);
920         el.constrained = true;
921         el.funps = parents[0];
922     } else
923         throw new Error("JSXGraph: Can't create line with parent types '" + 
924                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
925                         "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]");
926 
927     el.labelOffsets = atts['labelOffsets'];
928     return el;
929 };
930 
931 JXG.JSXGraph.registerElement('line', JXG.createLine);
932 
933 /**
934  * @class This element is used to provide a constructor for a segment. It's strictly spoken just a wrapper for element {@link Line} with {@link JXG.Line#straightFirst}
935  * and {@link JXG.Line#straightLast} properties set to false.
936  * @pseudo
937  * @description
938  * @name Segment
939  * @augments JXG.Line
940  * @constructor
941  * @type JXG.Line
942  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
943  * @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
944  * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
945  * @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
946  * of the equation <tt>a*x+b*y+c*z = 0</tt>.
947  * @see Line
948  * @example
949  * // Create a segment providing two points.
950  *   var p1 = board.create('point', [4.5, 2.0]);
951  *   var p2 = board.create('point', [1.0, 1.0]);
952  *   var l1 = board.create('segment', [p1, p2]);
953  * </pre><div id="d70e6aac-7c93-4525-a94c-a1820fa38e2f" style="width: 300px; height: 300px;"></div>
954  * <script type="text/javascript">
955  *   var slex1_board = JXG.JSXGraph.initBoard('d70e6aac-7c93-4525-a94c-a1820fa38e2f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
956  *   var slex1_p1 = slex1_board.create('point', [4.5, 2.0]);
957  *   var slex1_p2 = slex1_board.create('point', [1.0, 1.0]);
958  *   var slex1_l1 = slex1_board.create('segment', [slex1_p1, slex1_p2]);
959  * </script><pre>
960  */
961  JXG.createSegment = function(board, parents, atts) {
962     var el;
963 
964     atts = JXG.checkAttributes(atts,{withLabel:JXG.readOption(board.options,'line','withLabel'), layer:null});
965     atts['straightFirst'] = false;
966     atts['straightLast'] = false;
967     el = board.create('line', parents, atts);
968 
969     return el;
970 };
971 
972 JXG.JSXGraph.registerElement('segment', JXG.createSegment);
973 
974 /**
975  * @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}
976  * and {@link JXG.Line#straightLast} properties set to false and {@link JXG.Line#lastArrow} set to true.
977  * @pseudo
978  * @description
979  * @name Arrow
980  * @augments JXG.Line
981  * @constructor
982  * @type JXG.Line
983  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
984  * @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
985  * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
986  * @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
987  * of the equation <tt>a*x+b*y+c*z = 0</tt>.
988  * @see Line
989  * @example
990  * // Create an arrow providing two points.
991  *   var p1 = board.create('point', [4.5, 2.0]);
992  *   var p2 = board.create('point', [1.0, 1.0]);
993  *   var l1 = board.create('arrow', [p1, p2]);
994  * </pre><div id="1d26bd22-7d6d-4018-b164-4c8bc8d22ccf" style="width: 300px; height: 300px;"></div>
995  * <script type="text/javascript">
996  *   var alex1_board = JXG.JSXGraph.initBoard('1d26bd22-7d6d-4018-b164-4c8bc8d22ccf', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
997  *   var alex1_p1 = alex1_board.create('point', [4.5, 2.0]);
998  *   var alex1_p2 = alex1_board.create('point', [1.0, 1.0]);
999  *   var alex1_l1 = alex1_board.create('arrow', [alex1_p1, alex1_p2]);
1000  * </script><pre>
1001  */
1002 JXG.createArrow = function(board, parents, attributes) {
1003     var el;
1004 
1005     attributes = JXG.checkAttributes(attributes,{withLabel:JXG.readOption(board.options,'line','withLabel'), layer:null});
1006     //if ( (JXG.isPoint(parents[0])) && (JXG.isPoint(parents[1])) ) { // The constructability decision is delkegated to the line object
1007         el = board.create('line',parents,attributes);
1008         //el = new JXG.Line(board, parents[0], parents[1], attributes['id'], attributes['name'],attributes['withLabel']);
1009         el.setStraight(false,false);
1010         el.setArrow(false,true);
1011         el.type = JXG.OBJECT_TYPE_VECTOR;
1012     //} // Ansonsten eine fette Exception um die Ohren hauen
1013     //else
1014     //    throw new Error("JSXGraph: Can't create arrow with parent types '" + (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'.");
1015 
1016     return el;
1017 };
1018 
1019 JXG.JSXGraph.registerElement('arrow', JXG.createArrow);
1020 
1021 /**
1022  * @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}
1023  * 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.
1024  * @pseudo
1025  * @description
1026  * @name Axis
1027  * @augments JXG.Line
1028  * @constructor
1029  * @type JXG.Line
1030  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
1031  * @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
1032  * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
1033  * @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
1034  * of the equation <tt>a*x+b*y+c*z = 0</tt>.
1035  * @example
1036  * // Create an axis providing two coord pairs.
1037  *   var l1 = board.create('axis', [[0.0, 1.0], [1.0, 1.3]]);
1038  * </pre><div id="4f414733-624c-42e4-855c-11f5530383ae" style="width: 300px; height: 300px;"></div>
1039  * <script type="text/javascript">
1040  *   var axex1_board = JXG.JSXGraph.initBoard('4f414733-624c-42e4-855c-11f5530383ae', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
1041  *   var axex1_l1 = axex1_board.create('axis', [[0.0, 1.0], [1.0, 1.3]]);
1042  * </script><pre>
1043  */
1044 JXG.createAxis = function(board, parents, attributes) {
1045     var point1,
1046         point2,
1047         line, dist, c1, c2, len, defTicks;
1048 
1049     // Arrays oder Punkte, mehr brauchen wir nicht.
1050     if ( (JXG.isArray(parents[0]) || JXG.isPoint(parents[0]) ) && (JXG.isArray(parents[1]) || JXG.isPoint(parents[1])) ) {
1051         if( JXG.isPoint(parents[0]) )
1052             point1 = parents[0];
1053         else
1054             point1 = new JXG.Point(board, parents[0],'','',false);
1055 
1056         if( JXG.isPoint(parents[1]) )
1057             point2 = parents[1];
1058         else
1059             point2 = new JXG.Point(board,parents[1],'','',false);
1060 
1061         /* Make the points fixed */
1062         point1.fixed = true;
1063         point2.fixed = true;
1064 
1065         attributes = JXG.checkAttributes(attributes,
1066             {'lastArrow':true, 'straightFirst':true, 'straightLast':true,
1067              'strokeWidth':1, 'withLabel':false,
1068              'strokeColor':board.options.axis.strokeColor});
1069 
1070         attributes.highlightStrokeColor = attributes.highlightStrokeColor || attributes.strokeColor || board.options.axis.highlightStrokeColor;
1071 
1072         line = board.create('line', [point1, point2], attributes);
1073         line.setProperty({needsRegularUpdate : false});  // Axes only updated after zooming and moving of  the origin.
1074 
1075         attributes = JXG.checkAttributes(attributes,{'minorTicks':4, 'insertTicks': true});
1076 
1077         if(attributes.ticksDistance != 'undefined' && attributes.ticksDistance != null) {
1078             dist = attributes.ticksDistance;
1079         } else if(JXG.isArray(attributes.ticks)) {
1080             dist = attributes.ticks;
1081         } else {
1082             c1 = new JXG.Coords(JXG.COORDS_BY_USER, [line.point1.coords.usrCoords.slice(1)],board);
1083             c2 = new JXG.Coords(JXG.COORDS_BY_USER, [line.point2.coords.usrCoords.slice(1)],board);
1084             board.renderer.calcStraight(line, c1, c2);
1085             len = c1.distance(JXG.COORDS_BY_USER,c2);
1086             //len *= 0.33;
1087             dist = 1.0; //len;
1088         }
1089         
1090         line.defaultTicks = board.create('ticks', [line, dist], attributes);
1091         line.defaultTicks.setProperty({needsRegularUpdate : false});
1092     }
1093     else
1094         throw new Error("JSXGraph: Can't create point with parent types '" + 
1095                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
1096                         "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]");
1097 
1098     return line;
1099 };
1100 
1101 JXG.JSXGraph.registerElement('axis', JXG.createAxis);
1102 
1103 /**
1104  * @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
1105  * by a glider on a line, circle, or curve and describes the tangent in the glider point on that line, circle, or curve.
1106  * @pseudo
1107  * @description
1108  * @name Tangent
1109  * @augments JXG.Line
1110  * @constructor
1111  * @type JXG.Line
1112  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
1113  * @param {Glider} g A glider on a line, circle, or curve.
1114  * @example
1115  * // Create a tangent providing a glider on a function graph
1116  *   var c1 = board.create('curve', [function(t){return t},function(t){return t*t*t;}]);
1117  *   var g1 = board.create('glider', [0.6, 1.2, c1]);
1118  *   var t1 = board.create('tangent', [g1]);
1119  * </pre><div id="7b7233a0-f363-47dd-9df5-4018d0d17a98" style="width: 400px; height: 400px;"></div>
1120  * <script type="text/javascript">
1121  *   var tlex1_board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-4018d0d17a98', {boundingbox: [-6, 6, 6, -6], axis: true, showcopyright: false, shownavigation: false});
1122  *   var tlex1_c1 = tlex1_board.create('curve', [function(t){return t},function(t){return t*t*t;}]);
1123  *   var tlex1_g1 = tlex1_board.create('glider', [0.6, 1.2, tlex1_c1]);
1124  *   var tlex1_t1 = tlex1_board.create('tangent', [tlex1_g1]);
1125  * </script><pre>
1126  */
1127 JXG.createTangent = function(board, parents, attributes) {
1128     var p,
1129         c,
1130         g, f, i, j, el, Dg, Df, tangent;
1131 
1132     if (parents.length==1) { // One arguments: glider on line, circle or curve
1133         p = parents[0];
1134         c = p.slideObject;
1135     } else if (parents.length==2) {     // Two arguments: (point,line|curve|circle|conic) or (line|curve|circle|conic,point). // Not yet: curve!
1136         if (JXG.isPoint(parents[0])) {  // In fact, for circles and conics it is the polar.
1137             p = parents[0];
1138             c = parents[1];
1139         } else if (JXG.isPoint(parents[1])) {
1140             c = parents[0];
1141             p = parents[1];
1142         } else {
1143             throw new Error("JSXGraph: Can't create tangent with parent types '" + 
1144                             (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
1145                             "\nPossible parent types: [glider], [point,line|curve|circle|conic]");
1146         }
1147     } else {
1148         throw new Error("JSXGraph: Can't create tangent with parent types '" + 
1149                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
1150                         "\nPossible parent types: [glider], [point,line|curve|circle|conic]");
1151     }
1152 
1153     attributes = JXG.checkAttributes(attributes,{withLabel:JXG.readOption(board.options,'line','withLabel'), layer:null});
1154     
1155     if (c.elementClass == JXG.OBJECT_CLASS_LINE) {
1156         tangent = board.create('line', [c.point1,c.point2], attributes);
1157     } else if (c.elementClass == JXG.OBJECT_CLASS_CURVE && !(c.type == JXG.OBJECT_TYPE_CONIC)) {
1158         if (c.curveType!='plot') {
1159             g = c.X;
1160             f = c.Y;
1161             tangent = board.create('line', [
1162                     function(){ return -p.X()*board.D(f)(p.position)+p.Y()*board.D(g)(p.position);},
1163                     function(){ return board.D(f)(p.position);},
1164                     function(){ return -board.D(g)(p.position);}
1165                     ], attributes );
1166             p.addChild(tangent);
1167             // this is required for the geogebra reader to display a slope
1168             tangent.glider = p;
1169         } else {  // curveType 'plot'
1170             // equation of the line segment: 0 = y*(x1-x2) + x*(y2-y1) + y1*x2-x1*y2
1171             tangent = board.create('line', [
1172                     function(){ i=Math.floor(p.position);
1173                                 if (i==c.numberPoints-1) i--;
1174                                 if (i<0) return 1.0;
1175                                 return c.Y(i)*c.X(i+1)-c.X(i)*c.Y(i+1);},
1176                     function(){ i=Math.floor(p.position);
1177                                 if (i==c.numberPoints-1) i--;
1178                                 if (i<0) return 0.0;
1179                                 return c.Y(i+1)-c.Y(i);},
1180                     function(){ i=Math.floor(p.position);
1181                                 if (i==c.numberPoints-1) i--;
1182                                 if (i<0) return 0.0;
1183                                 return c.X(i)-c.X(i+1);}
1184                     ], attributes );
1185             p.addChild(tangent);
1186             // this is required for the geogebra reader to display a slope
1187             tangent.glider = p;
1188         }
1189     } else if (c.type == JXG.OBJECT_TYPE_TURTLE) {
1190             tangent = board.create('line', [
1191                     function(){ i=Math.floor(p.position);
1192                                 for(j=0;j<c.objects.length;j++) {  // run through all curves of this turtle
1193                                     el = c.objects[j];
1194                                     if (el.type==JXG.OBJECT_TYPE_CURVE) {
1195                                         if (i<el.numberPoints) break;
1196                                         i-=el.numberPoints;
1197                                     }
1198                                 }
1199                                 if (i==el.numberPoints-1) i--;
1200                                 if (i<0) return 1.0;
1201                                 return el.Y(i)*el.X(i+1)-el.X(i)*el.Y(i+1);},
1202                     function(){ i=Math.floor(p.position);
1203                                 for(j=0;j<c.objects.length;j++) {  // run through all curves of this turtle
1204                                     el = c.objects[j];
1205                                     if (el.type==JXG.OBJECT_TYPE_CURVE) {
1206                                         if (i<el.numberPoints) break;
1207                                         i-=el.numberPoints;
1208                                     }
1209                                 }
1210                                 if (i==el.numberPoints-1) i--;
1211                                 if (i<0) return 0.0;
1212                                 return el.Y(i+1)-el.Y(i);},
1213                     function(){ i=Math.floor(p.position);
1214                                 for(j=0;j<c.objects.length;j++) {  // run through all curves of this turtle
1215                                     el = c.objects[j];
1216                                     if (el.type==JXG.OBJECT_TYPE_CURVE) {
1217                                         if (i<el.numberPoints) break;
1218                                         i-=el.numberPoints;
1219                                     }
1220                                 }
1221                                 if (i==el.numberPoints-1) i--;
1222                                 if (i<0) return 0.0;
1223                                 return el.X(i)-el.X(i+1);}
1224                     ], attributes );
1225             p.addChild(tangent);
1226             // this is required for the geogebra reader to display a slope
1227             tangent.glider = p;
1228     } else if (c.elementClass == JXG.OBJECT_CLASS_CIRCLE || c.type == JXG.OBJECT_TYPE_CONIC) {
1229         /*
1230         Dg = function(t){ return -c.Radius()*Math.sin(t); };
1231         Df = function(t){ return c.Radius()*Math.cos(t); };
1232         return board.create('line', [
1233                     function(){ return -p.X()*Df(p.position)+p.Y()*Dg(p.position);},
1234                     function(){ return Df(p.position);},
1235                     function(){ return -Dg(p.position);}
1236                     ], attributes );
1237         */
1238 
1239         // If p is not on c, the tangent is the polar.
1240         // This construction should work on conics, too. p has to lie on c.
1241         tangent = board.create('line', [
1242                     function(){ return JXG.Math.matVecMult(c.quadraticform,p.coords.usrCoords)[0]; },
1243                     function(){ return JXG.Math.matVecMult(c.quadraticform,p.coords.usrCoords)[1]; },
1244                     function(){ return JXG.Math.matVecMult(c.quadraticform,p.coords.usrCoords)[2]; }
1245                 ] , attributes);
1246 
1247         p.addChild(tangent);
1248         // this is required for the geogebra reader to display a slope
1249         tangent.glider = p;
1250     }
1251     
1252     return tangent;
1253 };
1254 
1255 /**
1256  * Register the element type tangent at JSXGraph
1257  * @private
1258  */
1259 JXG.JSXGraph.registerElement('tangent', JXG.createTangent);
1260 JXG.JSXGraph.registerElement('polar', JXG.createTangent);
1261 // vim: et ts=4
1262