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