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