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 In this file the geometry element Curve is defined.
 28  */
 29 
 30 /**
 31  * Curves are the common object for function graphs, parametric curves, polar curves, and data plots.
 32  * @class Creates a new curve object. Do not use this constructor to create a curve. Use {@link JXG.Board#create} with
 33  * type {@link Curve}, or {@link Functiongraph} instead.  
 34  * @augments JXG.GeometryElement
 35  * @param {string|JXG.Board} board The board the new curve is drawn on.
 36  * @param {Array} parents defining terms An array with the functon terms or the data points of the curve.
 37  * @param {Object} attributes Defines the visual appearance of the curve.
 38  * @see JXG.Board#generateName
 39  * @see JXG.Board#addCurve
 40   */
 41 JXG.Curve = function (board, parents, attributes) {
 42     this.constructor(board, attributes, JXG.OBJECT_TYPE_CURVE, JXG.OBJECT_CLASS_CURVE);
 43  
 44     this.points = []; 
 45     /** 
 46      * Number of points on curves. This value changes
 47      * between numberPointsLow and numberPointsHigh.
 48      * It is set in {@link JXG.Curve#updateCurve}.
 49      */
 50     this.numberPoints = this.visProp.numberpointshigh; 
 51     
 52     this.bezierDegree = 1;
 53 
 54     this.dataX = null;
 55     this.dataY = null;
 56 
 57     if (parents[0]!=null) {
 58         this.varname = parents[0];
 59     } else {
 60         this.varname = 'x';
 61     }
 62     this.xterm = parents[1];  // function graphs: "x"
 63     this.yterm = parents[2];  // function graphs: e.g. "x^2"
 64     this.generateTerm(this.varname,this.xterm,this.yterm,parents[3],parents[4]);  // Converts GEONExT syntax into JavaScript syntax
 65     this.updateCurve();                        // First evaluation of the curve
 66     
 67     this.id = this.board.setId(this,'G');
 68     this.board.renderer.drawCurve(this);
 69     
 70     this.board.finalizeAdding(this);
 71 
 72     this.createGradient();
 73     this.elType = 'curve';
 74     this.createLabel();
 75 
 76     if (typeof this.xterm=='string') {
 77         this.notifyParents(this.xterm);
 78     }
 79     if (typeof this.yterm=='string') {
 80         this.notifyParents(this.yterm);
 81     }
 82 };
 83 JXG.Curve.prototype = new JXG.GeometryElement;
 84 
 85 
 86 JXG.extend(JXG.Curve.prototype, /** @lends JXG.Curve.prototype */ {
 87 
 88     /**
 89      * Gives the default value of the left bound for the curve.
 90      * May be overwritten in {@link JXG.Curve#generateTerm}.
 91      * @returns {Number} Left bound for the curve.
 92      */
 93     minX: function () {
 94         if (this.visProp.curvetype=='polar') {
 95             return 0.0;
 96         } else {
 97             var leftCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0, 0], this.board);
 98             return leftCoords.usrCoords[1];
 99         }
100     },
101 
102     /**
103      * Gives the default value of the right bound for the curve.
104      * May be overwritten in {@link JXG.Curve#generateTerm}.
105      * @returns {Number} Right bound for the curve.
106      */
107     maxX: function () {
108         var rightCoords;
109         if (this.visProp.curvetype=='polar') {
110             return 2.0*Math.PI;
111         } else {
112             rightCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [this.board.canvasWidth, 0], this.board);
113             return rightCoords.usrCoords[1];
114         }
115     },
116 
117     /**
118      * Treat the curve as curve with homogeneous coordinates
119      * @param {Number} t A number between 0.0 and 1.0.
120      * @return {Number} Always 1.0
121      */
122     Z: function (t) {
123         return 1.0;
124     },
125 
126     /**
127      * Checks whether (x,y) is near the curve.
128      * @param {Number} x Coordinate in x direction, screen coordinates.
129      * @param {Number} y Coordinate in y direction, screen coordinates.
130      * @param {Number} start Optional start index for search on data plots. 
131      * @return {Boolean} True if (x,y) is near the curve, False otherwise.
132      */
133     hasPoint: function (x, y, start) {
134         var t, dist = Infinity,
135             i, tX, tY,
136             xi, yi, x0, y0, x1, y1, xy, den, lbda, 
137             steps = this.visProp.numberpointslow,
138             d = (this.maxX()-this.minX())/steps,
139             prec = this.board.options.precision.hasPoint/this.board.unitX,
140             checkPoint, len,
141             suspendUpdate = true,
142             invMat, c;
143 
144         prec = prec*prec;
145         checkPoint = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this.board);
146         x = checkPoint.usrCoords[1];
147         y = checkPoint.usrCoords[2];
148         
149         if (this.transformations.length>0) {
150             /** 
151              * Transform the mouse/touch coordinates 
152              * back to the original position of the curve.
153              */
154             this.updateTransformMatrix();
155             invMat = JXG.Math.inverse(this.transformMat);
156             c = JXG.Math.matVecMult(invMat, [1, x, y]);
157             x = c[1];
158             y = c[2];
159         }
160         
161         if (this.visProp.curvetype=='parameter' 
162             || this.visProp.curvetype=='polar' 
163             || this.visProp.curvetype=='functiongraph') {
164             
165             // Brute fore search for a point on the curve close to the mouse pointer
166             
167             for (i=0,t=this.minX(); i<steps; i++) {
168                 tX = this.X(t,suspendUpdate);
169                 tY = this.Y(t,suspendUpdate);
170                 
171                 dist = (x-tX)*(x-tX)+(y-tY)*(y-tY);
172                 if (dist<prec) { return true; }
173                 t+=d;
174             }
175         } else if (this.visProp.curvetype == 'plot') {
176             if (!JXG.exists(start) || start<0) {
177                 start = 0;
178             }
179             len = this.numberPoints; // Rough search quality
180             for (i=start;i<len-1;i++) {
181                 xi  = this.X(i);
182                 yi  = this.Y(i);
183 
184                 x0  = x - xi;
185                 y0  = y - yi;
186 
187                 x1  = this.X(i+1) - xi;
188                 y1  = this.Y(i+1) - yi;
189                 
190                 den = x1*x1+y1*y1;
191                 dist = x0*x0+y0*y0;
192                 
193                 if (den>=JXG.Math.eps) {
194                     xy = x0*x1+y0*y1;
195                     lbda = xy/den;
196                     dist -= lbda*xy;
197                 } else {
198                     lbda = 0.0;
199                 }
200                 if (lbda>=0.0 && lbda<=1.0 && dist<prec) {
201                     return true;
202                 }
203             }
204             return false;
205         }
206         return (dist<prec);
207     },
208 
209     /**
210      * Allocate points in the Coords array this.points
211      */
212     allocatePoints: function () {
213         var i, len;
214         
215         len = this.numberPoints;
216         
217         if (this.points.length < this.numberPoints) {
218             for (i = this.points.length; i < len; i++) {
219                 this.points[i] = new JXG.Coords(JXG.COORDS_BY_USER, [0,0], this.board);
220             }
221         }
222     },
223 
224     /**
225      * Computes for equidistant points on the x-axis the values of the function
226      * @returns {JXG.Curve} Reference to the curve object.
227      * @see JXG.Curve#updateCurve
228      */
229     update: function () {
230         if (this.needsUpdate) {
231             if (this.visProp.trace) {
232                 this.cloneToBackground(true);
233             }
234             this.updateCurve();
235         }
236 
237         return this;
238     },
239 
240     /**
241      * Updates the visual contents of the curve.
242      * @returns {JXG.Curve} Reference to the curve object.
243      */
244     updateRenderer: function () {
245         if (this.needsUpdate && this.visProp.visible) {
246             this.board.renderer.updateCurve(this);
247             this.needsUpdate = false;
248 
249             // Update the label if visible.
250             if(this.hasLabel && this.label.content.visProp.visible) {
251                 this.label.content.update();
252                 this.board.renderer.updateText(this.label.content);
253             }
254         }
255         return this;
256     },
257 
258     /**
259      * For dynamic dataplots updateCurve can be used to compute new entries
260      * for the arrays {@link JXG.Curve#dataX} and {@link JXG.Curve#dataY}. It
261      * is used in {@link JXG.Curve#updateCurve}. Default is an empty method, can
262      * be overwritten by the user.
263      */
264     updateDataArray: function () {
265         // this used to return this, but we shouldn't rely on the user to implement it.
266     },
267 
268     /**
269      * Computes for equidistant points on the x-axis the values
270      * of the function.
271      * If the mousemove event triggers this update, we use only few
272      * points. Otherwise, e.g. on mouseup, many points are used.
273      * @see JXG.Curve#update
274      * @returns {JXG.Curve} Reference to the curve object.
275      */
276     updateCurve: function () {
277         var len, mi, ma, x, y, i,
278             suspendUpdate = false;
279 
280         this.updateTransformMatrix();
281         this.updateDataArray();
282         mi = this.minX();
283         ma = this.maxX();
284 
285         // Discrete data points
286         if (this.dataX != null) { // x-coordinates are in an array
287             this.numberPoints = this.dataX.length;
288             len = this.numberPoints;
289             this.allocatePoints();  // It is possible, that the array length has increased.
290             for (i=0; i<len; i++) {
291                 x = i;
292                 if (this.dataY!=null) { // y-coordinates are in an array
293                     y = i;
294                     this.points[i].setCoordinates(JXG.COORDS_BY_USER, [this.dataX[i],this.dataY[i]], false); // The last parameter prevents rounding in usr2screen().
295                 } else {
296                     y = this.X(x); // discrete x data, continuous y data
297                     this.points[i].setCoordinates(JXG.COORDS_BY_USER, [this.dataX[i],this.Y(y,suspendUpdate)], false); // The last parameter prevents rounding in usr2screen().
298                 }
299                 //this.points[i].setCoordinates(JXG.COORDS_BY_USER, [this.X(x,suspendUpdate),this.Y(y,suspendUpdate)], false); // The last parameter prevents rounding in usr2screen().
300                 this.updateTransform(this.points[i]);
301                 suspendUpdate = true;
302             }
303         } else { // continuous x data
304             if (this.visProp.doadvancedplot) {
305                 this.updateParametricCurve(mi, ma, len);
306             } else {
307                 if (this.board.updateQuality==this.board.BOARD_QUALITY_HIGH) {
308                     this.numberPoints = this.visProp.numberpointshigh;
309                 } else {
310                     this.numberPoints = this.visProp.numberpointslow;
311                 }
312                 this.allocatePoints();  // It is possible, that the array length has increased.
313                 this.updateParametricCurveNaive(mi, ma, this.numberPoints);
314             }
315             len = this.numberPoints;
316             for (i=0; i<len; i++) {
317                 this.updateTransform(this.points[i]);
318             }
319         }
320 
321         return this;
322     },
323 
324     updateTransformMatrix: function() {
325         var t, c, i,
326             len = this.transformations.length;
327 
328         this.transformMat = [[1,0,0], [0,1,0], [0,0,1]];
329         
330         for (i = 0; i < len; i++) {
331             t = this.transformations[i];
332             t.update();
333             this.transformMat = JXG.Math.matMatMult(t.matrix, this.transformMat);
334         }
335     
336         return this;
337     },
338     
339     /**
340      * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#visProp.doadvancedplot} is <tt>false</tt>.
341      * @param {Number} mi Left bound of curve
342      * @param {Number} ma Right bound of curve
343      * @param {Number} len Number of data points
344      * @returns {JXG.Curve} Reference to the curve object.
345      */
346     updateParametricCurveNaive: function(mi, ma, len) {
347         var i, t,
348             suspendUpdate = false,
349             stepSize = (ma-mi)/len;
350 
351         for (i=0; i<len; i++) {
352             t = mi+i*stepSize;
353             this.points[i].setCoordinates(JXG.COORDS_BY_USER, [this.X(t,suspendUpdate),this.Y(t,suspendUpdate)], false); // The last parameter prevents rounding in usr2screen().
354             suspendUpdate = true;
355         }
356         return this;
357     },
358 
359     /**
360      * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#visProp.doadvancedplot} is <tt>true</tt>.
361      * @param {Number} mi Left bound of curve
362      * @param {Number} ma Right bound of curve
363      * @param {Number} len Number of data points
364      * @returns {JXG.Curve} Reference to the curve object.
365      */
366     updateParametricCurve: function(mi, ma) {
367         var i, t, t0,
368             suspendUpdate = false,
369             po = new JXG.Coords(JXG.COORDS_BY_USER, [0,0], this.board),
370             x, y, x0, y0, top, depth,
371             MAX_DEPTH,
372             MAX_XDIST,
373             MAX_YDIST,
374             dyadicStack = [],
375             depthStack = [],
376             pointStack = [],
377             divisors = [],
378             distOK = false,
379             j = 0,
380             d,
381             distFromLine = function(p1, p2, p0) {
382                 var x0 = p0[1] - p1[1],
383                     y0 = p0[2] - p1[2],
384                     x1 = p2[0] - p1[1],
385                     y1 = p2[1] - p1[2],
386                     den = x1 * x1 + y1 * y1,
387                     lbda, d;
388 
389                 if (den >= JXG.Math.eps) {
390                     lbda = (x0 * x1 + y0 * y1) / den;
391                     if (lbda>0.0) {
392                         if (lbda<=1.0) {
393                             x0 -= lbda*x1;
394                             y0 -= lbda*y1;
395                             
396                         } else { // lbda = 1.0;
397                             x0 -= x1;
398                             y0 -= y1;
399                         }
400                     }
401                 }
402                 d = x0*x0 + y0*y0;
403                 return Math.sqrt(d);
404             };
405 
406         if (this.board.updateQuality == this.board.BOARD_QUALITY_LOW) {
407             MAX_DEPTH = 15;
408             MAX_XDIST = 10;
409             MAX_YDIST = 10;
410         } else {
411             MAX_DEPTH = 21;
412             MAX_XDIST = 0.7;
413             MAX_YDIST = 0.7;
414         }
415         
416         divisors[0] = ma-mi;
417         for (i = 1; i < MAX_DEPTH; i++) {
418             divisors[i] = divisors[i-1]*0.5;
419         }
420 
421         i = 1;
422         dyadicStack[0] = 1;
423         depthStack[0] = 0;
424         
425         t = mi;
426         po.setCoordinates(JXG.COORDS_BY_USER, [this.X(t,suspendUpdate),this.Y(t,suspendUpdate)], false);
427         
428         // Now, there was a first call to the functions defining the curve.
429         // Defining elements like sliders have been evaluated.
430         // Therefore, we can set suspendUpdate to false, so that these defining elements
431         // need not be evaluated anymore for the rest of the plotting.
432         suspendUpdate = true; 
433         x0 = po.scrCoords[1];
434         y0 = po.scrCoords[2];
435         t0 = t;
436 
437         t = ma;
438         po.setCoordinates(JXG.COORDS_BY_USER, [this.X(t,suspendUpdate),this.Y(t,suspendUpdate)], false);
439         x = po.scrCoords[1];
440         y = po.scrCoords[2];
441 
442         pointStack[0] = [x,y];
443 
444         top = 1;
445         depth = 0;
446 
447         this.points = [];
448         this.points[j++] = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x0, y0], this.board);
449 
450         do {
451             distOK = this.isDistOK(x-x0, y-y0, MAX_XDIST, MAX_YDIST) || this.isSegmentOutside(x0,y0,x,y);
452             while (depth < MAX_DEPTH 
453                    && (!distOK || depth < 6) 
454                    && (depth <= 7 || this.isSegmentDefined(x0, y0, x, y)) ) {
455                 // We jump out of the loop if
456                 // * depth>=MAX_DEPTH or
457                 // * (depth>=6 and distOK) or
458                 // * (depth>7 and segment is not defined)
459                 
460                 dyadicStack[top] = i;
461                 depthStack[top] = depth;
462                 pointStack[top] = [x,y];
463                 top++;
464 
465                 i = 2*i-1;
466                 depth++;                   // Here, depth is increased and may reach MAX_DEPTH
467                 t = mi+i*divisors[depth];  // In that case, t is undefined and we will see a jump
468                                            // in the curve.
469 
470                 po.setCoordinates(JXG.COORDS_BY_USER, [this.X(t,suspendUpdate),this.Y(t,suspendUpdate)], false);
471                 x = po.scrCoords[1];
472                 y = po.scrCoords[2];
473                 distOK = this.isDistOK(x-x0, y-y0, MAX_XDIST, MAX_YDIST) || this.isSegmentOutside(x0,y0,x,y);
474             }
475             
476             if (j > 1) {
477                 d = distFromLine(this.points[j-2].scrCoords, [x,y], this.points[j-1].scrCoords);
478                 if (d<0.015) {
479                     j--;
480                 }
481             }
482             this.points[j] = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x, y], this.board);
483             j++;
484 
485             x0 = x;
486             y0 = y;
487             t0 = t;
488 
489             top--;
490             x = pointStack[top][0];
491             y = pointStack[top][1];
492             depth = depthStack[top]+1;
493             i = dyadicStack[top]*2;
494 
495         } while (top > 0 && j<500000);
496         this.numberPoints = this.points.length;
497 
498         return this;
499     },
500 
501     /**
502      * Crude and cheap test if the segment defined by the two points <tt>(x0, y0)</tt> and <tt>(x1, y1)</tt> is
503      * outside the viewport of the board. All parameters have to be given in screen coordinates.
504      * @param {Number} x0
505      * @param {Number} y0
506      * @param {Number} x1
507      * @param {Number} y1
508      * @returns {Boolean} <tt>true</tt> if the given segment is outside the visible area.
509      */
510     isSegmentOutside: function (x0, y0, x1, y1) {
511         return (y0 < 0 && y1 < 0) || (y0 > this.board.canvasHeight && y1 > this.board.canvasHeight) ||
512                (x0 < 0 && x1 < 0) || (x0 > this.board.canvasWidth && x1 > this.board.canvasWidth);
513     },
514 
515     /**
516      * Compares the absolute value of <tt>dx</tt> with <tt>MAXX</tt> and the absolute value of <tt>dy</tt>
517      * with <tt>MAXY</tt>.
518      * @param {Number} dx
519      * @param {Number} dy
520      * @param {Number} MAXX
521      * @param {Number} MAXY
522      * @returns {Boolean} <tt>true</tt>, if <tt>|dx| < MAXX</tt> and <tt>|dy| < MAXY</tt>.
523      */
524     isDistOK: function (dx, dy, MAXX, MAXY) {
525         return (Math.abs(dx) < MAXX && Math.abs(dy) < MAXY) && !isNaN(dx+dy);
526     },
527 
528     isSegmentDefined: function (x0,y0,x1,y1) {
529         return !(isNaN(x0 + y0) && isNaN(x1 + y1));
530     },
531 
532     /**
533      * Applies the transformations of the curve to the given point <tt>p</tt>.
534      * Before using it, {@link JXG.Curve#updateTransformMatrix} has to be called.
535      * @param {JXG.Point} p
536      * @returns {JXG.Point} The given point.
537      */
538     updateTransform: function (p) {
539         var c, len = this.transformations.length;
540         /*
541         for (i = 0; i < len; i++) {
542             t = this.transformations[i];
543             t.update();
544             c = JXG.Math.matVecMult(t.matrix, p.usrCoords);
545             p.setCoordinates(JXG.COORDS_BY_USER, [c[1], c[2]]);
546         }
547         */
548         if (len>0) {
549             c = JXG.Math.matVecMult(this.transformMat, p.usrCoords);
550             p.setCoordinates(JXG.COORDS_BY_USER, [c[1], c[2]]);
551         }
552         
553         return p;
554     },
555 
556     /**
557      * Add transformations to this curve.
558      * @param {JXG.Transform|Array} transform Either one {@link JXG.Transform} or an array of {@link JXG.Transform}s.
559      * @returns {JXG.Curve} Reference to the curve object.
560      */
561     addTransform: function (transform) {
562         var i,
563             list = JXG.isArray(transform) ? transform : [transform],
564             len = list.length;
565         
566         for (i = 0; i < len; i++) {
567             this.transformations.push(list[i]);
568         }
569         
570         return this;
571     },
572 
573     /**
574      * Translates the object by <tt>(x, y)</tt>.
575      * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
576      * @param {Array} coords array of translation vector.
577      * @returns {JXG.Curve} Reference to the curve object.
578      */
579     setPosition: function (method, coords) {
580         var t, obj, len=0, i;
581         
582         if (JXG.exists(this.parents)) {
583             len = this.parents.length;
584         }
585         
586         for (i=0; i<len; i++) {
587             obj = JXG.getRef(this.board, this.parents[i]);
588             
589             if (!obj.draggable()) {
590                 return this;
591             }
592         }
593 
594         /**
595          * We distinguish two cases:
596          * 1) curves which depend on free elements, i.e. arcs and sectors
597          * 2) other curves
598          *
599          * In the first case we simply transform the parents elements
600          * In the second case we add a transform to the curve.
601          */
602 
603         coords = new JXG.Coords(method, coords, this.board);
604         t = this.board.create('transform', coords.usrCoords.slice(1),{type:'translate'});
605         
606         if (len>0) {   // First case
607             for (i=0; i<len; i++) {
608                 obj = JXG.getRef(this.board, this.parents[i]);
609                 t.applyOnce(obj);
610             }
611         } else {      // Second case
612             if (this.transformations.length > 0 
613                 && this.transformations[this.transformations.length-1].isNumericMatrix) {
614             
615                 this.transformations[this.transformations.length-1].melt(t);
616             } else {
617                 this.addTransform(t);
618             }
619         }
620         return this;
621     },
622 
623     /**
624      * Moves the cuvre by the difference of two coordinates.
625      * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
626      * @param {Array} coords coordinates in screen/user units
627      * @param {Array} oldcoords previous coordinates in screen/user units
628      * @returns {JXG.Curve}
629      */
630     setPositionDirectly: function (method, coords, oldcoords) {
631         var c = new JXG.Coords(method, coords, this.board),
632             oldc = new JXG.Coords(method, oldcoords, this.board),
633             dc = JXG.Math.Statistics.subtract(c.usrCoords, oldc.usrCoords);
634             
635         this.setPosition(JXG.COORDS_BY_USER, dc);
636         
637         return this;
638     },
639     
640     /**
641      * Generate the method curve.X() in case curve.dataX is an array
642      * and generate the method curve.Y() in case curve.dataY is an array.
643      * @see JXG.Math.Geometry#generateTerm.
644      * @private
645      * @param {String} which Either 'X' or 'Y'
646      * @return {Function} 
647      **/
648     interpolationFunctionFromArray: function(which) {
649         var data = 'data' + which;
650         return function(t, suspendedUpdate) { 
651                 var i, f1, f2, 
652                     arr = this[data],
653                     len = arr.length,
654                     z, t0, t1, 
655                     f=[], j;
656 
657                 if (isNaN(t)) {
658                     return NaN;
659                 }
660                 
661                 if (t < 0) {
662                     if (JXG.isFunction(arr[0])) {
663                        return arr[0]();
664                     } else {
665                        return arr[0];
666                     }
667                 }
668  
669                 if (this.bezierDegree==3) {
670                     len /=3;
671                     if (t >= len) {
672                         if (JXG.isFunction(arr[arr.length-1])) {
673                             return arr[arr.length-1]();
674                         } else {
675                             return arr[arr.length-1];
676                         }
677                     }
678                            
679                     i = Math.floor(t) * 3,
680                     t0 = t % 1,
681                     t1 = 1 - t0;             
682                     
683                     for (j=0;j<4;j++) {
684                         if (JXG.isFunction(arr[i+j])) {
685                             f[j] = arr[i+j]();
686                         } else {
687                             f[j] = arr[i+j];
688                         }
689                     }
690                     return t1*t1*(t1*f[0] + 3*t0*f[1]) + (3*t1*f[2] + t0*f[3])*t0*t0;
691                 } else {
692                     if (t>len-2) {
693                         i = len-2;
694                     } else {
695                         i = parseInt(Math.floor(t));
696                     }
697                         
698                     if (i==t) {
699                         if (JXG.isFunction(arr[i])) {
700                             return arr[i](); 
701                         } else {
702                             return arr[i]; 
703                         }
704                     } else {
705                         for (j=0;j<2;j++) {
706                             if (JXG.isFunction(arr[i+j])) {
707                                 f[j] = arr[i+j]();
708                             } else {
709                                 f[j] = arr[i+j];
710                             }
711                         }
712                         return f[0]+(f[1]-f[0])*(t-i);
713                     }
714                 }
715             };
716     },
717     /**
718      * Converts the GEONExT syntax of the defining function term into JavaScript.
719      * New methods X() and Y() for the Curve object are generated, further
720      * new methods for minX() and maxX().
721      * @see JXG.GeonextParser#geonext2JS.
722      */
723     generateTerm: function (varname, xterm, yterm, mi, ma) {
724         var fx, fy;
725 
726         // Generate the methods X() and Y()
727         if (JXG.isArray(xterm)) {
728             // Discrete data
729             this.dataX = xterm;
730             
731             this.numberPoints = this.dataX.length;
732             this.X = this.interpolationFunctionFromArray('X');
733             this.visProp.curvetype = 'plot';
734             this.isDraggable = true;
735         } else {
736             // Continuous data
737             this.X = JXG.createFunction(xterm, this.board, varname);
738             if (JXG.isString(xterm)) {
739                 this.visProp.curvetype = 'functiongraph';
740             } else if (JXG.isFunction(xterm) || JXG.isNumber(xterm)) {
741                 this.visProp.curvetype = 'parameter';
742             }
743 
744             this.isDraggable = true;
745         }
746 
747         if (JXG.isArray(yterm)) {
748             this.dataY = yterm;
749             this.Y = this.interpolationFunctionFromArray('Y');
750         } else {
751             this.Y = JXG.createFunction(yterm,this.board,varname);
752         }
753 
754         /**
755           * Polar form
756           * Input data is function xterm() and offset coordinates yterm
757           */
758         if (JXG.isFunction(xterm) && JXG.isArray(yterm)) {
759             // Xoffset, Yoffset
760             fx = JXG.createFunction(yterm[0],this.board,'');
761             fy = JXG.createFunction(yterm[1],this.board,'');
762             this.X = function(phi){return (xterm)(phi)*Math.cos(phi)+fx();};
763             this.Y = function(phi){return (xterm)(phi)*Math.sin(phi)+fy();};
764             this.visProp.curvetype = 'polar';
765         }
766 
767         // Set the bounds
768         // lower bound
769         if (mi!=null) this.minX = JXG.createFunction(mi,this.board,'');
770         if (ma!=null) this.maxX = JXG.createFunction(ma,this.board,'');
771     },
772 
773     /**
774      * Finds dependencies in a given term and notifies the parents by adding the
775      * dependent object to the found objects child elements.
776      * @param {String} contentStr String containing dependencies for the given object.
777      */
778     notifyParents: function (contentStr) {
779         JXG.GeonextParser.findDependencies(this,contentStr, this.board);
780     },
781 
782     // documented in geometry element
783     getLabelAnchor: function() {
784         var c, x, y, 
785             ax = 0.05*this.board.canvasWidth,
786             ay = 0.05*this.board.canvasHeight,
787             bx = 0.95*this.board.canvasWidth, 
788             by = 0.95*this.board.canvasHeight;
789         
790         switch (this.visProp.label.position) {
791             case 'ulft':
792                 x = ax; y = ay; break;
793             case 'llft':
794                 x = ax; y = by; break;
795             case 'rt':
796                 x = bx; y = 0.5*by; break;
797             case 'lrt':
798                 x = bx; y = by; break;
799             case 'urt':
800                 x = bx; y = ay; break;
801             case 'top':
802                 x = 0.5*bx; y = ay; break;
803             case 'bot':
804                 x = 0.5*bx; y = by; break;
805             case 'lft':
806             default:
807                 x = ax; y = 0.5*by; break;
808         }
809         c = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x, y], this.board);
810         return JXG.Math.Geometry.projectCoordsToCurve(c.usrCoords[1],c.usrCoords[2], 0.0, this, this.board)[0];
811     },
812 
813     // documented in geometry element
814     cloneToBackground: function () {
815         var copy = {}, er;
816 
817         copy.id = this.id + 'T' + this.numTraces;
818         copy.elementClass = JXG.OBJECT_CLASS_CURVE;
819         this.numTraces++;
820 
821         copy.points = this.points.slice(0);
822         copy.numberPoints = this.numberPoints;
823         copy.board = this.board;
824         copy.visProp = JXG.deepCopy(this.visProp, this.visProp.traceattributes, true);
825         copy.visProp.layer = this.board.options.layer.trace;
826         copy.visProp.curvetype = this.visProp.curvetype;
827 
828         JXG.clearVisPropOld(copy);
829         
830         er = this.board.renderer.enhancedRendering;
831         this.board.renderer.enhancedRendering = true;
832         this.board.renderer.drawCurve(copy);
833         this.board.renderer.enhancedRendering = er;
834         this.traces[copy.id] = copy.rendNode;
835 
836         return this;
837     },
838 
839     // already documented in GeometryElement
840     bounds: function () {
841         var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity,
842             l = this.points.length, i;
843 
844         for (i = 0; i < l; i++) {
845             if (minX > this.points[i].usrCoords[1]) {
846                 minX = this.points[i].usrCoords[1];
847             }
848 
849             if (maxX < this.points[i].usrCoords[1]) {
850                 maxX = this.points[i].usrCoords[1];
851             }
852 
853             if (minY > this.points[i].usrCoords[2]) {
854                 minY = this.points[i].usrCoords[2];
855             }
856 
857             if (maxY < this.points[i].usrCoords[2]) {
858                 maxY = this.points[i].usrCoords[2];
859             }
860         }
861 
862         return [minX, maxY, maxX, minY];
863     }
864 });
865 
866 
867 /**
868  * @class This element is used to provide a constructor for curve, which is just a wrapper for element {@link Curve}. 
869  * A curve is a mapping from R to R^2. t mapsto (x(t),y(t)). The graph is drawn for t in the interval [a,b]. 
870  * <p>
871  * The following types of curves can be plotted:
872  * <ul>
873  *  <li> parametric curves: t mapsto (x(t),y(t)), where x() and y() are univariate functions.
874  *  <li> polar curves: curves commonly written with polar equations like spirals and cardioids.
875  *  <li> data plots: plot linbe segments through a given list of coordinates.
876  * </ul>
877  * @pseudo
878  * @description
879  * @name Curve
880  * @augments JXG.Curve
881  * @constructor
882  * @type JXG.Curve
883  *
884  * @param {function,number_function,number_function,number_function,number} x,y,a_,b_ Parent elements for Parametric Curves. 
885  *                     <p>
886  *                     x describes the x-coordinate of the curve. It may be a function term in one variable, e.g. x(t). 
887  *                     In case of x being of type number, x(t) is set to  a constant function.
888  *                     this function at the values of the array.
889  *                     </p>
890  *                     <p>
891  *                     y describes the y-coordinate of the curve. In case of a number, y(t) is set to the constant function
892  *                     returning this number. 
893  *                     </p>
894  *                     <p>
895  *                     Further parameters are an optional number or function for the left interval border a, 
896  *                     and an optional number or function for the right interval border b. 
897  *                     </p>
898  *                     <p>
899  *                     Default values are a=-10 and b=10.
900  *                     </p>
901  * @param {array_array,function,number} x,y Parent elements for Data Plots.
902  *                     <p>
903  *                     x and y are arrays contining the x and y coordinates of the data points which are connected by
904  *                     line segments. The individual entries of x and y may also be functions.
905  *                     In case of x being an array the curve type is data plot, regardless of the second parameter and 
906  *                     if additionally the second parameter y is a function term the data plot evaluates.
907  *                     </p>
908  * @param {function_array,function,number_function,number_function,number} r,offset_,a_,b_ Parent elements for Polar Curves.
909  *                     <p>
910  *                     The first parameter is a function term r(phi) describing the polar curve.
911  *                     </p>
912  *                     <p>
913  *                     The second parameter is the offset of the curve. It has to be
914  *                     an array containing numbers or functions describing the offset. Default value is the origin [0,0].
915  *                     </p>
916  *                     <p>
917  *                     Further parameters are an optional number or function for the left interval border a, 
918  *                     and an optional number or function for the right interval border b. 
919  *                     </p>
920  *                     <p>
921  *                     Default values are a=-10 and b=10.
922  *                     </p>
923  * @see JXG.Curve
924  * @example
925  * // Parametric curve
926  * // Create a curve of the form (t-sin(t), 1-cos(t), i.e.
927  * // the cycloid curve.
928  *   var graph = board.create('curve', 
929  *                        [function(t){ return t-Math.sin(t);}, 
930  *                         function(t){ return 1-Math.cos(t);},
931  *                         0, 2*Math.PI]
932  *                     );
933  * </pre><div id="af9f818b-f3b6-4c4d-8c4c-e4a4078b726d" style="width: 300px; height: 300px;"></div>
934  * <script type="text/javascript">
935  *   var c1_board = JXG.JSXGraph.initBoard('af9f818b-f3b6-4c4d-8c4c-e4a4078b726d', {boundingbox: [-1, 5, 7, -1], axis: true, showcopyright: false, shownavigation: false});
936  *   var graph1 = c1_board.create('curve', [function(t){ return t-Math.sin(t);},function(t){ return 1-Math.cos(t);},0, 2*Math.PI]);
937  * </script><pre>
938  * @example
939  * // Data plots
940  * // Connect a set of points given by coordinates with dashed line segments.
941  * // The x- and y-coordinates of the points are given in two separate 
942  * // arrays.
943  *   var x = [0,1,2,3,4,5,6,7,8,9];
944  *   var y = [9.2,1.3,7.2,-1.2,4.0,5.3,0.2,6.5,1.1,0.0];
945  *   var graph = board.create('curve', [x,y], {dash:2});
946  * </pre><div id="7dcbb00e-b6ff-481d-b4a8-887f5d8c6a83" style="width: 300px; height: 300px;"></div>
947  * <script type="text/javascript">
948  *   var c3_board = JXG.JSXGraph.initBoard('7dcbb00e-b6ff-481d-b4a8-887f5d8c6a83', {boundingbox: [-1,10,10,-1], axis: true, showcopyright: false, shownavigation: false});
949  *   var x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
950  *   var y = [9.2, 1.3, 7.2, -1.2, 4.0, 5.3, 0.2, 6.5, 1.1, 0.0];
951  *   var graph3 = c3_board.create('curve', [x,y], {dash:2});
952  * </script><pre>
953  * @example
954  * // Polar plot
955  * // Create a curve with the equation r(phi)= a*(1+phi), i.e.
956  * // a cardioid.
957  *   var a = board.create('slider',[[0,2],[2,2],[0,1,2]]);
958  *   var graph = board.create('curve', 
959  *                        [function(phi){ return a.Value()*(1-Math.cos(phi));}, 
960  *                         [1,0], 
961  *                         0, 2*Math.PI]
962  *                     );
963  * </pre><div id="d0bc7a2a-8124-45ca-a6e7-142321a8f8c2" style="width: 300px; height: 300px;"></div>
964  * <script type="text/javascript">
965  *   var c2_board = JXG.JSXGraph.initBoard('d0bc7a2a-8124-45ca-a6e7-142321a8f8c2', {boundingbox: [-3,3,3,-3], axis: true, showcopyright: false, shownavigation: false});
966  *   var a = c2_board.create('slider',[[0,2],[2,2],[0,1,2]]);
967  *   var graph2 = c2_board.create('curve', [function(phi){ return a.Value()*(1-Math.cos(phi));}, [1,0], 0, 2*Math.PI]);
968  * </script><pre>
969  */
970 JXG.createCurve = function(board, parents, attributes) {
971     var attr = JXG.copyAttributes(attributes, board.options, 'curve');
972     return new JXG.Curve(board, ['x'].concat(parents), attr);
973 };
974 
975 JXG.JSXGraph.registerElement('curve', JXG.createCurve);
976 
977 /**
978  * @class This element is used to provide a constructor for functiongraph, which is just a wrapper for element {@link Curve} with {@link JXG.Curve#X()}
979  * set to x. The graph is drawn for x in the interval [a,b].
980  * @pseudo
981  * @description
982  * @name Functiongraph
983  * @augments JXG.Curve
984  * @constructor
985  * @type JXG.Curve
986  * @param {function_number,function_number,function} f,a_,b_ Parent elements are a function term f(x) describing the function graph. 
987  *         <p>
988  *         Further, an optional number or function for the left interval border a, 
989  *         and an optional number or function for the right interval border b. 
990  *         <p>
991  *         Default values are a=-10 and b=10.
992  * @see JXG.Curve
993  * @example
994  * // Create a function graph for f(x) = 0.5*x*x-2*x
995  *   var graph = board.create('functiongraph', 
996  *                        [function(x){ return 0.5*x*x-2*x;}, -2, 4]
997  *                     );
998  * </pre><div id="efd432b5-23a3-4846-ac5b-b471e668b437" style="width: 300px; height: 300px;"></div>
999  * <script type="text/javascript">
1000  *   var alex1_board = JXG.JSXGraph.initBoard('efd432b5-23a3-4846-ac5b-b471e668b437', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false});
1001  *   var graph = alex1_board.create('functiongraph', [function(x){ return 0.5*x*x-2*x;}, -2, 4]);
1002  * </script><pre>
1003  * @example
1004  * // Create a function graph for f(x) = 0.5*x*x-2*x with variable interval
1005  *   var s = board.create('slider',[[0,4],[3,4],[-2,4,5]]);
1006  *   var graph = board.create('functiongraph', 
1007  *                        [function(x){ return 0.5*x*x-2*x;}, 
1008  *                         -2, 
1009  *                         function(){return s.Value();}]
1010  *                     );
1011  * </pre><div id="4a203a84-bde5-4371-ad56-44619690bb50" style="width: 300px; height: 300px;"></div>
1012  * <script type="text/javascript">
1013  *   var alex2_board = JXG.JSXGraph.initBoard('4a203a84-bde5-4371-ad56-44619690bb50', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false});
1014  *   var s = alex2_board.create('slider',[[0,4],[3,4],[-2,4,5]]);
1015  *   var graph = alex2_board.create('functiongraph', [function(x){ return 0.5*x*x-2*x;}, -2, function(){return s.Value();}]);
1016  * </script><pre>
1017  */
1018 JXG.createFunctiongraph = function(board, parents, attributes) {
1019     var attr, par = ["x","x"].concat(parents);
1020 
1021     attr = JXG.copyAttributes(attributes, board.options, 'curve');
1022     attr['curvetype'] = 'functiongraph';
1023     return new JXG.Curve(board, par, attr);
1024 };
1025 
1026 JXG.JSXGraph.registerElement('functiongraph', JXG.createFunctiongraph);
1027 JXG.JSXGraph.registerElement('plot', JXG.createFunctiongraph);
1028 
1029 
1030 /**
1031  * TODO
1032  * Create a dynamic spline interpolated curve given by sample points p_1 to p_n.
1033  * @param {JXG.Board} board Reference to the board the spline is drawn on.
1034  * @param {Array} parents Array of points the spline interpolates
1035  * @param {Object} attributes Define color, width, ... of the spline
1036  * @type JXG.Curve
1037  * @return Returns reference to an object of type JXG.Curve.
1038  */
1039 JXG.createSpline = function(board, parents, attributes) {
1040     var F;
1041     F = function() {
1042         var D, x=[], y=[];
1043         
1044         var fct = function (t,suspended) {
1045             var i, j;
1046         
1047             if (!suspended) {
1048                 x = [];
1049                 y = [];
1050 
1051                 // given as [x[], y[]]
1052                 if(parents.length == 2 && JXG.isArray(parents[0]) && JXG.isArray(parents[1]) && parents[0].length == parents[1].length) {
1053                     for(i=0; i<parents[0].length; i++) {
1054                         if(typeof parents[0][i] == 'function')
1055                             x.push(parents[0][i]());
1056                         else
1057                             x.push(parents[0][i]);
1058                         if(typeof parents[1][i] == 'function')
1059                             y.push(parents[1][i]());
1060                         else
1061                             y.push(parents[1][i]);
1062                     }
1063                 } else {
1064                     for(i=0; i<parents.length; i++) {
1065                         if(JXG.isPoint(parents[i])) {
1066                             //throw new Error("JSXGraph: JXG.createSpline: Parents has to be an array of JXG.Point.");
1067                             x.push(parents[i].X());
1068                             y.push(parents[i].Y());
1069                         } else if (JXG.isArray(parents[i]) && parents[i].length == 2) {     // given as [[x1,y1], [x2, y2], ...]
1070                             for(i=0; i<parents.length; i++) {
1071                                 if(typeof parents[i][0] == 'function')
1072                                     x.push(parents[i][0]());
1073                                 else
1074                                     x.push(parents[i][0]);
1075                                 if(typeof parents[i][1] == 'function')
1076                                     y.push(parents[i][1]());
1077                                 else
1078                                     y.push(parents[i][1]);
1079                             }
1080                         }
1081                     }
1082                 }
1083         
1084                 // The array D has only to be calculated when the position of one or more sample point
1085                 // changes. otherwise D is always the same for all points on the spline.
1086                 D = JXG.Math.Numerics.splineDef(x, y);
1087             }
1088             return JXG.Math.Numerics.splineEval(t, x, y, D);
1089         };
1090         return fct;
1091     };
1092     return board.create('curve', ["x", F()], attributes);
1093 };
1094 
1095 /**
1096  * Register the element type spline at JSXGraph
1097  * @private
1098  */
1099 JXG.JSXGraph.registerElement('spline', JXG.createSpline);
1100 
1101 /**
1102  * @class This element is used to provide a constructor for Riemann sums, which is realized as a special curve. 
1103  * The returned element has the method Value() which returns the sum of the areas of the rectangles.
1104  * @pseudo
1105  * @description
1106  * @name Riemannsum
1107  * @augments JXG.Curve
1108  * @constructor
1109  * @type JXG.Curve
1110  * @param {function_number,function_string,function_function,number_function,number} f,n,type_,a_,b_ Parent elements of Riemannsum are a 
1111  *         function term f(x) describing the function graph which is filled by the Riemann rectangles.
1112  *         <p>
1113  *         n determines the number of rectangles, it is either a fixed number or a function.
1114  *         <p>
1115  *         type is a string or function returning one of the values:  'left', 'right', 'middle', 'lower', 'upper', or 'trapezodial'.
1116  *         Default value is 'left'.
1117  *         <p>
1118  *         Further parameters are an optional number or function for the left interval border a, 
1119  *         and an optional number or function for the right interval border b. 
1120  *         <p>
1121  *         Default values are a=-10 and b=10.
1122  * @see JXG.Curve
1123  * @example
1124  * // Create Riemann sums for f(x) = 0.5*x*x-2*x.
1125  *   var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1});
1126  *   var f = function(x) { return 0.5*x*x-2*x; };
1127  *   var r = board.create('riemannsum', 
1128  *               [f, function(){return s.Value();}, 'upper', -2, 5],
1129  *               {fillOpacity:0.4}
1130  *               );
1131  *   var g = board.create('functiongraph',[f, -2, 5]);
1132  *   var t = board.create('text',[-1,-1, function(){ return 'Sum=' + r.Value().toFixed(4); }]);
1133  * </pre><div id="940f40cc-2015-420d-9191-c5d83de988cf" style="width: 300px; height: 300px;"></div>
1134  * <script type="text/javascript">
1135  *   var rs1_board = JXG.JSXGraph.initBoard('940f40cc-2015-420d-9191-c5d83de988cf', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false});
1136  *   var f = function(x) { return 0.5*x*x-2*x; };
1137  *   var s = rs1_board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1});
1138  *   var r = rs1_board.create('riemannsum', [f, function(){return s.Value();}, 'upper', -2, 5], {fillOpacity:0.4});
1139  *   var g = rs1_board.create('functiongraph', [f, -2, 5]);
1140  *   var t = board.create('text',[-1,-1, function(){ return 'Sum=' + r.Value().toFixed(4); }]);
1141  * </script><pre>
1142  */
1143 JXG.createRiemannsum = function(board, parents, attributes) {
1144     var n, type, f, par, c, attr;
1145     
1146     attr = JXG.copyAttributes(attributes, board.options, 'riemannsum');
1147     attr['curvetype'] = 'plot';
1148 
1149     f = parents[0]; 
1150     n = JXG.createFunction(parents[1],board,'');
1151     if (n==null) {
1152         throw new Error("JSXGraph: JXG.createRiemannsum: argument '2' n has to be number or function." +
1153                         "\nPossible parent types: [function,n:number|function,type,start:number|function,end:number|function]");
1154     }
1155 
1156     type = JXG.createFunction(parents[2],board,'',false);
1157     if (type==null) {
1158         throw new Error("JSXGraph: JXG.createRiemannsum: argument 3 'type' has to be string or function." +
1159                         "\nPossible parent types: [function,n:number|function,type,start:number|function,end:number|function]");
1160     }
1161 
1162     par = [[0], [0]].concat(parents.slice(3));
1163     
1164     c = board.create('curve', par, attr);
1165     
1166     // Value(): Return the "Riemann sum"
1167     c.sum = 0.0;
1168     c.Value = function() { return this.sum; };
1169     
1170     c.updateDataArray = function() {
1171         var u = JXG.Math.Numerics.riemann(f, n(), type(), this.minX(), this.maxX());
1172         this.dataX = u[0];
1173         this.dataY = u[1];
1174         // Update "Riemann sum"
1175         this.sum = u[2];
1176     };
1177 
1178     return c;
1179 };
1180 
1181 JXG.JSXGraph.registerElement('riemannsum', JXG.createRiemannsum);
1182 
1183 /**
1184  * @class This element is used to provide a constructor for travce curve (simple locus curve), which is realized as a special curve. 
1185  * @pseudo
1186  * @description
1187  * @name Tracecurve
1188  * @augments JXG.Curve
1189  * @constructor
1190  * @type JXG.Curve
1191  * @param {Point,Point} Parent elements of Tracecurve are a 
1192  *         glider point and a point whose locus is traced.
1193  * @see JXG.Curve
1194  * @example
1195  * // Create trace curve.
1196     var c1 = board.create('circle',[[0, 0], [2, 0]]),
1197         p1 = board.create('point',[-3, 1]),
1198         g1 = board.create('glider',[2, 1, c1]),
1199         s1 = board.create('segment',[g1, p1]),
1200         p2 = board.create('midpoint',[s1]),
1201         curve = board.create('tracecurve', [g1, p2]);
1202     
1203  * </pre><div id="5749fb7d-04fc-44d2-973e-45c1951e29ad" style="width: 300px; height: 300px;"></div>
1204  * <script type="text/javascript">
1205  *   var tc1_board = JXG.JSXGraph.initBoard('5749fb7d-04fc-44d2-973e-45c1951e29ad', {boundingbox: [-4, 4, 4, -4], axis: false, showcopyright: false, shownavigation: false});
1206  *   var c1 = tc1_board.create('circle',[[0, 0], [2, 0]]),
1207  *       p1 = tc1_board.create('point',[-3, 1]),
1208  *       g1 = tc1_board.create('glider',[2, 1, c1]),
1209  *       s1 = tc1_board.create('segment',[g1, p1]),
1210  *       p2 = tc1_board.create('midpoint',[s1]),
1211  *       curve = tc1_board.create('tracecurve', [g1, p2]);
1212  * </script><pre>
1213  */
1214 JXG.createTracecurve = function(board, parents, attributes) {
1215     var c, glider, tracepoint, attr;
1216     
1217     if (parents.length!=2) {
1218         throw new Error("JSXGraph: Can't create trace curve with given parent'" +
1219                         "\nPossible parent types: [glider, point]");
1220     }
1221     
1222     glider = JXG.getRef(this.board, parents[0]);
1223     tracepoint = JXG.getRef(this.board, parents[1]);
1224 
1225     if (glider.type != JXG.OBJECT_TYPE_GLIDER || !JXG.isPoint(tracepoint)) {
1226         throw new Error("JSXGraph: Can't create trace curve with parent types '" +
1227                         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
1228                         "\nPossible parent types: [glider, point]");
1229     }
1230 
1231     attr = JXG.copyAttributes(attributes, board.options, 'tracecurve');
1232     attr['curvetype'] = 'plot';
1233   
1234     c = board.create('curve',[[0],[0]], attr);
1235 
1236     c.updateDataArray = function(){
1237         var i, step, t, el, pEl, x, y, v,
1238             le = attr.numberpoints, 
1239             from,
1240             savePos = glider.position, 
1241             slideObj = glider.slideObject,
1242             mi = slideObj.minX(),
1243             ma = slideObj.maxX(), savetrace;
1244 
1245         step = (ma-mi)/le;                    // set step width
1246         this.dataX = [];
1247         this.dataY = [];
1248         /*
1249          * For gliders on circles and lines a closed curve is computed.
1250          * For gliders on curves the curve is not closed.
1251          */
1252         if (slideObj.elementClass!=JXG.OBJECT_CLASS_CURVE) {   
1253             le++;
1254         }
1255         for (i=0; i<le; i++) {                    // Loop over all steps
1256             t = mi + i*step;
1257             x = slideObj.X(t)/slideObj.Z(t);
1258             y = slideObj.Y(t)/slideObj.Z(t);
1259             glider.setPositionDirectly(JXG.COORDS_BY_USER, [x, y]);    // Position the glider
1260             from = false;
1261             for (el in this.board.objects) {                         // Update all elements from the glider up to the trace element
1262                 pEl = this.board.objects[el];
1263                 if (pEl==glider) { 
1264                     from = true;
1265                 }
1266                 if (!from) {
1267                     continue;
1268                 }
1269                 if (!pEl.needsRegularUpdate) { continue; }
1270                 savetrace = pEl.visProp.trace;                       // Save the trace mode of the element
1271                 pEl.visProp.trace = false;
1272                 pEl.needsUpdate = true;
1273                 pEl.update(true);
1274                 pEl.visProp.trace = savetrace;                       // Restore the trace mode
1275                 if (pEl==tracepoint) { break; }
1276             }
1277             this.dataX[i] = tracepoint.X();                          // Store the position of the trace point
1278             this.dataY[i] = tracepoint.Y();
1279         }
1280         glider.position = savePos;                                   // Restore the original position of the glider
1281         from = false;
1282         for (el in this.board.objects) {                             // Update all elements from the glider to the trace point
1283             pEl = this.board.objects[el];
1284             if (pEl==glider) { 
1285                 from = true;
1286             }
1287             if (!from) {
1288                 continue;
1289             }
1290             if (!pEl.needsRegularUpdate) { continue; }
1291             savetrace = pEl.visProp.trace;
1292             pEl.visProp.trace = false;
1293             pEl.needsUpdate = true;
1294             pEl.update(true); //.updateRenderer();
1295             pEl.visProp.trace = savetrace;
1296             if (pEl==tracepoint) { 
1297                 break;
1298             }
1299         }
1300     };
1301 
1302     return c;
1303 };
1304 
1305 JXG.JSXGraph.registerElement('tracecurve', JXG.createTracecurve);
1306 
1307