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