      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