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