1 /*
  2     Copyright 2008-2014
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
 29     and <http://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 
 33 /*global JXG: true, define: true*/
 34 /*jslint nomen: true, plusplus: true*/
 35 
 36 /* depends:
 37  jxg
 38  base/constants
 39  base/coords
 40  base/element
 41  math/math
 42  math/geometry
 43  math/statistics
 44  math/numerics
 45  parser/geonext
 46  utils/type
 47   elements:
 48    transform
 49  */
 50 
 51 /**
 52  * @fileoverview In this file the geometry element Curve is defined.
 53  */
 54 
 55 define([
 56     'jxg', 'base/constants', 'base/coords', 'base/element', 'math/math', 'math/statistics', 'math/numerics',
 57     'math/geometry', 'parser/geonext', 'utils/type', 'base/transformation', 'math/qdt'
 58 ], function (JXG, Const, Coords, GeometryElement, Mat, Statistics, Numerics, Geometry, GeonextParser, Type, Transform, QDT) {
 59 
 60     "use strict";
 61 
 62     /**
 63      * Curves are the common object for function graphs, parametric curves, polar curves, and data plots.
 64      * @class Creates a new curve object. Do not use this constructor to create a curve. Use {@link JXG.Board#create} with
 65      * type {@link Curve}, or {@link Functiongraph} instead.
 66      * @augments JXG.GeometryElement
 67      * @param {String|JXG.Board} board The board the new curve is drawn on.
 68      * @param {Array} parents defining terms An array with the functon terms or the data points of the curve.
 69      * @param {Object} attributes Defines the visual appearance of the curve.
 70      * @see JXG.Board#generateName
 71      * @see JXG.Board#addCurve
 72      */
 73     JXG.Curve = function (board, parents, attributes) {
 74         this.constructor(board, attributes, Const.OBJECT_TYPE_CURVE, Const.OBJECT_CLASS_CURVE);
 75 
 76         this.points = [];
 77         /**
 78          * Number of points on curves. This value changes
 79          * between numberPointsLow and numberPointsHigh.
 80          * It is set in {@link JXG.Curve#updateCurve}.
 81          */
 82         this.numberPoints = this.visProp.numberpointshigh;
 83 
 84         this.bezierDegree = 1;
 85 
 86         this.dataX = null;
 87         this.dataY = null;
 88 
 89         /**
 90          * Stores a quad tree if it is required. The quad tree is generated in the curve
 91          * updates and can be used to speed up the hasPoint method.
 92          * @type {JXG.Math.Quadtree}
 93          */
 94         this.qdt = null;
 95 
 96         if (Type.exists(parents[0])) {
 97             this.varname = parents[0];
 98         } else {
 99             this.varname = 'x';
100         }
101 
102         // function graphs: "x"
103         this.xterm = parents[1];
104         // function graphs: e.g. "x^2"
105         this.yterm = parents[2];
106 
107         // Converts GEONExT syntax into JavaScript syntax
108         this.generateTerm(this.varname, this.xterm, this.yterm, parents[3], parents[4]);
109         // First evaluation of the curve
110         this.updateCurve();
111 
112         this.id = this.board.setId(this, 'G');
113         this.board.renderer.drawCurve(this);
114 
115         this.board.finalizeAdding(this);
116 
117         this.createGradient();
118         this.elType = 'curve';
119         this.createLabel();
120 
121         if (typeof this.xterm === 'string') {
122             this.notifyParents(this.xterm);
123         }
124         if (typeof this.yterm === 'string') {
125             this.notifyParents(this.yterm);
126         }
127 
128         this.methodMap = Type.deepCopy(this.methodMap, {
129             generateTerm: 'generateTerm',
130             setTerm: 'generateTerm'
131         });
132     };
133 
134     JXG.Curve.prototype = new GeometryElement();
135 
136 
137     JXG.extend(JXG.Curve.prototype, /** @lends JXG.Curve.prototype */ {
138 
139         /**
140          * Gives the default value of the left bound for the curve.
141          * May be overwritten in {@link JXG.Curve#generateTerm}.
142          * @returns {Number} Left bound for the curve.
143          */
144         minX: function () {
145             var leftCoords;
146 
147             if (this.visProp.curvetype === 'polar') {
148                 return 0;
149             }
150 
151             leftCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board, false);
152             return leftCoords.usrCoords[1];
153         },
154 
155         /**
156          * Gives the default value of the right bound for the curve.
157          * May be overwritten in {@link JXG.Curve#generateTerm}.
158          * @returns {Number} Right bound for the curve.
159          */
160         maxX: function () {
161             var rightCoords;
162 
163             if (this.visProp.curvetype === 'polar') {
164                 return 2 * Math.PI;
165             }
166             rightCoords = new Coords(Const.COORDS_BY_SCREEN, [this.board.canvasWidth, 0], this.board, false);
167 
168             return rightCoords.usrCoords[1];
169         },
170 
171         /**
172          * Treat the curve as curve with homogeneous coordinates.
173          * @param {Number} t A number between 0.0 and 1.0.
174          * @return {Number} Always 1.0
175          */
176         Z: function (t) {
177             return 1;
178         },
179 
180         /**
181          * Checks whether (x,y) is near the curve.
182          * @param {Number} x Coordinate in x direction, screen coordinates.
183          * @param {Number} y Coordinate in y direction, screen coordinates.
184          * @param {Number} start Optional start index for search on data plots.
185          * @return {Boolean} True if (x,y) is near the curve, False otherwise.
186          */
187         hasPoint: function (x, y, start) {
188             var t, checkPoint, len, invMat, c,
189                 i, j, tX, tY, res, points, qdt,
190                 steps = this.visProp.numberpointslow,
191                 d = (this.maxX() - this.minX()) / steps,
192                 prec = this.board.options.precision.hasPoint / this.board.unitX,
193                 dist = Infinity,
194                 suspendUpdate = true;
195 
196             checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board, false);
197             x = checkPoint.usrCoords[1];
198             y = checkPoint.usrCoords[2];
199 
200             if (this.transformations.length > 0) {
201                 /**
202                  * Transform the mouse/touch coordinates
203                  * back to the original position of the curve.
204                  */
205                 this.updateTransformMatrix();
206                 invMat = Mat.inverse(this.transformMat);
207                 c = Mat.matVecMult(invMat, [1, x, y]);
208                 x = c[1];
209                 y = c[2];
210             }
211 
212             if (this.visProp.curvetype === 'parameter' ||
213                     this.visProp.curvetype === 'polar') {
214 
215                 prec = prec * prec;
216 
217                 // Brute force search for a point on the curve close to the mouse pointer
218                 for (i = 0, t = this.minX(); i < steps; i++) {
219                     tX = this.X(t, suspendUpdate);
220                     tY = this.Y(t, suspendUpdate);
221 
222                     dist = (x - tX) * (x - tX) + (y - tY) * (y - tY);
223 
224                     if (dist < prec) {
225                         return true;
226                     }
227 
228                     t += d;
229                 }
230             } else if (this.visProp.curvetype === 'plot' ||
231                     this.visProp.curvetype === 'functiongraph') {
232 
233                 if (!Type.exists(start) || start < 0) {
234                     start = 0;
235                 }
236 
237                 if (Type.exists(this.qdt) && this.visProp.useqdt && this.bezierDegree !== 3) {
238                     qdt = this.qdt.query(new Coords(Const.COORDS_BY_USER, [x, y], this.board));
239                     points = qdt.points;
240                     len = points.length;
241                 } else {
242                     points = this.points;
243                     len = this.numberPoints - 1;
244                 }
245 
246                 for (i = start; i < len; i++) {
247                     res = [];
248                     if (this.bezierDegree === 3) {
249                         res.push(Geometry.projectCoordsToBeziersegment([1, x, y], this, i));
250                     } else {
251                         if (qdt) {
252                             if (points[i].prev) {
253                                 res.push(Geometry.projectCoordsToSegment(
254                                     [1, x, y],
255                                     points[i].prev.usrCoords,
256                                     points[i].usrCoords
257                                 ));
258                             }
259 
260                             // If the next point in the array is the same as the current points
261                             // next neighbor we don't have to project it onto that segment because
262                             // that will already be done in the next iteration of this loop.
263                             if (points[i].next && points[i + 1] !== points[i].next) {
264                                 res.push(Geometry.projectCoordsToSegment(
265                                     [1, x, y],
266                                     points[i].usrCoords,
267                                     points[i].next.usrCoords
268                                 ));
269                             }
270                         } else {
271                             res.push(Geometry.projectCoordsToSegment(
272                                 [1, x, y],
273                                 points[i].usrCoords,
274                                 points[i + 1].usrCoords
275                             ));
276                         }
277                     }
278 
279                     for (j = 0; j < res.length; j++) {
280                         if (res[j][1] >= 0 && res[j][1] <= 1 &&
281                                 Geometry.distance([1, x, y], res[j][0], 3) <= prec) {
282                             return true;
283                         }
284                     }
285                 }
286                 return false;
287             }
288             return (dist < prec);
289         },
290 
291         /**
292          * Allocate points in the Coords array this.points
293          */
294         allocatePoints: function () {
295             var i, len;
296 
297             len = this.numberPoints;
298 
299             if (this.points.length < this.numberPoints) {
300                 for (i = this.points.length; i < len; i++) {
301                     this.points[i] = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false);
302                 }
303             }
304         },
305 
306         /**
307          * Computes for equidistant points on the x-axis the values of the function
308          * @returns {JXG.Curve} Reference to the curve object.
309          * @see JXG.Curve#updateCurve
310          */
311         update: function () {
312             if (this.needsUpdate) {
313                 if (this.visProp.trace) {
314                     this.cloneToBackground(true);
315                 }
316                 this.updateCurve();
317             }
318 
319             return this;
320         },
321 
322         /**
323          * Updates the visual contents of the curve.
324          * @returns {JXG.Curve} Reference to the curve object.
325          */
326         updateRenderer: function () {
327             var wasReal;
328 
329             if (this.needsUpdate && this.visProp.visible) {
330                 wasReal = this.isReal;
331 
332                 this.checkReal();
333 
334                 if (this.isReal || wasReal) {
335                     this.board.renderer.updateCurve(this);
336                 }
337 
338                 if (this.isReal) {
339                     if (wasReal !== this.isReal) {
340                         this.board.renderer.show(this);
341                         if (this.hasLabel && this.label.visProp.visible) {
342                             this.board.renderer.show(this.label.content);
343                         }
344                     }
345                 } else {
346                     if (wasReal !== this.isReal) {
347                         this.board.renderer.hide(this);
348                         if (this.hasLabel && this.label.visProp.visible) {
349                             this.board.renderer.hide(this.label);
350                         }
351                     }
352                 }
353 
354                 // Update the label if visible.
355                 if (this.hasLabel && Type.exists(this.label.visProp) && this.label.visProp.visible) {
356                     this.label.update();
357                     this.board.renderer.updateText(this.label);
358                 }
359             }
360             return this;
361         },
362 
363         /**
364          * For dynamic dataplots updateCurve can be used to compute new entries
365          * for the arrays {@link JXG.Curve#dataX} and {@link JXG.Curve#dataY}. It
366          * is used in {@link JXG.Curve#updateCurve}. Default is an empty method, can
367          * be overwritten by the user.
368          */
369         updateDataArray: function () {
370             // this used to return this, but we shouldn't rely on the user to implement it.
371         },
372 
373         /**
374          * Computes for equidistant points on the x-axis the values
375          * of the function.
376          * If the mousemove event triggers this update, we use only few
377          * points. Otherwise, e.g. on mouseup, many points are used.
378          * @see JXG.Curve#update
379          * @returns {JXG.Curve} Reference to the curve object.
380          */
381         updateCurve: function () {
382             var len, mi, ma, x, y, i,
383                 //t1, t2, l1,
384                 suspendUpdate = false;
385 
386             this.updateTransformMatrix();
387             this.updateDataArray();
388             mi = this.minX();
389             ma = this.maxX();
390 
391             // Discrete data points
392             // x-coordinates are in an array
393             if (Type.exists(this.dataX)) {
394                 this.numberPoints = this.dataX.length;
395                 len = this.numberPoints;
396 
397                 // It is possible, that the array length has increased.
398                 this.allocatePoints();
399 
400                 for (i = 0; i < len; i++) {
401                     x = i;
402 
403                     // y-coordinates are in an array
404                     if (Type.exists(this.dataY)) {
405                         y = i;
406                         // The last parameter prevents rounding in usr2screen().
407                         this.points[i].setCoordinates(Const.COORDS_BY_USER, [this.dataX[i], this.dataY[i]], false);
408                     } else {
409                         // discrete x data, continuous y data
410                         y = this.X(x);
411                         // The last parameter prevents rounding in usr2screen().
412                         this.points[i].setCoordinates(Const.COORDS_BY_USER, [this.dataX[i], this.Y(y, suspendUpdate)], false);
413                     }
414 
415                     this.updateTransform(this.points[i]);
416                     suspendUpdate = true;
417                 }
418             // continuous x data
419             } else {
420                 if (this.visProp.doadvancedplot) {
421                     this.updateParametricCurve(mi, ma, len);
422                 } else if (this.visProp.doadvancedplotold) {
423                     this.updateParametricCurveOld(mi, ma, len);
424                 } else {
425                     if (this.board.updateQuality === this.board.BOARD_QUALITY_HIGH) {
426                         this.numberPoints = this.visProp.numberpointshigh;
427                     } else {
428                         this.numberPoints = this.visProp.numberpointslow;
429                     }
430 
431                     // It is possible, that the array length has increased.
432                     this.allocatePoints();
433                     this.updateParametricCurveNaive(mi, ma, this.numberPoints);
434                 }
435                 len = this.numberPoints;
436 
437                 if (this.visProp.useqdt && this.board.updateQuality === this.board.BOARD_QUALITY_HIGH) {
438                     this.qdt = new QDT(this.board.getBoundingBox());
439                     for (i = 0; i < this.points.length; i++) {
440                         this.qdt.insert(this.points[i]);
441 
442                         if (i > 0) {
443                             this.points[i].prev = this.points[i - 1];
444                         }
445 
446                         if (i < len - 1) {
447                             this.points[i].next = this.points[i + 1];
448                         }
449                     }
450                 }
451 
452                 for (i = 0; i < len; i++) {
453                     this.updateTransform(this.points[i]);
454                 }
455             }
456 
457             return this;
458         },
459 
460         updateTransformMatrix: function () {
461             var t, c, i,
462                 len = this.transformations.length;
463 
464             this.transformMat = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];
465 
466             for (i = 0; i < len; i++) {
467                 t = this.transformations[i];
468                 t.update();
469                 this.transformMat = Mat.matMatMult(t.matrix, this.transformMat);
470             }
471 
472             return this;
473         },
474 
475         /**
476          * Check if at least one point on the curve is finite and real.
477          **/
478         checkReal: function () {
479             var b = false, i, p,
480                 len = this.numberPoints;
481 
482             for (i = 0; i < len; i++) {
483                 p = this.points[i].usrCoords;
484                 if (!isNaN(p[1]) && !isNaN(p[2]) && Math.abs(p[0]) > Mat.eps) {
485                     b = true;
486                     break;
487                 }
488             }
489             this.isReal = b;
490         },
491 
492         /**
493          * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#doadvancedplot} is <tt>false</tt>.
494          * @param {Number} mi Left bound of curve
495          * @param {Number} ma Right bound of curve
496          * @param {Number} len Number of data points
497          * @returns {JXG.Curve} Reference to the curve object.
498          */
499         updateParametricCurveNaive: function (mi, ma, len) {
500             var i, t,
501                 suspendUpdate = false,
502                 stepSize = (ma - mi) / len;
503 
504             for (i = 0; i < len; i++) {
505                 t = mi + i * stepSize;
506                 // The last parameter prevents rounding in usr2screen().
507                 this.points[i].setCoordinates(Const.COORDS_BY_USER, [this.X(t, suspendUpdate), this.Y(t, suspendUpdate)], false);
508                 suspendUpdate = true;
509             }
510             return this;
511         },
512 
513         /**
514          * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#doadvancedplot} is <tt>true</tt>.
515          * Since 0.99 this algorithm is deprecated. It still can be used if {@link JXG.Curve#doadvancedplotold} is <tt>true</tt>.
516          *
517          * @deprecated
518          * @param {Number} mi Left bound of curve
519          * @param {Number} ma Right bound of curve
520          * @returns {JXG.Curve} Reference to the curve object.
521          */
522         updateParametricCurveOld: function (mi, ma) {
523             var i, t, t0, d,
524                 x, y, x0, y0, top, depth,
525                 MAX_DEPTH, MAX_XDIST, MAX_YDIST,
526                 suspendUpdate = false,
527                 po = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false),
528                 dyadicStack = [],
529                 depthStack = [],
530                 pointStack = [],
531                 divisors = [],
532                 distOK = false,
533                 j = 0,
534                 distFromLine = function (p1, p2, p0) {
535                     var lbda, d,
536                         x0 = p0[1] - p1[1],
537                         y0 = p0[2] - p1[2],
538                         x1 = p2[0] - p1[1],
539                         y1 = p2[1] - p1[2],
540                         den = x1 * x1 + y1 * y1;
541 
542                     if (den >= Mat.eps) {
543                         lbda = (x0 * x1 + y0 * y1) / den;
544                         if (lbda > 0) {
545                             if (lbda <= 1) {
546                                 x0 -= lbda * x1;
547                                 y0 -= lbda * y1;
548                             // lbda = 1.0;
549                             } else {
550                                 x0 -= x1;
551                                 y0 -= y1;
552                             }
553                         }
554                     }
555                     d = x0 * x0 + y0 * y0;
556                     return Math.sqrt(d);
557                 };
558 
559             if (this.board.updateQuality === this.board.BOARD_QUALITY_LOW) {
560                 MAX_DEPTH = 15;
561                 MAX_XDIST = 10; // 10
562                 MAX_YDIST = 10; // 10
563             } else {
564                 MAX_DEPTH = 21;
565                 MAX_XDIST = 0.7; // 0.7
566                 MAX_YDIST = 0.7; // 0.7
567             }
568 
569             divisors[0] = ma - mi;
570             for (i = 1; i < MAX_DEPTH; i++) {
571                 divisors[i] = divisors[i - 1] * 0.5;
572             }
573 
574             i = 1;
575             dyadicStack[0] = 1;
576             depthStack[0] = 0;
577 
578             t = mi;
579             po.setCoordinates(Const.COORDS_BY_USER, [this.X(t, suspendUpdate), this.Y(t, suspendUpdate)], false);
580 
581             // Now, there was a first call to the functions defining the curve.
582             // Defining elements like sliders have been evaluated.
583             // Therefore, we can set suspendUpdate to false, so that these defining elements
584             // need not be evaluated anymore for the rest of the plotting.
585             suspendUpdate = true;
586             x0 = po.scrCoords[1];
587             y0 = po.scrCoords[2];
588             t0 = t;
589 
590             t = ma;
591             po.setCoordinates(Const.COORDS_BY_USER, [this.X(t, suspendUpdate), this.Y(t, suspendUpdate)], false);
592             x = po.scrCoords[1];
593             y = po.scrCoords[2];
594 
595             pointStack[0] = [x, y];
596 
597             top = 1;
598             depth = 0;
599 
600             this.points = [];
601             this.points[j++] = new Coords(Const.COORDS_BY_SCREEN, [x0, y0], this.board, false);
602 
603             do {
604                 distOK = this.isDistOK(x - x0, y - y0, MAX_XDIST, MAX_YDIST) || this.isSegmentOutside(x0, y0, x, y);
605                 while (depth < MAX_DEPTH && (!distOK || depth < 6) && (depth <= 7 || this.isSegmentDefined(x0, y0, x, y))) {
606                     // We jump out of the loop if
607                     // * depth>=MAX_DEPTH or
608                     // * (depth>=6 and distOK) or
609                     // * (depth>7 and segment is not defined)
610 
611                     dyadicStack[top] = i;
612                     depthStack[top] = depth;
613                     pointStack[top] = [x, y];
614                     top += 1;
615 
616                     i = 2 * i - 1;
617                     // Here, depth is increased and may reach MAX_DEPTH
618                     depth++;
619                     // In that case, t is undefined and we will see a jump in the curve.
620                     t = mi + i * divisors[depth];
621 
622                     po.setCoordinates(Const.COORDS_BY_USER, [this.X(t, suspendUpdate), this.Y(t, suspendUpdate)], false, true);
623                     x = po.scrCoords[1];
624                     y = po.scrCoords[2];
625                     distOK = this.isDistOK(x - x0, y - y0, MAX_XDIST, MAX_YDIST) || this.isSegmentOutside(x0, y0, x, y);
626                 }
627 
628                 if (j > 1) {
629                     d = distFromLine(this.points[j - 2].scrCoords, [x, y], this.points[j - 1].scrCoords);
630                     if (d < 0.015) {
631                         j -= 1;
632                     }
633                 }
634 
635                 this.points[j] = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board, false);
636                 j += 1;
637 
638                 x0 = x;
639                 y0 = y;
640                 t0 = t;
641 
642                 top -= 1;
643                 x = pointStack[top][0];
644                 y = pointStack[top][1];
645                 depth = depthStack[top] + 1;
646                 i = dyadicStack[top] * 2;
647 
648             } while (top > 0 && j < 500000);
649 
650             this.numberPoints = this.points.length;
651 
652             return this;
653         },
654 
655         /**
656          * Crude and cheap test if the segment defined by the two points <tt>(x0, y0)</tt> and <tt>(x1, y1)</tt> is
657          * outside the viewport of the board. All parameters have to be given in screen coordinates.
658          *
659          * @private
660          * @param {Number} x0
661          * @param {Number} y0
662          * @param {Number} x1
663          * @param {Number} y1
664          * @returns {Boolean} <tt>true</tt> if the given segment is outside the visible area.
665          */
666         isSegmentOutside: function (x0, y0, x1, y1) {
667             return (y0 < 0 && y1 < 0) || (y0 > this.board.canvasHeight && y1 > this.board.canvasHeight) ||
668                 (x0 < 0 && x1 < 0) || (x0 > this.board.canvasWidth && x1 > this.board.canvasWidth);
669         },
670 
671         /**
672          * Compares the absolute value of <tt>dx</tt> with <tt>MAXX</tt> and the absolute value of <tt>dy</tt>
673          * with <tt>MAXY</tt>.
674          *
675          * @private
676          * @param {Number} dx
677          * @param {Number} dy
678          * @param {Number} MAXX
679          * @param {Number} MAXY
680          * @returns {Boolean} <tt>true</tt>, if <tt>|dx| < MAXX</tt> and <tt>|dy| < MAXY</tt>.
681          */
682         isDistOK: function (dx, dy, MAXX, MAXY) {
683             return (Math.abs(dx) < MAXX && Math.abs(dy) < MAXY) && !isNaN(dx + dy);
684         },
685 
686          /**
687          * @private
688          */
689         isSegmentDefined: function (x0, y0, x1, y1) {
690             return !(isNaN(x0 + y0) && isNaN(x1 + y1));
691         },
692 
693         /**
694          * Add a point to the curve plot. If the new point is too close to the previously inserted point,
695          * it is skipped.
696          * Used in {@link JXG.Curve._plotRecursive}.
697          *
698          * @private
699          * @param {JXG.Coords} pnt Coords to add to the list of points
700          */
701         _insertPoint: function(pnt) {
702             var lastReal = !isNaN(this._lastCrds[1] + this._lastCrds[2]),     // The last point was real
703                 newReal = !isNaN(pnt.scrCoords[1] + pnt.scrCoords[2]);        // New point is real point
704                 
705             /*
706              * Prevents two consecutive NaNs or points wich are too close
707              */
708             if ( (!newReal && lastReal) ||
709                  (newReal &&
710                   (!lastReal ||
711                    Math.abs(pnt.scrCoords[1] - this._lastCrds[1]) > 0.7 ||
712                    Math.abs(pnt.scrCoords[2] - this._lastCrds[2]) > 0.7)) ) {
713                 this.points.push(pnt);
714                 this._lastCrds = pnt.copy('scrCoords');
715             } 
716         },
717         
718         /**
719          * Investigate a function term at the bounds of intervals where
720          * the function is not defined, e.g. log(x) at x = 0.
721          * 
722          * c is inbetween a and b
723          * @private
724          * @param {Array} a Screen coordinates of the left interval bound
725          * @param {Array} b Screen coordinates of the right interval bound
726          * @param {Array} c Screen coordinates of the bisection point at (ta + tb) / 2
727          * @param {Number} ta Parameter which evaluates to a, i.e. [1, X(ta), Y(ta)] = a in screen coordinates
728          * @param {Number} tb Parameter which evaluates to b, i.e. [1, X(tb), Y(tb)] = b in screen coordinates
729          * @param {Number} tc (ta + tb) / 2 = tc. Parameter which evaluates to b, i.e. [1, X(tc), Y(tc)] = c in screen coordinates
730          * @param {Number} depth Actual recursion depth. The recursion stops if depth is equal to 0.
731          * @returns {JXG.Boolean} true if the point is inserted and the recursion should stop, false otherwise.
732          */
733         _borderCase: function(a, b, c, ta, tb, tc, depth) {
734             var t, pnt, p, p_good = null,
735                 i, j, maxit = 5,
736                 maxdepth = 70,
737                 is_undef = false;
738             
739             if (depth < this.smoothLevel) {
740                 pnt = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false);
741                 
742                 
743                 if (isNaN(a[1] + a[2]) && !isNaN(c[1] + c[2] + b[1] + b[2])) {
744                     // a is outside of the definition interval, c and b are inside
745                     
746                     for (i = 0; i < maxdepth; ++i) {
747                         j = 0;
748                         
749                         // Bisect a and c until the new point is inside of the definition interval
750                         do {
751                             t = 0.5 * (ta + tc); 
752                             pnt.setCoordinates(Const.COORDS_BY_USER, [this.X(t, true), this.Y(t, true)], false);        
753                             p = pnt.scrCoords;
754                             is_undef = isNaN(p[1] + p[2]);
755 
756                             if (is_undef) {
757                                 ta = t;
758                             }
759                             ++j;
760                         } while (is_undef && j < maxit);
761                         
762                         // If bisection was successful, remember this point
763                         if (j < maxit) {
764                             tc = t;
765                             p_good = p.slice();
766                         } else {
767                             break;
768                         }
769                     }
770                     
771                 } else if (isNaN(b[1] + b[2]) && !isNaN(c[1] + c[2] + a[1] + a[2]))  {
772                     // b is outside of the definition interval, a and c are inside
773                     
774                     for (i = 0; i < maxdepth; ++i) {
775                         j = 0;
776                         do {
777                             t = 0.5 * (tc + tb); 
778                             pnt.setCoordinates(Const.COORDS_BY_USER, [this.X(t, true), this.Y(t, true)], false);        
779                             p = pnt.scrCoords;
780                             is_undef = isNaN(p[1] + p[2]);
781 
782                             if (is_undef) {
783                                 tb = t;
784                             }
785                             ++j;
786                         } while (is_undef && j < maxit);
787                         if (j < maxit) {
788                             tc = t;
789                             p_good = p.slice();
790                         } else {
791                             break;
792                         }
793                     }
794                 }
795                 
796                 if (p_good !== null) {  
797                     this._insertPoint(new Coords(Const.COORDS_BY_SCREEN, p_good.slice(1), this.board, false));
798                     return true;
799                 }
800             }
801             return false;
802         },
803         
804         /**
805          * Compute distances in screen coordinates between the points ab,
806          * ac, cb, and cd, where d = (a + b)/2.
807          * cd is used for the smoothness test, ab, ac, cb are used to detect jumps, cusps and poles.
808          * 
809          * @private
810          * @param {Array} a Screen coordinates of the left interval bound
811          * @param {Array} b Screen coordinates of the right interval bound
812          * @param {Array} c Screen coordinates of the bisection point at (ta + tb) / 2
813          * @returns {Array} array of distances in screen coordinates between: ab, ac, cb, and cd.
814          */
815         _triangleDists: function(a, b, c) {
816             var d, d_ab, d_ac, d_cb, d_cd;
817             
818             d = [a[0] * b[0], (a[1] + b[1]) * 0.5, (a[2] + b[2]) * 0.5];
819             
820             d_ab = Geometry.distance(a, b, 3);
821             d_ac = Geometry.distance(a, c, 3);
822             d_cb = Geometry.distance(c, b, 3);
823             d_cd = Geometry.distance(c, d, 3);
824             //d_cd = Math.abs(c[2] - d[2]);
825             
826             return [d_ab, d_ac, d_cb, d_cd];
827         },
828             
829         /**
830          * Recursive interval bisection algorithm for curve plotting. 
831          * Used in {@link JXG.Curve.updateParametricCurve}.
832          * @private
833          * @param {Array} a Screen coordinates of the left interval bound
834          * @param {Number} ta Parameter which evaluates to a, i.e. [1, X(ta), Y(ta)] = a in screen coordinates
835          * @param {Array} b Screen coordinates of the right interval bound
836          * @param {Number} tb Parameter which evaluates to b, i.e. [1, X(tb), Y(tb)] = b in screen coordinates
837          * @param {Number} depth Actual recursion depth. The recursion stops if depth is equal to 0.
838          * @param {Number} delta If the distance of the bisection point at (ta + tb) / 2 from the point (a + b) / 2 is less then delta,
839          *                 the segement [a,b] is regarded as straight line.
840          * @returns {JXG.Curve} Reference to the curve object.
841          */
842         _plotRecursive: function (a, ta, b, tb, depth, delta) {
843             var tc, c, 
844                 ds, mindepth = 0,
845                 isSmooth, isJump, isCusp, 
846                 cusp_threshold = 0.5,
847                 pnt = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false);
848 
849             if (this.numberPoints > 65536) return;
850 
851             tc = 0.5 * (ta  + tb);
852             pnt.setCoordinates(Const.COORDS_BY_USER, [this.X(tc, true), this.Y(tc, true)], false);
853             c = pnt.scrCoords;
854               
855             if (this._borderCase(a, b, c, ta, tb, tc, depth)) {
856                 return this;
857             }
858             
859             ds = this._triangleDists(a, b, c);           // returns [d_ab, d_ac, d_cb, d_cd]
860             isSmooth = (depth < this.smoothLevel) && (ds[3] < delta);
861             
862             isJump = (depth < this.jumpLevel) && 
863                         ((ds[2] > 0.99 * ds[0]) || (ds[1] > 0.99 * ds[0]) ||
864                         ds[0] === Infinity || ds[1] === Infinity || ds[2] === Infinity);
865             isCusp = (depth < this.smoothLevel + 2) && (ds[0] < cusp_threshold * (ds[1] + ds[2])); 
866             
867             if (isCusp) { 
868                 mindepth = 0; 
869                 isSmooth = false;
870             }
871 
872             --depth;
873             
874             if (isJump) {
875                 this._insertPoint(new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], this.board, false));
876             } else if (depth <= mindepth || isSmooth) {
877                 this._insertPoint(pnt);
878             } else {
879                 this._plotRecursive(a, ta, c, tc, depth, delta);
880                 this._insertPoint(pnt);
881                 this._plotRecursive(c, tc, b, tb, depth, delta);
882             }
883             
884             return this;
885         },
886         
887         /**
888          * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#doadvancedplot} is <tt>true</tt>.
889          * @param {Number} mi Left bound of curve
890          * @param {Number} ma Right bound of curve
891          * @returns {JXG.Curve} Reference to the curve object.
892          */
893         updateParametricCurve: function (mi, ma) {
894             var ta, tb, a, b, 
895                 suspendUpdate = false,
896                 pa = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false),
897                 pb = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false),
898                 depth, delta; 
899             
900             if (this.board.updateQuality === this.board.BOARD_QUALITY_LOW) {
901                 depth = 12;
902                 delta = 3;
903                 this.smoothLevel = depth - 5;
904                 this.jumpLevel = 5;
905             } else {
906                 depth = 17;
907                 delta = 0.9;
908                 this.smoothLevel = depth - 9;
909                 this.jumpLevel = 3;
910             }
911             
912             this.points = [];
913             this._lastCrds = [0, NaN, NaN];   // Used in _insertPoint
914             
915             ta = mi;
916             pa.setCoordinates(Const.COORDS_BY_USER, [this.X(ta, suspendUpdate), this.Y(ta, suspendUpdate)], false);
917             a = pa.copy('scrCoords');
918             suspendUpdate = true,
919 
920             tb = ma;
921             pb.setCoordinates(Const.COORDS_BY_USER, [this.X(tb, suspendUpdate), this.Y(tb, suspendUpdate)], false);
922             b = pb.copy('scrCoords');
923             
924             this.points.push(pa);
925             this._plotRecursive(a, ta, b, tb, depth, delta);
926             this.points.push(pb);
927 
928             this.numberPoints = this.points.length;
929 
930             return this;
931         },
932 
933         /**
934          * Applies the transformations of the curve to the given point <tt>p</tt>.
935          * Before using it, {@link JXG.Curve#updateTransformMatrix} has to be called.
936          * @param {JXG.Point} p
937          * @returns {JXG.Point} The given point.
938          */
939         updateTransform: function (p) {
940             var c,
941                 len = this.transformations.length;
942 
943             if (len > 0) {
944                 c = Mat.matVecMult(this.transformMat, p.usrCoords);
945                 p.setPosition(Const.COORDS_BY_USER, [c[1], c[2]]);
946             }
947 
948             return p;
949         },
950 
951         /**
952          * Add transformations to this curve.
953          * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of {@link JXG.Transformation}s.
954          * @returns {JXG.Curve} Reference to the curve object.
955          */
956         addTransform: function (transform) {
957             var i,
958                 list = Type.isArray(transform) ? transform : [transform],
959                 len = list.length;
960 
961             for (i = 0; i < len; i++) {
962                 this.transformations.push(list[i]);
963             }
964 
965             return this;
966         },
967 
968         /**
969          * Translates the object by <tt>(x, y)</tt>.
970          * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
971          * @param {Array} coords array of translation vector.
972          * @returns {JXG.Curve} Reference to the curve object.
973          */
974         setPosition: function (method, coords) {
975             var t, obj, i,
976                 len = 0;
977 
978             if (Type.exists(this.parents)) {
979                 len = this.parents.length;
980             }
981 
982             for (i = 0; i < len; i++) {
983                 obj = this.board.select(this.parents[i]);
984 
985                 if (!obj.draggable()) {
986                     return this;
987                 }
988             }
989 
990             // We distinguish two cases:
991             // 1) curves which depend on free elements, i.e. arcs and sectors
992             // 2) other curves
993             //
994             // In the first case we simply transform the parents elements
995             // In the second case we add a transform to the curve.
996             //
997             coords = new Coords(method, coords, this.board, false);
998             t = this.board.create('transform', coords.usrCoords.slice(1), {type: 'translate'});
999 
1000             if (len > 0) {
1001                 for (i = 0; i < len; i++) {
1002                     obj = this.board.select(this.parents[i]);
1003                     t.applyOnce(obj);
1004                 }
1005             } else {
1006                 if (this.transformations.length > 0 &&
1007                         this.transformations[this.transformations.length - 1].isNumericMatrix) {
1008                     this.transformations[this.transformations.length - 1].melt(t);
1009                 } else {
1010                     this.addTransform(t);
1011                 }
1012             }
1013             return this;
1014         },
1015 
1016         /**
1017          * Moves the cuvre by the difference of two coordinates.
1018          * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
1019          * @param {Array} coords coordinates in screen/user units
1020          * @param {Array} oldcoords previous coordinates in screen/user units
1021          * @returns {JXG.Curve} this element
1022          */
1023         setPositionDirectly: function (method, coords, oldcoords) {
1024             var c = new Coords(method, coords, this.board, false),
1025                 oldc = new Coords(method, oldcoords, this.board, false),
1026                 dc = Statistics.subtract(c.usrCoords, oldc.usrCoords);
1027 
1028             this.setPosition(Const.COORDS_BY_USER, dc);
1029 
1030             return this;
1031         },
1032 
1033         /**
1034          * Generate the method curve.X() in case curve.dataX is an array
1035          * and generate the method curve.Y() in case curve.dataY is an array.
1036          * @private
1037          * @param {String} which Either 'X' or 'Y'
1038          * @returns {function}
1039          **/
1040         interpolationFunctionFromArray: function (which) {
1041             var data = 'data' + which;
1042 
1043             return function (t, suspendedUpdate) {
1044                 var i, j, f1, f2, z, t0, t1,
1045                     arr = this[data],
1046                     len = arr.length,
1047                     f = [];
1048 
1049                 if (isNaN(t)) {
1050                     return NaN;
1051                 }
1052 
1053                 if (t < 0) {
1054                     if (Type.isFunction(arr[0])) {
1055                         return arr[0]();
1056                     }
1057 
1058                     return arr[0];
1059                 }
1060 
1061                 if (this.bezierDegree === 3) {
1062                     len /= 3;
1063                     if (t >= len) {
1064                         if (Type.isFunction(arr[arr.length - 1])) {
1065                             return arr[arr.length - 1]();
1066                         }
1067 
1068                         return arr[arr.length - 1];
1069                     }
1070 
1071                     i = Math.floor(t) * 3;
1072                     t0 = t % 1;
1073                     t1 = 1 - t0;
1074 
1075                     for (j = 0; j < 4; j++) {
1076                         if (Type.isFunction(arr[i + j])) {
1077                             f[j] = arr[i + j]();
1078                         } else {
1079                             f[j] = arr[i + j];
1080                         }
1081                     }
1082 
1083                     return t1 * t1 * (t1 * f[0] + 3 * t0 * f[1]) + (3 * t1 * f[2] + t0 * f[3]) * t0 * t0;
1084                 }
1085 
1086                 if (t > len - 2) {
1087                     i = len - 2;
1088                 } else {
1089                     i = parseInt(Math.floor(t), 10);
1090                 }
1091 
1092                 if (i === t) {
1093                     if (Type.isFunction(arr[i])) {
1094                         return arr[i]();
1095                     }
1096                     return arr[i];
1097                 }
1098 
1099                 for (j = 0; j < 2; j++) {
1100                     if (Type.isFunction(arr[i + j])) {
1101                         f[j] = arr[i + j]();
1102                     } else {
1103                         f[j] = arr[i + j];
1104                     }
1105                 }
1106                 return f[0] + (f[1] - f[0]) * (t - i);
1107             };
1108         },
1109         
1110         /**
1111          * Converts the GEONExT syntax of the defining function term into JavaScript.
1112          * New methods X() and Y() for the Curve object are generated, further
1113          * new methods for minX() and maxX().
1114          * @see JXG.GeonextParser.geonext2JS.
1115          */
1116         generateTerm: function (varname, xterm, yterm, mi, ma) {
1117             var fx, fy;
1118 
1119             // Generate the methods X() and Y()
1120             if (Type.isArray(xterm)) {
1121                 // Discrete data
1122                 this.dataX = xterm;
1123 
1124                 this.numberPoints = this.dataX.length;
1125                 this.X = this.interpolationFunctionFromArray('X');
1126                 this.visProp.curvetype = 'plot';
1127                 this.isDraggable = true;
1128             } else {
1129                 // Continuous data
1130                 this.X = Type.createFunction(xterm, this.board, varname);
1131                 if (Type.isString(xterm)) {
1132                     this.visProp.curvetype = 'functiongraph';
1133                 } else if (Type.isFunction(xterm) || Type.isNumber(xterm)) {
1134                     this.visProp.curvetype = 'parameter';
1135                 }
1136 
1137                 this.isDraggable = true;
1138             }
1139 
1140             if (Type.isArray(yterm)) {
1141                 this.dataY = yterm;
1142                 this.Y = this.interpolationFunctionFromArray('Y');
1143             } else {
1144                 this.Y = Type.createFunction(yterm, this.board, varname);
1145             }
1146 
1147             /**
1148              * Polar form
1149              * Input data is function xterm() and offset coordinates yterm
1150              */
1151             if (Type.isFunction(xterm) && Type.isArray(yterm)) {
1152                 // Xoffset, Yoffset
1153                 fx = Type.createFunction(yterm[0], this.board, '');
1154                 fy = Type.createFunction(yterm[1], this.board, '');
1155 
1156                 this.X = function (phi) {
1157                     return xterm(phi) * Math.cos(phi) + fx();
1158                 };
1159 
1160                 this.Y = function (phi) {
1161                     return xterm(phi) * Math.sin(phi) + fy();
1162                 };
1163 
1164                 this.visProp.curvetype = 'polar';
1165             }
1166 
1167             // Set the bounds lower bound
1168             if (Type.exists(mi)) {
1169                 this.minX = Type.createFunction(mi, this.board, '');
1170             }
1171             if (Type.exists(ma)) {
1172                 this.maxX = Type.createFunction(ma, this.board, '');
1173             }
1174         },
1175 
1176         /**
1177          * Finds dependencies in a given term and notifies the parents by adding the
1178          * dependent object to the found objects child elements.
1179          * @param {String} contentStr String containing dependencies for the given object.
1180          */
1181         notifyParents: function (contentStr) {
1182             GeonextParser.findDependencies(this, contentStr, this.board);
1183         },
1184 
1185         // documented in geometry element
1186         getLabelAnchor: function () {
1187             var c, x, y,
1188                 ax = 0.05 * this.board.canvasWidth,
1189                 ay = 0.05 * this.board.canvasHeight,
1190                 bx = 0.95 * this.board.canvasWidth,
1191                 by = 0.95 * this.board.canvasHeight;
1192 
1193             switch (this.visProp.label.position) {
1194             case 'ulft':
1195                 x = ax;
1196                 y = ay;
1197                 break;
1198             case 'llft':
1199                 x = ax;
1200                 y = by;
1201                 break;
1202             case 'rt':
1203                 x = bx;
1204                 y = 0.5 * by;
1205                 break;
1206             case 'lrt':
1207                 x = bx;
1208                 y = by;
1209                 break;
1210             case 'urt':
1211                 x = bx;
1212                 y = ay;
1213                 break;
1214             case 'top':
1215                 x = 0.5 * bx;
1216                 y = ay;
1217                 break;
1218             case 'bot':
1219                 x = 0.5 * bx;
1220                 y = by;
1221                 break;
1222             default:
1223                 // includes case 'lft'
1224                 x = ax;
1225                 y = 0.5 * by;
1226             }
1227 
1228             c = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board, false);
1229             return Geometry.projectCoordsToCurve(c.usrCoords[1], c.usrCoords[2], 0, this, this.board)[0];
1230         },
1231 
1232         // documented in geometry element
1233         cloneToBackground: function () {
1234             var er,
1235                 copy = {
1236                     id: this.id + 'T' + this.numTraces,
1237                     elementClass: Const.OBJECT_CLASS_CURVE,
1238 
1239                     points: this.points.slice(0),
1240                     bezierDegree: this.bezierDegree,
1241                     numberPoints: this.numberPoints,
1242                     board: this.board,
1243                     visProp: Type.deepCopy(this.visProp, this.visProp.traceattributes, true)
1244                 };
1245 
1246             copy.visProp.layer = this.board.options.layer.trace;
1247             copy.visProp.curvetype = this.visProp.curvetype;
1248             this.numTraces++;
1249 
1250             Type.clearVisPropOld(copy);
1251 
1252             er = this.board.renderer.enhancedRendering;
1253             this.board.renderer.enhancedRendering = true;
1254             this.board.renderer.drawCurve(copy);
1255             this.board.renderer.enhancedRendering = er;
1256             this.traces[copy.id] = copy.rendNode;
1257 
1258             return this;
1259         },
1260 
1261         // already documented in GeometryElement
1262         bounds: function () {
1263             var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity,
1264                 l = this.points.length, i;
1265 
1266             for (i = 0; i < l; i++) {
1267                 if (minX > this.points[i].usrCoords[1]) {
1268                     minX = this.points[i].usrCoords[1];
1269                 }
1270 
1271                 if (maxX < this.points[i].usrCoords[1]) {
1272                     maxX = this.points[i].usrCoords[1];
1273                 }
1274 
1275                 if (minY > this.points[i].usrCoords[2]) {
1276                     minY = this.points[i].usrCoords[2];
1277                 }
1278 
1279                 if (maxY < this.points[i].usrCoords[2]) {
1280                     maxY = this.points[i].usrCoords[2];
1281                 }
1282             }
1283 
1284             return [minX, maxY, maxX, minY];
1285         }
1286     });
1287 
1288 
1289     /**
1290      * @class This element is used to provide a constructor for curve, which is just a wrapper for element {@link Curve}.
1291      * 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].
1292      * <p>
1293      * The following types of curves can be plotted:
1294      * <ul>
1295      *  <li> parametric curves: t mapsto (x(t),y(t)), where x() and y() are univariate functions.
1296      *  <li> polar curves: curves commonly written with polar equations like spirals and cardioids.
1297      *  <li> data plots: plot linbe segments through a given list of coordinates.
1298      * </ul>
1299      * @pseudo
1300      * @description
1301      * @name Curve
1302      * @augments JXG.Curve
1303      * @constructor
1304      * @type JXG.Curve
1305      *
1306      * @param {function,number_function,number_function,number_function,number} x,y,a_,b_ Parent elements for Parametric Curves.
1307      *                     <p>
1308      *                     x describes the x-coordinate of the curve. It may be a function term in one variable, e.g. x(t).
1309      *                     In case of x being of type number, x(t) is set to  a constant function.
1310      *                     this function at the values of the array.
1311      *                     </p>
1312      *                     <p>
1313      *                     y describes the y-coordinate of the curve. In case of a number, y(t) is set to the constant function
1314      *                     returning this number.
1315      *                     </p>
1316      *                     <p>
1317      *                     Further parameters are an optional number or function for the left interval border a,
1318      *                     and an optional number or function for the right interval border b.
1319      *                     </p>
1320      *                     <p>
1321      *                     Default values are a=-10 and b=10.
1322      *                     </p>
1323      * @param {array_array,function,number} x,y Parent elements for Data Plots.
1324      *                     <p>
1325      *                     x and y are arrays contining the x and y coordinates of the data points which are connected by
1326      *                     line segments. The individual entries of x and y may also be functions.
1327      *                     In case of x being an array the curve type is data plot, regardless of the second parameter and
1328      *                     if additionally the second parameter y is a function term the data plot evaluates.
1329      *                     </p>
1330      * @param {function_array,function,number_function,number_function,number} r,offset_,a_,b_ Parent elements for Polar Curves.
1331      *                     <p>
1332      *                     The first parameter is a function term r(phi) describing the polar curve.
1333      *                     </p>
1334      *                     <p>
1335      *                     The second parameter is the offset of the curve. It has to be
1336      *                     an array containing numbers or functions describing the offset. Default value is the origin [0,0].
1337      *                     </p>
1338      *                     <p>
1339      *                     Further parameters are an optional number or function for the left interval border a,
1340      *                     and an optional number or function for the right interval border b.
1341      *                     </p>
1342      *                     <p>
1343      *                     Default values are a=-10 and b=10.
1344      *                     </p>
1345      * @see JXG.Curve
1346      * @example
1347      * // Parametric curve
1348      * // Create a curve of the form (t-sin(t), 1-cos(t), i.e.
1349      * // the cycloid curve.
1350      *   var graph = board.create('curve',
1351      *                        [function(t){ return t-Math.sin(t);},
1352      *                         function(t){ return 1-Math.cos(t);},
1353      *                         0, 2*Math.PI]
1354      *                     );
1355      * </pre><div id="af9f818b-f3b6-4c4d-8c4c-e4a4078b726d" style="width: 300px; height: 300px;"></div>
1356      * <script type="text/javascript">
1357      *   var c1_board = JXG.JSXGraph.initBoard('af9f818b-f3b6-4c4d-8c4c-e4a4078b726d', {boundingbox: [-1, 5, 7, -1], axis: true, showcopyright: false, shownavigation: false});
1358      *   var graph1 = c1_board.create('curve', [function(t){ return t-Math.sin(t);},function(t){ return 1-Math.cos(t);},0, 2*Math.PI]);
1359      * </script><pre>
1360      * @example
1361      * // Data plots
1362      * // Connect a set of points given by coordinates with dashed line segments.
1363      * // The x- and y-coordinates of the points are given in two separate
1364      * // arrays.
1365      *   var x = [0,1,2,3,4,5,6,7,8,9];
1366      *   var y = [9.2,1.3,7.2,-1.2,4.0,5.3,0.2,6.5,1.1,0.0];
1367      *   var graph = board.create('curve', [x,y], {dash:2});
1368      * </pre><div id="7dcbb00e-b6ff-481d-b4a8-887f5d8c6a83" style="width: 300px; height: 300px;"></div>
1369      * <script type="text/javascript">
1370      *   var c3_board = JXG.JSXGraph.initBoard('7dcbb00e-b6ff-481d-b4a8-887f5d8c6a83', {boundingbox: [-1,10,10,-1], axis: true, showcopyright: false, shownavigation: false});
1371      *   var x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
1372      *   var y = [9.2, 1.3, 7.2, -1.2, 4.0, 5.3, 0.2, 6.5, 1.1, 0.0];
1373      *   var graph3 = c3_board.create('curve', [x,y], {dash:2});
1374      * </script><pre>
1375      * @example
1376      * // Polar plot
1377      * // Create a curve with the equation r(phi)= a*(1+phi), i.e.
1378      * // a cardioid.
1379      *   var a = board.create('slider',[[0,2],[2,2],[0,1,2]]);
1380      *   var graph = board.create('curve',
1381      *                        [function(phi){ return a.Value()*(1-Math.cos(phi));},
1382      *                         [1,0],
1383      *                         0, 2*Math.PI]
1384      *                     );
1385      * </pre><div id="d0bc7a2a-8124-45ca-a6e7-142321a8f8c2" style="width: 300px; height: 300px;"></div>
1386      * <script type="text/javascript">
1387      *   var c2_board = JXG.JSXGraph.initBoard('d0bc7a2a-8124-45ca-a6e7-142321a8f8c2', {boundingbox: [-3,3,3,-3], axis: true, showcopyright: false, shownavigation: false});
1388      *   var a = c2_board.create('slider',[[0,2],[2,2],[0,1,2]]);
1389      *   var graph2 = c2_board.create('curve', [function(phi){ return a.Value()*(1-Math.cos(phi));}, [1,0], 0, 2*Math.PI]);
1390      * </script><pre>
1391      */
1392     JXG.createCurve = function (board, parents, attributes) {
1393         var attr = Type.copyAttributes(attributes, board.options, 'curve');
1394         return new JXG.Curve(board, ['x'].concat(parents), attr);
1395     };
1396 
1397     JXG.registerElement('curve', JXG.createCurve);
1398 
1399     /**
1400      * @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()}
1401      * set to x. The graph is drawn for x in the interval [a,b].
1402      * @pseudo
1403      * @description
1404      * @name Functiongraph
1405      * @augments JXG.Curve
1406      * @constructor
1407      * @type JXG.Curve
1408      * @param {function_number,function_number,function} f,a_,b_ Parent elements are a function term f(x) describing the function graph.
1409      *         <p>
1410      *         Further, an optional number or function for the left interval border a,
1411      *         and an optional number or function for the right interval border b.
1412      *         <p>
1413      *         Default values are a=-10 and b=10.
1414      * @see JXG.Curve
1415      * @example
1416      * // Create a function graph for f(x) = 0.5*x*x-2*x
1417      *   var graph = board.create('functiongraph',
1418      *                        [function(x){ return 0.5*x*x-2*x;}, -2, 4]
1419      *                     );
1420      * </pre><div id="efd432b5-23a3-4846-ac5b-b471e668b437" style="width: 300px; height: 300px;"></div>
1421      * <script type="text/javascript">
1422      *   var alex1_board = JXG.JSXGraph.initBoard('efd432b5-23a3-4846-ac5b-b471e668b437', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false});
1423      *   var graph = alex1_board.create('functiongraph', [function(x){ return 0.5*x*x-2*x;}, -2, 4]);
1424      * </script><pre>
1425      * @example
1426      * // Create a function graph for f(x) = 0.5*x*x-2*x with variable interval
1427      *   var s = board.create('slider',[[0,4],[3,4],[-2,4,5]]);
1428      *   var graph = board.create('functiongraph',
1429      *                        [function(x){ return 0.5*x*x-2*x;},
1430      *                         -2,
1431      *                         function(){return s.Value();}]
1432      *                     );
1433      * </pre><div id="4a203a84-bde5-4371-ad56-44619690bb50" style="width: 300px; height: 300px;"></div>
1434      * <script type="text/javascript">
1435      *   var alex2_board = JXG.JSXGraph.initBoard('4a203a84-bde5-4371-ad56-44619690bb50', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false});
1436      *   var s = alex2_board.create('slider',[[0,4],[3,4],[-2,4,5]]);
1437      *   var graph = alex2_board.create('functiongraph', [function(x){ return 0.5*x*x-2*x;}, -2, function(){return s.Value();}]);
1438      * </script><pre>
1439      */
1440     JXG.createFunctiongraph = function (board, parents, attributes) {
1441         var attr,
1442             par = ['x', 'x'].concat(parents);
1443 
1444         attr = Type.copyAttributes(attributes, board.options, 'curve');
1445         attr.curvetype = 'functiongraph';
1446         return new JXG.Curve(board, par, attr);
1447     };
1448 
1449     JXG.registerElement('functiongraph', JXG.createFunctiongraph);
1450     JXG.registerElement('plot', JXG.createFunctiongraph);
1451 
1452     /**
1453      * TODO
1454      * Create a dynamic spline interpolated curve given by sample points p_1 to p_n.
1455      * @param {JXG.Board} board Reference to the board the spline is drawn on.
1456      * @param {Array} parents Array of points the spline interpolates
1457      * @param {Object} attributes Define color, width, ... of the spline
1458      * @returns {JXG.Curve} Returns reference to an object of type JXG.Curve.
1459      */
1460     JXG.createSpline = function (board, parents, attributes) {
1461         var f;
1462 
1463         f = function () {
1464             var D, x = [], y = [];
1465 
1466             return function (t, suspended) {
1467                 var i, j;
1468 
1469                 if (!suspended) {
1470                     x = [];
1471                     y = [];
1472 
1473                     // given as [x[], y[]]
1474                     if (parents.length === 2 && Type.isArray(parents[0]) && Type.isArray(parents[1]) && parents[0].length === parents[1].length) {
1475                         for (i = 0; i < parents[0].length; i++) {
1476                             if (typeof parents[0][i] === 'function') {
1477                                 x.push(parents[0][i]());
1478                             } else {
1479                                 x.push(parents[0][i]);
1480                             }
1481 
1482                             if (typeof parents[1][i] === 'function') {
1483                                 y.push(parents[1][i]());
1484                             } else {
1485                                 y.push(parents[1][i]);
1486                             }
1487                         }
1488                     } else {
1489                         for (i = 0; i < parents.length; i++) {
1490                             if (Type.isPoint(parents[i])) {
1491                                 x.push(parents[i].X());
1492                                 y.push(parents[i].Y());
1493                             // given as [[x1,y1], [x2, y2], ...]
1494                             } else if (Type.isArray(parents[i]) && parents[i].length === 2) {
1495                                 for (i = 0; i < parents.length; i++) {
1496                                     if (typeof parents[i][0] === 'function') {
1497                                         x.push(parents[i][0]());
1498                                     } else {
1499                                         x.push(parents[i][0]);
1500                                     }
1501 
1502                                     if (typeof parents[i][1] === 'function') {
1503                                         y.push(parents[i][1]());
1504                                     } else {
1505                                         y.push(parents[i][1]);
1506                                     }
1507                                 }
1508                             }
1509                         }
1510                     }
1511 
1512                     // The array D has only to be calculated when the position of one or more sample point
1513                     // changes. otherwise D is always the same for all points on the spline.
1514                     D = Numerics.splineDef(x, y);
1515                 }
1516                 return Numerics.splineEval(t, x, y, D);
1517             };
1518         };
1519         return board.create('curve', ["x", f()], attributes);
1520     };
1521 
1522     /**
1523      * Register the element type spline at JSXGraph
1524      * @private
1525      */
1526     JXG.registerElement('spline', JXG.createSpline);
1527 
1528     /**
1529      * @class This element is used to provide a constructor for Riemann sums, which is realized as a special curve.
1530      * The returned element has the method Value() which returns the sum of the areas of the rectangles.
1531      * @pseudo
1532      * @description
1533      * @name Riemannsum
1534      * @augments JXG.Curve
1535      * @constructor
1536      * @type JXG.Curve
1537      * @param {function_number,function_string,function_function,number_function,number} f,n,type_,a_,b_ Parent elements of Riemannsum are a
1538      *         function term f(x) describing the function graph which is filled by the Riemann rectangles.
1539      *         <p>
1540      *         n determines the number of rectangles, it is either a fixed number or a function.
1541      *         <p>
1542      *         type is a string or function returning one of the values:  'left', 'right', 'middle', 'lower', 'upper', 'random', 'simpson', or 'trapezodial'.
1543      *         Default value is 'left'.
1544      *         <p>
1545      *         Further parameters are an optional number or function for the left interval border a,
1546      *         and an optional number or function for the right interval border b.
1547      *         <p>
1548      *         Default values are a=-10 and b=10.
1549      * @see JXG.Curve
1550      * @example
1551      * // Create Riemann sums for f(x) = 0.5*x*x-2*x.
1552      *   var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1});
1553      *   var f = function(x) { return 0.5*x*x-2*x; };
1554      *   var r = board.create('riemannsum',
1555      *               [f, function(){return s.Value();}, 'upper', -2, 5],
1556      *               {fillOpacity:0.4}
1557      *               );
1558      *   var g = board.create('functiongraph',[f, -2, 5]);
1559      *   var t = board.create('text',[-1,-1, function(){ return 'Sum=' + r.Value().toFixed(4); }]);
1560      * </pre><div id="940f40cc-2015-420d-9191-c5d83de988cf" style="width: 300px; height: 300px;"></div>
1561      * <script type="text/javascript">
1562      *   var rs1_board = JXG.JSXGraph.initBoard('940f40cc-2015-420d-9191-c5d83de988cf', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false});
1563      *   var f = function(x) { return 0.5*x*x-2*x; };
1564      *   var s = rs1_board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1});
1565      *   var r = rs1_board.create('riemannsum', [f, function(){return s.Value();}, 'upper', -2, 5], {fillOpacity:0.4});
1566      *   var g = rs1_board.create('functiongraph', [f, -2, 5]);
1567      *   var t = board.create('text',[-1,-1, function(){ return 'Sum=' + r.Value().toFixed(4); }]);
1568      * </script><pre>
1569      */
1570     JXG.createRiemannsum = function (board, parents, attributes) {
1571         var n, type, f, par, c, attr;
1572 
1573         attr = Type.copyAttributes(attributes, board.options, 'riemannsum');
1574         attr.curvetype = 'plot';
1575 
1576         f = parents[0];
1577         n = Type.createFunction(parents[1], board, '');
1578 
1579         if (!Type.exists(n)) {
1580             throw new Error("JSXGraph: JXG.createRiemannsum: argument '2' n has to be number or function." +
1581                 "\nPossible parent types: [function,n:number|function,type,start:number|function,end:number|function]");
1582         }
1583 
1584         type = Type.createFunction(parents[2], board, '', false);
1585         if (!Type.exists(type)) {
1586             throw new Error("JSXGraph: JXG.createRiemannsum: argument 3 'type' has to be string or function." +
1587                 "\nPossible parent types: [function,n:number|function,type,start:number|function,end:number|function]");
1588         }
1589 
1590         par = [[0], [0]].concat(parents.slice(3));
1591 
1592         c = board.create('curve', par, attr);
1593 
1594         c.sum = 0.0;
1595         c.Value = function () {
1596             return this.sum;
1597         };
1598 
1599         c.updateDataArray = function () {
1600             var u = Numerics.riemann(f, n(), type(), this.minX(), this.maxX());
1601             this.dataX = u[0];
1602             this.dataY = u[1];
1603 
1604             // Update "Riemann sum"
1605             this.sum = u[2];
1606         };
1607 
1608         return c;
1609     };
1610 
1611     JXG.registerElement('riemannsum', JXG.createRiemannsum);
1612 
1613     /**
1614      * @class This element is used to provide a constructor for trace curve (simple locus curve), which is realized as a special curve.
1615      * @pseudo
1616      * @description
1617      * @name Tracecurve
1618      * @augments JXG.Curve
1619      * @constructor
1620      * @type JXG.Curve
1621      * @param {Point,Point} Parent elements of Tracecurve are a
1622      *         glider point and a point whose locus is traced.
1623      * @see JXG.Curve
1624      * @example
1625      * // Create trace curve.
1626      var c1 = board.create('circle',[[0, 0], [2, 0]]),
1627      p1 = board.create('point',[-3, 1]),
1628      g1 = board.create('glider',[2, 1, c1]),
1629      s1 = board.create('segment',[g1, p1]),
1630      p2 = board.create('midpoint',[s1]),
1631      curve = board.create('tracecurve', [g1, p2]);
1632 
1633      * </pre><div id="5749fb7d-04fc-44d2-973e-45c1951e29ad" style="width: 300px; height: 300px;"></div>
1634      * <script type="text/javascript">
1635      *   var tc1_board = JXG.JSXGraph.initBoard('5749fb7d-04fc-44d2-973e-45c1951e29ad', {boundingbox: [-4, 4, 4, -4], axis: false, showcopyright: false, shownavigation: false});
1636      *   var c1 = tc1_board.create('circle',[[0, 0], [2, 0]]),
1637      *       p1 = tc1_board.create('point',[-3, 1]),
1638      *       g1 = tc1_board.create('glider',[2, 1, c1]),
1639      *       s1 = tc1_board.create('segment',[g1, p1]),
1640      *       p2 = tc1_board.create('midpoint',[s1]),
1641      *       curve = tc1_board.create('tracecurve', [g1, p2]);
1642      * </script><pre>
1643      */
1644     JXG.createTracecurve = function (board, parents, attributes) {
1645         var c, glider, tracepoint, attr;
1646 
1647         if (parents.length !== 2) {
1648             throw new Error("JSXGraph: Can't create trace curve with given parent'" +
1649                 "\nPossible parent types: [glider, point]");
1650         }
1651 
1652         glider = board.select(parents[0]);
1653         tracepoint = board.select(parents[1]);
1654 
1655         if (glider.type !== Const.OBJECT_TYPE_GLIDER || !Type.isPoint(tracepoint)) {
1656             throw new Error("JSXGraph: Can't create trace curve with parent types '" +
1657                 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
1658                 "\nPossible parent types: [glider, point]");
1659         }
1660 
1661         attr = Type.copyAttributes(attributes, board.options, 'tracecurve');
1662         attr.curvetype = 'plot';
1663         c = board.create('curve', [[0], [0]], attr);
1664 
1665         c.updateDataArray = function () {
1666             var i, step, t, el, pEl, x, y, v, from, savetrace,
1667                 le = attr.numberpoints,
1668                 savePos = glider.position,
1669                 slideObj = glider.slideObject,
1670                 mi = slideObj.minX(),
1671                 ma = slideObj.maxX();
1672 
1673             // set step width
1674             step = (ma - mi) / le;
1675             this.dataX = [];
1676             this.dataY = [];
1677 
1678             /*
1679              * For gliders on circles and lines a closed curve is computed.
1680              * For gliders on curves the curve is not closed.
1681              */
1682             if (slideObj.elementClass !== Const.OBJECT_CLASS_CURVE) {
1683                 le++;
1684             }
1685 
1686             // Loop over all steps
1687             for (i = 0; i < le; i++) {
1688                 t = mi + i * step;
1689                 x = slideObj.X(t) / slideObj.Z(t);
1690                 y = slideObj.Y(t) / slideObj.Z(t);
1691 
1692                 // Position the glider
1693                 glider.setPositionDirectly(Const.COORDS_BY_USER, [x, y]);
1694                 from = false;
1695 
1696                 // Update all elements from the glider up to the trace element
1697                 for (el in this.board.objects) {
1698                     if (this.board.objects.hasOwnProperty(el)) {
1699                         pEl = this.board.objects[el];
1700 
1701                         if (pEl === glider) {
1702                             from = true;
1703                         }
1704 
1705                         if (from && pEl.needsRegularUpdate) {
1706                             // Save the trace mode of the element
1707                             savetrace = pEl.visProp.trace;
1708                             pEl.visProp.trace = false;
1709                             pEl.needsUpdate = true;
1710                             pEl.update(true);
1711 
1712                             // Restore the trace mode
1713                             pEl.visProp.trace = savetrace;
1714                             if (pEl === tracepoint) {
1715                                 break;
1716                             }
1717                         }
1718                     }
1719                 }
1720 
1721                 // Store the position of the trace point
1722                 this.dataX[i] = tracepoint.X();
1723                 this.dataY[i] = tracepoint.Y();
1724             }
1725 
1726             // Restore the original position of the glider
1727             glider.position = savePos;
1728             from = false;
1729 
1730             // Update all elements from the glider to the trace point
1731             for (el in this.board.objects) {
1732                 if (this.board.objects.hasOwnProperty(el)) {
1733                     pEl = this.board.objects[el];
1734                     if (pEl === glider) {
1735                         from = true;
1736                     }
1737 
1738                     if (from && pEl.needsRegularUpdate) {
1739                         savetrace = pEl.visProp.trace;
1740                         pEl.visProp.trace = false;
1741                         pEl.needsUpdate = true;
1742                         pEl.update(true);
1743                         pEl.visProp.trace = savetrace;
1744 
1745                         if (pEl === tracepoint) {
1746                             break;
1747                         }
1748                     }
1749                 }
1750             }
1751         };
1752 
1753         return c;
1754     };
1755 
1756     JXG.registerElement('tracecurve', JXG.createTracecurve);
1757 
1758     /**
1759      * @class This element is used to provide a constructor for step function, which is realized as a special curve.
1760      * 
1761      * In case the data points should be updated after creation time, they can be accessed by curve.xterm and curve.yterm.
1762      * @pseudo
1763      * @description
1764      * @name Stepfunction
1765      * @augments JXG.Curve
1766      * @constructor
1767      * @type JXG.Curve
1768      * @param {Array,Array|Function} Parent elements of Stepfunction are two arrays containing the coordinates.
1769      * @see JXG.Curve
1770      * @example
1771      * // Create step function.
1772      var curve = board.create('stepfunction', [[0,1,2,3,4,5], [1,3,0,2,2,1]]);
1773 
1774      * </pre><div id="32342ec9-ad17-4339-8a97-ff23dc34f51a" style="width: 300px; height: 300px;"></div>
1775      * <script type="text/javascript">
1776      *   var sf1_board = JXG.JSXGraph.initBoard('32342ec9-ad17-4339-8a97-ff23dc34f51a', {boundingbox: [-1, 5, 6, -2], axis: true, showcopyright: false, shownavigation: false});
1777      *   var curve = sf1_board.create('stepfunction', [[0,1,2,3,4,5], [1,3,0,2,2,1]]);
1778      * </script><pre>
1779      */
1780     JXG.createStepfunction = function (board, parents, attributes) {
1781         var c, attr;
1782         if (parents.length !== 2) {
1783             throw new Error("JSXGraph: Can't create step function with given parent'" +
1784                 "\nPossible parent types: [array, array|function]");
1785         }
1786 
1787         attr = Type.copyAttributes(attributes, board.options, 'stepfunction');
1788         c = board.create('curve', parents, attr);
1789         c.updateDataArray = function () {
1790             var i, j = 0,
1791                 len = this.xterm.length;
1792                 
1793             this.dataX = [];
1794             this.dataY = [];
1795 
1796             if (len === 0) {
1797                 return;
1798             }
1799             
1800             this.dataX[j] = this.xterm[0];
1801             this.dataY[j] = this.yterm[0];
1802             ++j;
1803             
1804             for (i = 1; i < len; ++i) {
1805                 this.dataX[j] = this.xterm[i];
1806                 this.dataY[j] = this.dataY[j - 1];
1807                 ++j;
1808                 this.dataX[j] = this.xterm[i];
1809                 this.dataY[j] = this.yterm[i];
1810                 ++j;
1811             }
1812         };
1813         
1814         return c;
1815     };
1816 
1817     JXG.registerElement('stepfunction', JXG.createStepfunction);
1818 
1819     return {
1820         Curve: JXG.Curve,
1821         createCurve: JXG.createCurve,
1822         createFunctiongraph: JXG.createFunctiongraph,
1823         createPlot: JXG.createPlot,
1824         createSpline: JXG.createSpline,
1825         createRiemannsum: JXG.createRiemannsum,
1826         createTracecurve: JXG.createTracecurve,
1827         createStepfunction: JXG.createStepfunction
1828     };
1829 });
1830