1 /* 2 Copyright 2008-2024 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Alfred Wassermann 7 8 This file is part of JSXGraph. 9 10 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 11 12 You can redistribute it and/or modify it under the terms of the 13 14 * GNU Lesser General Public License as published by 15 the Free Software Foundation, either version 3 of the License, or 16 (at your option) any later version 17 OR 18 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 19 20 JSXGraph is distributed in the hope that it will be useful, 21 but WITHOUT ANY WARRANTY; without even the implied warranty of 22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 GNU Lesser General Public License for more details. 24 25 You should have received a copy of the GNU Lesser General Public License and 26 the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/> 27 and <https://opensource.org/licenses/MIT/>. 28 */ 29 30 /*global JXG: true, define: true*/ 31 /*jslint nomen: true, plusplus: true*/ 32 33 import JXG from "../jxg.js"; 34 import Const from "../base/constants.js"; 35 import Coords from "../base/coords.js"; 36 import Mat from "./math.js"; 37 import Extrapolate from "./extrapolate.js"; 38 import Numerics from "./numerics.js"; 39 import Statistics from "./statistics.js"; 40 import Geometry from "./geometry.js"; 41 import IntervalArithmetic from "./ia.js"; 42 import Type from "../utils/type.js"; 43 44 /** 45 * Functions for plotting of curves. 46 * @name JXG.Math.Plot 47 * @exports Mat.Plot as JXG.Math.Plot 48 * @namespace 49 */ 50 Mat.Plot = { 51 /** 52 * Check if at least one point on the curve is finite and real. 53 **/ 54 checkReal: function (points) { 55 var b = false, 56 i, 57 p, 58 len = points.length; 59 60 for (i = 0; i < len; i++) { 61 p = points[i].usrCoords; 62 if (!isNaN(p[1]) && !isNaN(p[2]) && Math.abs(p[0]) > Mat.eps) { 63 b = true; 64 break; 65 } 66 } 67 return b; 68 }, 69 70 //---------------------------------------------------------------------- 71 // Plot algorithm v0 72 //---------------------------------------------------------------------- 73 /** 74 * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#doadvancedplot} is <tt>false</tt>. 75 * @param {JXG.Curve} curve JSXGraph curve element 76 * @param {Number} mi Left bound of curve 77 * @param {Number} ma Right bound of curve 78 * @param {Number} len Number of data points 79 * @returns {JXG.Curve} Reference to the curve object. 80 */ 81 updateParametricCurveNaive: function (curve, mi, ma, len) { 82 var i, 83 t, 84 suspendUpdate = false, 85 stepSize = (ma - mi) / len; 86 87 for (i = 0; i < len; i++) { 88 t = mi + i * stepSize; 89 // The last parameter prevents rounding in usr2screen(). 90 curve.points[i].setCoordinates( 91 Const.COORDS_BY_USER, 92 [curve.X(t, suspendUpdate), curve.Y(t, suspendUpdate)], 93 false 94 ); 95 curve.points[i]._t = t; 96 suspendUpdate = true; 97 } 98 return curve; 99 }, 100 101 //---------------------------------------------------------------------- 102 // Plot algorithm v1 103 //---------------------------------------------------------------------- 104 /** 105 * Crude and cheap test if the segment defined by the two points <tt>(x0, y0)</tt> and <tt>(x1, y1)</tt> is 106 * outside the viewport of the board. All parameters have to be given in screen coordinates. 107 * 108 * @private 109 * @deprecated 110 * @param {Number} x0 111 * @param {Number} y0 112 * @param {Number} x1 113 * @param {Number} y1 114 * @param {JXG.Board} board 115 * @returns {Boolean} <tt>true</tt> if the given segment is outside the visible area. 116 */ 117 isSegmentOutside: function (x0, y0, x1, y1, board) { 118 return ( 119 (y0 < 0 && y1 < 0) || 120 (y0 > board.canvasHeight && y1 > board.canvasHeight) || 121 (x0 < 0 && x1 < 0) || 122 (x0 > board.canvasWidth && x1 > board.canvasWidth) 123 ); 124 }, 125 126 /** 127 * Compares the absolute value of <tt>dx</tt> with <tt>MAXX</tt> and the absolute value of <tt>dy</tt> 128 * with <tt>MAXY</tt>. 129 * 130 * @private 131 * @deprecated 132 * @param {Number} dx 133 * @param {Number} dy 134 * @param {Number} MAXX 135 * @param {Number} MAXY 136 * @returns {Boolean} <tt>true</tt>, if <tt>|dx| < MAXX</tt> and <tt>|dy| < MAXY</tt>. 137 */ 138 isDistOK: function (dx, dy, MAXX, MAXY) { 139 return Math.abs(dx) < MAXX && Math.abs(dy) < MAXY && !isNaN(dx + dy); 140 }, 141 142 /** 143 * @private 144 * @deprecated 145 */ 146 isSegmentDefined: function (x0, y0, x1, y1) { 147 return !(isNaN(x0 + y0) && isNaN(x1 + y1)); 148 }, 149 150 /** 151 * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#doadvancedplot} is <tt>true</tt>. 152 * Since 0.99 this algorithm is deprecated. It still can be used if {@link JXG.Curve#doadvancedplotold} is <tt>true</tt>. 153 * 154 * @deprecated 155 * @param {JXG.Curve} curve JSXGraph curve element 156 * @param {Number} mi Left bound of curve 157 * @param {Number} ma Right bound of curve 158 * @returns {JXG.Curve} Reference to the curve object. 159 */ 160 updateParametricCurveOld: function (curve, mi, ma) { 161 var i, t, d, x, y, 162 x0, y0,// t0, 163 top, 164 depth, 165 MAX_DEPTH, 166 MAX_XDIST, 167 MAX_YDIST, 168 suspendUpdate = false, 169 po = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false), 170 dyadicStack = [], 171 depthStack = [], 172 pointStack = [], 173 divisors = [], 174 distOK = false, 175 j = 0, 176 distFromLine = function (p1, p2, p0) { 177 var lbda, 178 x0 = p0[1] - p1[1], 179 y0 = p0[2] - p1[2], 180 x1 = p2[0] - p1[1], 181 y1 = p2[1] - p1[2], 182 den = x1 * x1 + y1 * y1; 183 184 if (den >= Mat.eps) { 185 lbda = (x0 * x1 + y0 * y1) / den; 186 if (lbda > 0) { 187 if (lbda <= 1) { 188 x0 -= lbda * x1; 189 y0 -= lbda * y1; 190 // lbda = 1.0; 191 } else { 192 x0 -= x1; 193 y0 -= y1; 194 } 195 } 196 } 197 return Mat.hypot(x0, y0); 198 }; 199 200 JXG.deprecated("Curve.updateParametricCurveOld()"); 201 202 if (curve.board.updateQuality === curve.board.BOARD_QUALITY_LOW) { 203 MAX_DEPTH = 15; 204 MAX_XDIST = 10; // 10 205 MAX_YDIST = 10; // 10 206 } else { 207 MAX_DEPTH = 21; 208 MAX_XDIST = 0.7; // 0.7 209 MAX_YDIST = 0.7; // 0.7 210 } 211 212 divisors[0] = ma - mi; 213 for (i = 1; i < MAX_DEPTH; i++) { 214 divisors[i] = divisors[i - 1] * 0.5; 215 } 216 217 i = 1; 218 dyadicStack[0] = 1; 219 depthStack[0] = 0; 220 221 t = mi; 222 po.setCoordinates( 223 Const.COORDS_BY_USER, 224 [curve.X(t, suspendUpdate), curve.Y(t, suspendUpdate)], 225 false 226 ); 227 228 // Now, there was a first call to the functions defining the curve. 229 // Defining elements like sliders have been evaluated. 230 // Therefore, we can set suspendUpdate to false, so that these defining elements 231 // need not be evaluated anymore for the rest of the plotting. 232 suspendUpdate = true; 233 x0 = po.scrCoords[1]; 234 y0 = po.scrCoords[2]; 235 // t0 = t; 236 237 t = ma; 238 po.setCoordinates( 239 Const.COORDS_BY_USER, 240 [curve.X(t, suspendUpdate), curve.Y(t, suspendUpdate)], 241 false 242 ); 243 x = po.scrCoords[1]; 244 y = po.scrCoords[2]; 245 246 pointStack[0] = [x, y]; 247 248 top = 1; 249 depth = 0; 250 251 curve.points = []; 252 curve.points[j++] = new Coords(Const.COORDS_BY_SCREEN, [x0, y0], curve.board, false); 253 254 do { 255 distOK = 256 this.isDistOK(x - x0, y - y0, MAX_XDIST, MAX_YDIST) || 257 this.isSegmentOutside(x0, y0, x, y, curve.board); 258 while ( 259 depth < MAX_DEPTH && 260 (!distOK || depth < 6) && 261 (depth <= 7 || this.isSegmentDefined(x0, y0, x, y)) 262 ) { 263 // We jump out of the loop if 264 // * depth>=MAX_DEPTH or 265 // * (depth>=6 and distOK) or 266 // * (depth>7 and segment is not defined) 267 268 dyadicStack[top] = i; 269 depthStack[top] = depth; 270 pointStack[top] = [x, y]; 271 top += 1; 272 273 i = 2 * i - 1; 274 // Here, depth is increased and may reach MAX_DEPTH 275 depth++; 276 // In that case, t is undefined and we will see a jump in the curve. 277 t = mi + i * divisors[depth]; 278 279 po.setCoordinates( 280 Const.COORDS_BY_USER, 281 [curve.X(t, suspendUpdate), curve.Y(t, suspendUpdate)], 282 false, 283 true 284 ); 285 x = po.scrCoords[1]; 286 y = po.scrCoords[2]; 287 distOK = 288 this.isDistOK(x - x0, y - y0, MAX_XDIST, MAX_YDIST) || 289 this.isSegmentOutside(x0, y0, x, y, curve.board); 290 } 291 292 if (j > 1) { 293 d = distFromLine( 294 curve.points[j - 2].scrCoords, 295 [x, y], 296 curve.points[j - 1].scrCoords 297 ); 298 if (d < 0.015) { 299 j -= 1; 300 } 301 } 302 303 curve.points[j] = new Coords(Const.COORDS_BY_SCREEN, [x, y], curve.board, false); 304 curve.points[j]._t = t; 305 j += 1; 306 307 x0 = x; 308 y0 = y; 309 // t0 = t; 310 311 top -= 1; 312 x = pointStack[top][0]; 313 y = pointStack[top][1]; 314 depth = depthStack[top] + 1; 315 i = dyadicStack[top] * 2; 316 } while (top > 0 && j < 500000); 317 318 curve.numberPoints = curve.points.length; 319 320 return curve; 321 }, 322 323 //---------------------------------------------------------------------- 324 // Plot algorithm v2 325 //---------------------------------------------------------------------- 326 327 /** 328 * Add a point to the curve plot. If the new point is too close to the previously inserted point, 329 * it is skipped. 330 * Used in {@link JXG.Curve._plotRecursive}. 331 * 332 * @private 333 * @param {JXG.Coords} pnt Coords to add to the list of points 334 */ 335 _insertPoint_v2: function (curve, pnt, t) { 336 var lastReal = !isNaN(this._lastCrds[1] + this._lastCrds[2]), // The last point was real 337 newReal = !isNaN(pnt.scrCoords[1] + pnt.scrCoords[2]), // New point is real point 338 cw = curve.board.canvasWidth, 339 ch = curve.board.canvasHeight, 340 off = 500; 341 342 newReal = 343 newReal && 344 pnt.scrCoords[1] > -off && 345 pnt.scrCoords[2] > -off && 346 pnt.scrCoords[1] < cw + off && 347 pnt.scrCoords[2] < ch + off; 348 349 /* 350 * Prevents two consecutive NaNs or points wich are too close 351 */ 352 if ( 353 (!newReal && lastReal) || 354 (newReal && 355 (!lastReal || 356 Math.abs(pnt.scrCoords[1] - this._lastCrds[1]) > 0.7 || 357 Math.abs(pnt.scrCoords[2] - this._lastCrds[2]) > 0.7)) 358 ) { 359 pnt._t = t; 360 curve.points.push(pnt); 361 this._lastCrds = pnt.copy("scrCoords"); 362 } 363 }, 364 365 /** 366 * Check if there is a single NaN function value at t0. 367 * @param {*} curve 368 * @param {*} t0 369 * @returns {Boolean} true if there is a second NaN point close by, false otherwise 370 */ 371 neighborhood_isNaN_v2: function (curve, t0) { 372 var is_undef, 373 pnt = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false), 374 t, 375 p; 376 377 t = t0 + Mat.eps; 378 pnt.setCoordinates(Const.COORDS_BY_USER, [curve.X(t, true), curve.Y(t, true)], false); 379 p = pnt.usrCoords; 380 is_undef = isNaN(p[1] + p[2]); 381 if (!is_undef) { 382 t = t0 - Mat.eps; 383 pnt.setCoordinates( 384 Const.COORDS_BY_USER, 385 [curve.X(t, true), curve.Y(t, true)], 386 false 387 ); 388 p = pnt.usrCoords; 389 is_undef = isNaN(p[1] + p[2]); 390 if (!is_undef) { 391 return false; 392 } 393 } 394 return true; 395 }, 396 397 /** 398 * Investigate a function term at the bounds of intervals where 399 * the function is not defined, e.g. log(x) at x = 0. 400 * 401 * c is between a and b 402 * @private 403 * @param {JXG.Curve} curve JSXGraph curve element 404 * @param {Array} a Screen coordinates of the left interval bound 405 * @param {Array} b Screen coordinates of the right interval bound 406 * @param {Array} c Screen coordinates of the bisection point at (ta + tb) / 2 407 * @param {Number} ta Parameter which evaluates to a, i.e. [1, X(ta), Y(ta)] = a in screen coordinates 408 * @param {Number} tb Parameter which evaluates to b, i.e. [1, X(tb), Y(tb)] = b in screen coordinates 409 * @param {Number} tc (ta + tb) / 2 = tc. Parameter which evaluates to b, i.e. [1, X(tc), Y(tc)] = c in screen coordinates 410 * @param {Number} depth Actual recursion depth. The recursion stops if depth is equal to 0. 411 * @returns {JXG.Boolean} true if the point is inserted and the recursion should stop, false otherwise. 412 */ 413 _borderCase: function (curve, a, b, c, ta, tb, tc, depth) { 414 var t, pnt, p, 415 p_good = null, 416 j, 417 max_it = 30, 418 is_undef = false, 419 t_nan, t_real;// t_real2; 420 // dx, dy, 421 // vx, vy, vx2, vy2; 422 // asymptote; 423 424 if (depth <= 1) { 425 pnt = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false); 426 // Test if there is a single undefined point. 427 // If yes, we ignore it. 428 if ( 429 isNaN(a[1] + a[2]) && 430 !isNaN(c[1] + c[2]) && 431 !this.neighborhood_isNaN_v2(curve, ta) 432 ) { 433 return false; 434 } 435 if ( 436 isNaN(b[1] + b[2]) && 437 !isNaN(c[1] + c[2]) && 438 !this.neighborhood_isNaN_v2(curve, tb) 439 ) { 440 return false; 441 } 442 if ( 443 isNaN(c[1] + c[2]) && 444 (!isNaN(a[1] + a[2]) || !isNaN(b[1] + b[2])) && 445 !this.neighborhood_isNaN_v2(curve, tc) 446 ) { 447 return false; 448 } 449 450 j = 0; 451 // Bisect a, b and c until the point t_real is inside of the definition interval 452 // and as close as possible at the boundary. 453 // t_real2 is the second closest point. 454 do { 455 // There are four cases: 456 // a | c | b 457 // --------------- 458 // inf | R | R 459 // R | R | inf 460 // inf | inf | R 461 // R | inf | inf 462 // 463 if (isNaN(a[1] + a[2]) && !isNaN(c[1] + c[2])) { 464 t_nan = ta; 465 t_real = tc; 466 // t_real2 = tb; 467 } else if (isNaN(b[1] + b[2]) && !isNaN(c[1] + c[2])) { 468 t_nan = tb; 469 t_real = tc; 470 // t_real2 = ta; 471 } else if (isNaN(c[1] + c[2]) && !isNaN(b[1] + b[2])) { 472 t_nan = tc; 473 t_real = tb; 474 // t_real2 = tb + (tb - tc); 475 } else if (isNaN(c[1] + c[2]) && !isNaN(a[1] + a[2])) { 476 t_nan = tc; 477 t_real = ta; 478 // t_real2 = ta - (tc - ta); 479 } else { 480 return false; 481 } 482 t = 0.5 * (t_nan + t_real); 483 pnt.setCoordinates( 484 Const.COORDS_BY_USER, 485 [curve.X(t, true), curve.Y(t, true)], 486 false 487 ); 488 p = pnt.usrCoords; 489 490 is_undef = isNaN(p[1] + p[2]); 491 if (is_undef) { 492 t_nan = t; 493 } else { 494 // t_real2 = t_real; 495 t_real = t; 496 } 497 ++j; 498 } while (is_undef && j < max_it); 499 500 // If bisection was successful, take this point. 501 // Useful only for general curves, for function graph 502 // the code below overwrite p_good from here. 503 if (j < max_it) { 504 p_good = p.slice(); 505 c = p.slice(); 506 t_real = t; 507 } 508 509 // OK, bisection has been done now. 510 // t_real contains the closest inner point to the border of the interval we could find. 511 // t_real2 is the second nearest point to this boundary. 512 // Now we approximate the derivative by computing the slope of the line through these two points 513 // and test if it is "infinite", i.e larger than 400 in absolute values. 514 // 515 // vx = curve.X(t_real, true); 516 // vx2 = curve.X(t_real2, true); 517 // vy = curve.Y(t_real, true); 518 // vy2 = curve.Y(t_real2, true); 519 // dx = (vx - vx2) / (t_real - t_real2); 520 // dy = (vy - vy2) / (t_real - t_real2); 521 522 if (p_good !== null) { 523 this._insertPoint_v2( 524 curve, 525 new Coords(Const.COORDS_BY_USER, p_good, curve.board, false) 526 ); 527 return true; 528 } 529 } 530 return false; 531 }, 532 533 /** 534 * Recursive interval bisection algorithm for curve plotting. 535 * Used in {@link JXG.Curve.updateParametricCurve}. 536 * @private 537 * @deprecated 538 * @param {JXG.Curve} curve JSXGraph curve element 539 * @param {Array} a Screen coordinates of the left interval bound 540 * @param {Number} ta Parameter which evaluates to a, i.e. [1, X(ta), Y(ta)] = a in screen coordinates 541 * @param {Array} b Screen coordinates of the right interval bound 542 * @param {Number} tb Parameter which evaluates to b, i.e. [1, X(tb), Y(tb)] = b in screen coordinates 543 * @param {Number} depth Actual recursion depth. The recursion stops if depth is equal to 0. 544 * @param {Number} delta If the distance of the bisection point at (ta + tb) / 2 from the point (a + b) / 2 is less then delta, 545 * the segment [a,b] is regarded as straight line. 546 * @returns {JXG.Curve} Reference to the curve object. 547 */ 548 _plotRecursive_v2: function (curve, a, ta, b, tb, depth, delta) { 549 var tc, 550 c, 551 ds, 552 mindepth = 0, 553 isSmooth, 554 isJump, 555 isCusp, 556 cusp_threshold = 0.5, 557 jump_threshold = 0.99, 558 pnt = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false); 559 560 if (curve.numberPoints > 65536) { 561 return; 562 } 563 564 // Test if the function is undefined in an interval 565 if (depth < this.nanLevel && this._isUndefined(curve, a, ta, b, tb)) { 566 return this; 567 } 568 569 if (depth < this.nanLevel && this._isOutside(a, ta, b, tb, curve.board)) { 570 return this; 571 } 572 573 tc = (ta + tb) * 0.5; 574 pnt.setCoordinates(Const.COORDS_BY_USER, [curve.X(tc, true), curve.Y(tc, true)], false); 575 c = pnt.scrCoords; 576 577 if (this._borderCase(curve, a, b, c, ta, tb, tc, depth)) { 578 return this; 579 } 580 581 ds = this._triangleDists(a, b, c); // returns [d_ab, d_ac, d_cb, d_cd] 582 583 isSmooth = depth < this.smoothLevel && ds[3] < delta; 584 585 isJump = 586 ( 587 depth <= this.jumpLevel && (isNaN(ds[0]) || isNaN(ds[1]) || isNaN(ds[2])) 588 ) || ( 589 depth < this.jumpLevel && 590 ( 591 ds[2] > jump_threshold * ds[0] || 592 ds[1] > jump_threshold * ds[0] || 593 ds[0] === Infinity || 594 ds[1] === Infinity || 595 ds[2] === Infinity 596 ) 597 ); 598 599 isCusp = depth < this.smoothLevel + 2 && ds[0] < cusp_threshold * (ds[1] + ds[2]); 600 601 if (isCusp) { 602 mindepth = 0; 603 isSmooth = false; 604 } 605 606 --depth; 607 608 if (isJump) { 609 this._insertPoint_v2( 610 curve, 611 new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], curve.board, false), 612 tc 613 ); 614 } else if (depth <= mindepth || isSmooth) { 615 this._insertPoint_v2(curve, pnt, tc); 616 //if (this._borderCase(a, b, c, ta, tb, tc, depth)) {} 617 } else { 618 this._plotRecursive_v2(curve, a, ta, c, tc, depth, delta); 619 620 if (!isNaN(pnt.scrCoords[1] + pnt.scrCoords[2])) { 621 this._insertPoint_v2(curve, pnt, tc); 622 } 623 624 this._plotRecursive_v2(curve, c, tc, b, tb, depth, delta); 625 } 626 627 return this; 628 }, 629 630 /** 631 * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#plotVersion} is <tt>3</tt>. 632 * 633 * @param {JXG.Curve} curve JSXGraph curve element 634 * @param {Number} mi Left bound of curve 635 * @param {Number} ma Right bound of curve 636 * @returns {JXG.Curve} Reference to the curve object. 637 */ 638 updateParametricCurve_v2: function (curve, mi, ma) { 639 var ta, tb, 640 a, b, 641 suspendUpdate = false, 642 pa = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false), 643 pb = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false), 644 depth, 645 delta, 646 w2, 647 // h2, 648 bbox, ret_arr; 649 650 //console.time("plot"); 651 if (curve.board.updateQuality === curve.board.BOARD_QUALITY_LOW) { 652 depth = curve.evalVisProp('recursiondepthlow') || 13; 653 delta = 2; 654 // this.smoothLevel = 5; //depth - 7; 655 this.smoothLevel = depth - 6; 656 this.jumpLevel = 3; 657 } else { 658 depth = curve.evalVisProp('recursiondepthhigh') || 17; 659 delta = 2; 660 // smoothLevel has to be small for graphs in a huge interval. 661 // this.smoothLevel = 3; //depth - 7; // 9 662 this.smoothLevel = depth - 9; // 9 663 this.jumpLevel = 2; 664 } 665 this.nanLevel = depth - 4; 666 667 curve.points = []; 668 669 if (this.xterm === "x") { 670 // For function graphs we can restrict the plot interval 671 // to the visible area + plus margin 672 bbox = curve.board.getBoundingBox(); 673 w2 = (bbox[2] - bbox[0]) * 0.3; 674 // h2 = (bbox[1] - bbox[3]) * 0.3; 675 ta = Math.max(mi, bbox[0] - w2); 676 tb = Math.min(ma, bbox[2] + w2); 677 } else { 678 ta = mi; 679 tb = ma; 680 } 681 pa.setCoordinates( 682 Const.COORDS_BY_USER, 683 [curve.X(ta, suspendUpdate), curve.Y(ta, suspendUpdate)], 684 false 685 ); 686 687 // The first function calls of X() and Y() are done. We can now 688 // switch `suspendUpdate` on. If supported by the functions, this 689 // avoids for the rest of the plotting algorithm, evaluation of any 690 // parent elements. 691 suspendUpdate = true; 692 693 pb.setCoordinates( 694 Const.COORDS_BY_USER, 695 [curve.X(tb, suspendUpdate), curve.Y(tb, suspendUpdate)], 696 false 697 ); 698 699 // Find start and end points of the visible area (plus a certain margin) 700 ret_arr = this._findStartPoint(curve, pa.scrCoords, ta, pb.scrCoords, tb); 701 pa.setCoordinates(Const.COORDS_BY_SCREEN, ret_arr[0], false); 702 ta = ret_arr[1]; 703 ret_arr = this._findStartPoint(curve, pb.scrCoords, tb, pa.scrCoords, ta); 704 pb.setCoordinates(Const.COORDS_BY_SCREEN, ret_arr[0], false); 705 tb = ret_arr[1]; 706 707 // Save the visible area. 708 // This can be used in Curve.hasPoint(). 709 this._visibleArea = [ta, tb]; 710 711 // Start recursive plotting algorithm 712 a = pa.copy("scrCoords"); 713 b = pb.copy("scrCoords"); 714 pa._t = ta; 715 curve.points.push(pa); 716 this._lastCrds = pa.copy("scrCoords"); // Used in _insertPoint 717 this._plotRecursive_v2(curve, a, ta, b, tb, depth, delta); 718 pb._t = tb; 719 curve.points.push(pb); 720 721 curve.numberPoints = curve.points.length; 722 //console.timeEnd("plot"); 723 724 return curve; 725 }, 726 727 //---------------------------------------------------------------------- 728 // Plot algorithm v3 729 //---------------------------------------------------------------------- 730 /** 731 * 732 * @param {JXG.Curve} curve JSXGraph curve element 733 * @param {*} pnt 734 * @param {*} t 735 * @param {*} depth 736 * @param {*} limes 737 * @private 738 */ 739 _insertLimesPoint: function (curve, pnt, t, depth, limes) { 740 var p0, p1, p2; 741 742 // Ignore jump point if it follows limes 743 if ( 744 (Math.abs(this._lastUsrCrds[1]) === Infinity && 745 Math.abs(limes.left_x) === Infinity) || 746 (Math.abs(this._lastUsrCrds[2]) === Infinity && Math.abs(limes.left_y) === Infinity) 747 ) { 748 // console.log("SKIP:", pnt.usrCoords, this._lastUsrCrds, limes); 749 return; 750 } 751 752 // // Ignore jump left from limes 753 // if (Math.abs(limes.left_x) > 100 * Math.abs(this._lastUsrCrds[1])) { 754 // x = Math.sign(limes.left_x) * Infinity; 755 // } else { 756 // x = limes.left_x; 757 // } 758 // if (Math.abs(limes.left_y) > 100 * Math.abs(this._lastUsrCrds[2])) { 759 // y = Math.sign(limes.left_y) * Infinity; 760 // } else { 761 // y = limes.left_y; 762 // } 763 // //pnt.setCoordinates(Const.COORDS_BY_USER, [x, y], false); 764 765 // Add points at a jump. pnt contains [NaN, NaN] 766 //console.log("Add", t, pnt.usrCoords, limes, depth) 767 p0 = new Coords(Const.COORDS_BY_USER, [limes.left_x, limes.left_y], curve.board); 768 p0._t = t; 769 curve.points.push(p0); 770 771 if ( 772 !isNaN(limes.left_x) && 773 !isNaN(limes.left_y) && 774 !isNaN(limes.right_x) && 775 !isNaN(limes.right_y) && 776 (Math.abs(limes.left_x - limes.right_x) > Mat.eps || 777 Math.abs(limes.left_y - limes.right_y) > Mat.eps) 778 ) { 779 p1 = new Coords(Const.COORDS_BY_SCREEN, pnt, curve.board); 780 p1._t = t; 781 curve.points.push(p1); 782 } 783 784 p2 = new Coords(Const.COORDS_BY_USER, [limes.right_x, limes.right_y], curve.board); 785 p2._t = t; 786 curve.points.push(p2); 787 this._lastScrCrds = p2.copy("scrCoords"); 788 this._lastUsrCrds = p2.copy("usrCoords"); 789 }, 790 791 /** 792 * Add a point to the curve plot. If the new point is too close to the previously inserted point, 793 * it is skipped. 794 * Used in {@link JXG.Curve._plotRecursive}. 795 * 796 * @private 797 * @param {JXG.Curve} curve JSXGraph curve element 798 * @param {JXG.Coords} pnt Coords to add to the list of points 799 */ 800 _insertPoint: function (curve, pnt, t, depth, limes) { 801 var last_is_real = !isNaN(this._lastScrCrds[1] + this._lastScrCrds[2]), // The last point was real 802 point_is_real = !isNaN(pnt[1] + pnt[2]), // New point is real point 803 cw = curve.board.canvasWidth, 804 ch = curve.board.canvasHeight, 805 p, 806 near = 0.8, 807 off = 500; 808 809 if (Type.exists(limes)) { 810 this._insertLimesPoint(curve, pnt, t, depth, limes); 811 return; 812 } 813 814 // Check if point has real coordinates and 815 // coordinates are not too far away from canvas. 816 point_is_real = 817 point_is_real && 818 pnt[1] > -off && 819 pnt[2] > -off && 820 pnt[1] < cw + off && 821 pnt[2] < ch + off; 822 823 // Prevent two consecutive NaNs 824 if (!last_is_real && !point_is_real) { 825 return; 826 } 827 828 // Prevent two consecutive points which are too close 829 if ( 830 point_is_real && 831 last_is_real && 832 Math.abs(pnt[1] - this._lastScrCrds[1]) < near && 833 Math.abs(pnt[2] - this._lastScrCrds[2]) < near 834 ) { 835 return; 836 } 837 838 // Prevent two consecutive points at infinity (either direction) 839 if ( 840 (Math.abs(pnt[1]) === Infinity && Math.abs(this._lastUsrCrds[1]) === Infinity) || 841 (Math.abs(pnt[2]) === Infinity && Math.abs(this._lastUsrCrds[2]) === Infinity) 842 ) { 843 return; 844 } 845 846 //console.log("add", t, pnt.usrCoords, depth) 847 // Add regular point 848 p = new Coords(Const.COORDS_BY_SCREEN, pnt, curve.board); 849 p._t = t; 850 curve.points.push(p); 851 this._lastScrCrds = p.copy("scrCoords"); 852 this._lastUsrCrds = p.copy("usrCoords"); 853 }, 854 855 /** 856 * Compute distances in screen coordinates between the points ab, 857 * ac, cb, and cd, where d = (a + b)/2. 858 * cd is used for the smoothness test, ab, ac, cb are used to detect jumps, cusps and poles. 859 * 860 * @private 861 * @param {Array} a Screen coordinates of the left interval bound 862 * @param {Array} b Screen coordinates of the right interval bound 863 * @param {Array} c Screen coordinates of the bisection point at (ta + tb) / 2 864 * @returns {Array} array of distances in screen coordinates between: ab, ac, cb, and cd. 865 */ 866 _triangleDists: function (a, b, c) { 867 var d, d_ab, d_ac, d_cb, d_cd; 868 869 d = [a[0] * b[0], (a[1] + b[1]) * 0.5, (a[2] + b[2]) * 0.5]; 870 871 d_ab = Geometry.distance(a, b, 3); 872 d_ac = Geometry.distance(a, c, 3); 873 d_cb = Geometry.distance(c, b, 3); 874 d_cd = Geometry.distance(c, d, 3); 875 876 return [d_ab, d_ac, d_cb, d_cd]; 877 }, 878 879 /** 880 * Test if the function is undefined on an interval: 881 * If the interval borders a and b are undefined, 20 random values 882 * are tested if they are undefined, too. 883 * Only if all values are undefined, we declare the function to be undefined in this interval. 884 * 885 * @private 886 * @param {JXG.Curve} curve JSXGraph curve element 887 * @param {Array} a Screen coordinates of the left interval bound 888 * @param {Number} ta Parameter which evaluates to a, i.e. [1, X(ta), Y(ta)] = a in screen coordinates 889 * @param {Array} b Screen coordinates of the right interval bound 890 * @param {Number} tb Parameter which evaluates to b, i.e. [1, X(tb), Y(tb)] = b in screen coordinates 891 */ 892 _isUndefined: function (curve, a, ta, b, tb) { 893 var t, i, pnt; 894 895 if (!isNaN(a[1] + a[2]) || !isNaN(b[1] + b[2])) { 896 return false; 897 } 898 899 pnt = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false); 900 901 for (i = 0; i < 20; ++i) { 902 t = ta + Math.random() * (tb - ta); 903 pnt.setCoordinates( 904 Const.COORDS_BY_USER, 905 [curve.X(t, true), curve.Y(t, true)], 906 false 907 ); 908 if (!isNaN(pnt.scrCoords[0] + pnt.scrCoords[1] + pnt.scrCoords[2])) { 909 return false; 910 } 911 } 912 913 return true; 914 }, 915 916 /** 917 * Decide if a path segment is too far from the canvas that we do not need to draw it. 918 * @private 919 * @param {Array} a Screen coordinates of the start point of the segment 920 * @param {Array} ta Curve parameter of a (unused). 921 * @param {Array} b Screen coordinates of the end point of the segment 922 * @param {Array} tb Curve parameter of b (unused). 923 * @param {JXG.Board} board 924 * @returns {Boolean} True if the segment is too far away from the canvas, false otherwise. 925 */ 926 _isOutside: function (a, ta, b, tb, board) { 927 var off = 500, 928 cw = board.canvasWidth, 929 ch = board.canvasHeight; 930 931 return !!( 932 (a[1] < -off && b[1] < -off) || 933 (a[2] < -off && b[2] < -off) || 934 (a[1] > cw + off && b[1] > cw + off) || 935 (a[2] > ch + off && b[2] > ch + off) 936 ); 937 }, 938 939 /** 940 * Decide if a point of a curve is too far from the canvas that we do not need to draw it. 941 * @private 942 * @param {Array} a Screen coordinates of the point 943 * @param {JXG.Board} board 944 * @returns {Boolean} True if the point is too far away from the canvas, false otherwise. 945 */ 946 _isOutsidePoint: function (a, board) { 947 var off = 500, 948 cw = board.canvasWidth, 949 ch = board.canvasHeight; 950 951 return !!(a[1] < -off || a[2] < -off || a[1] > cw + off || a[2] > ch + off); 952 }, 953 954 /** 955 * For a curve c(t) defined on the interval [ta, tb] find the first point 956 * which is in the visible area of the board (plus some outside margin). 957 * <p> 958 * This method is necessary to restrict the recursive plotting algorithm 959 * {@link JXG.Curve._plotRecursive} to the visible area and not waste 960 * recursion to areas far outside of the visible area. 961 * <p> 962 * This method can also be used to find the last visible point 963 * by reversing the input parameters. 964 * 965 * @param {JXG.Curve} curve JSXGraph curve element 966 * @param {Array} ta Curve parameter of a. 967 * @param {Array} b Screen coordinates of the end point of the segment (unused) 968 * @param {Array} tb Curve parameter of b 969 * @return {Array} Array of length two containing the screen ccordinates of 970 * the starting point and the curve parameter at this point. 971 * @private 972 */ 973 _findStartPoint: function (curve, a, ta, b, tb) { 974 // The code below is too unstable. 975 // E.g. [function(t) { return Math.pow(t, 2) * (t + 5) * Math.pow(t - 5, 2); }, -8, 8] 976 // Therefore, we return here. 977 return [a, ta]; 978 979 // var i, 980 // delta, 981 // tc, 982 // td, 983 // z, 984 // isFound, 985 // w2, 986 // h2, 987 // pnt = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false), 988 // steps = 40, 989 // eps = 0.01, 990 // fnX1, 991 // fnX2, 992 // fnY1, 993 // fnY2, 994 // bbox = curve.board.getBoundingBox(); 995 996 // if (true || !this._isOutsidePoint(a, curve.board)) { 997 // return [a, ta]; 998 // } 999 // w2 = (bbox[2] - bbox[0]) * 0.3; 1000 // h2 = (bbox[1] - bbox[3]) * 0.3; 1001 // bbox[0] -= w2; 1002 // bbox[1] += h2; 1003 // bbox[2] += w2; 1004 // bbox[3] -= h2; 1005 1006 // delta = (tb - ta) / steps; 1007 // tc = ta + delta; 1008 // isFound = false; 1009 1010 // fnX1 = function (t) { 1011 // return curve.X(t, true) - bbox[0]; 1012 // }; 1013 // fnY1 = function (t) { 1014 // return curve.Y(t, true) - bbox[1]; 1015 // }; 1016 // fnX2 = function (t) { 1017 // return curve.X(t, true) - bbox[2]; 1018 // }; 1019 // fnY2 = function (t) { 1020 // return curve.Y(t, true) - bbox[3]; 1021 // }; 1022 // for (i = 0; i < steps; ++i) { 1023 // // Left border 1024 // z = bbox[0]; 1025 // td = Numerics.root(fnX1, [tc - delta, tc], curve); 1026 // // td = Numerics.fzero(fnX1, [tc - delta, tc], this); 1027 // // console.log("A", tc - delta, tc, td, Math.abs(this.X(td, true) - z)); 1028 // if (Math.abs(curve.X(td, true) - z) < eps) { 1029 // //} * Math.abs(z)) { 1030 // isFound = true; 1031 // break; 1032 // } 1033 // // Top border 1034 // z = bbox[1]; 1035 // td = Numerics.root(fnY1, [tc - delta, tc], curve); 1036 // // td = Numerics.fzero(fnY1, [tc - delta, tc], this); 1037 // // console.log("B", tc - delta, tc, td, Math.abs(this.Y(td, true) - z)); 1038 // if (Math.abs(curve.Y(td, true) - z) < eps) { 1039 // // * Math.abs(z)) { 1040 // isFound = true; 1041 // break; 1042 // } 1043 // // Right border 1044 // z = bbox[2]; 1045 // td = Numerics.root(fnX2, [tc - delta, tc], curve); 1046 // // td = Numerics.fzero(fnX2, [tc - delta, tc], this); 1047 // // console.log("C", tc - delta, tc, td, Math.abs(this.X(td, true) - z)); 1048 // if (Math.abs(curve.X(td, true) - z) < eps) { 1049 // // * Math.abs(z)) { 1050 // isFound = true; 1051 // break; 1052 // } 1053 // // Bottom border 1054 // z = bbox[3]; 1055 // td = Numerics.root(fnY2, [tc - delta, tc], curve); 1056 // // td = Numerics.fzero(fnY2, [tc - delta, tc], this); 1057 // // console.log("D", tc - delta, tc, td, Math.abs(this.Y(td, true) - z)); 1058 // if (Math.abs(curve.Y(td, true) - z) < eps) { 1059 // // * Math.abs(z)) { 1060 // isFound = true; 1061 // break; 1062 // } 1063 // tc += delta; 1064 // } 1065 // if (isFound) { 1066 // pnt.setCoordinates( 1067 // Const.COORDS_BY_USER, 1068 // [curve.X(td, true), curve.Y(td, true)], 1069 // false 1070 // ); 1071 // return [pnt.scrCoords, td]; 1072 // } 1073 // console.log("TODO _findStartPoint", curve.Y.toString(), tc); 1074 // pnt.setCoordinates(Const.COORDS_BY_USER, [curve.X(ta, true), curve.Y(ta, true)], false); 1075 // return [pnt.scrCoords, ta]; 1076 }, 1077 1078 /** 1079 * Investigate a function term at the bounds of intervals where 1080 * the function is not defined, e.g. log(x) at x = 0. 1081 * 1082 * c is inbetween a and b 1083 * 1084 * @param {JXG.Curve} curve JSXGraph curve element 1085 * @param {Array} a Screen coordinates of the left interval bound 1086 * @param {Array} b Screen coordinates of the right interval bound 1087 * @param {Array} c Screen coordinates of the bisection point at (ta + tb) / 2 1088 * @param {Number} ta Parameter which evaluates to a, i.e. [1, X(ta), Y(ta)] = a in screen coordinates 1089 * @param {Number} tb Parameter which evaluates to b, i.e. [1, X(tb), Y(tb)] = b in screen coordinates 1090 * @param {Number} tc (ta + tb) / 2 = tc. Parameter which evaluates to b, i.e. [1, X(tc), Y(tc)] = c in screen coordinates 1091 * @param {Number} depth Actual recursion depth. The recursion stops if depth is equal to 0. 1092 * @returns {JXG.Boolean} true if the point is inserted and the recursion should stop, false otherwise. 1093 * 1094 * @private 1095 */ 1096 _getBorderPos: function (curve, ta, a, tc, c, tb, b) { 1097 var t, pnt, p, j, 1098 max_it = 30, 1099 is_undef = false, 1100 t_good, t_bad; 1101 1102 pnt = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false); 1103 j = 0; 1104 // Bisect a, b and c until the point t_real is inside of the definition interval 1105 // and as close as possible at the boundary. 1106 // (t_real2 is/was the second closest point). 1107 // There are four cases: 1108 // a | c | b 1109 // --------------- 1110 // inf | R | R 1111 // R | R | inf 1112 // inf | inf | R 1113 // R | inf | inf 1114 // 1115 if (isNaN(a[1] + a[2]) && !isNaN(c[1] + c[2])) { 1116 t_bad = ta; 1117 t_good = tc; 1118 } else if (isNaN(b[1] + b[2]) && !isNaN(c[1] + c[2])) { 1119 t_bad = tb; 1120 t_good = tc; 1121 } else if (isNaN(c[1] + c[2]) && !isNaN(b[1] + b[2])) { 1122 t_bad = tc; 1123 t_good = tb; 1124 } else if (isNaN(c[1] + c[2]) && !isNaN(a[1] + a[2])) { 1125 t_bad = tc; 1126 t_good = ta; 1127 } else { 1128 return false; 1129 } 1130 do { 1131 t = 0.5 * (t_good + t_bad); 1132 pnt.setCoordinates( 1133 Const.COORDS_BY_USER, 1134 [curve.X(t, true), curve.Y(t, true)], 1135 false 1136 ); 1137 p = pnt.usrCoords; 1138 is_undef = isNaN(p[1] + p[2]); 1139 if (is_undef) { 1140 t_bad = t; 1141 } else { 1142 t_good = t; 1143 } 1144 ++j; 1145 } while (j < max_it && Math.abs(t_good - t_bad) > Mat.eps); 1146 return t; 1147 }, 1148 1149 /** 1150 * 1151 * @param {JXG.Curve} curve JSXGraph curve element 1152 * @param {Number} ta 1153 * @param {Number} tb 1154 */ 1155 _getCuspPos: function (curve, ta, tb) { 1156 var a = [curve.X(ta, true), curve.Y(ta, true)], 1157 b = [curve.X(tb, true), curve.Y(tb, true)], 1158 max_func = function (t) { 1159 var c = [curve.X(t, true), curve.Y(t, true)]; 1160 return -( 1161 Mat.hypot(a[0] - c[0], a[1] - c[1]) + 1162 Mat.hypot(b[0] - c[0], b[1] - c[1]) 1163 ); 1164 }; 1165 1166 return Numerics.fminbr(max_func, [ta, tb], curve); 1167 }, 1168 1169 /** 1170 * 1171 * @param {JXG.Curve} curve JSXGraph curve element 1172 * @param {Number} ta 1173 * @param {Number} tb 1174 */ 1175 _getJumpPos: function (curve, ta, tb) { 1176 var max_func = function (t) { 1177 var e = Mat.eps * Mat.eps, 1178 c1 = [curve.X(t, true), curve.Y(t, true)], 1179 c2 = [curve.X(t + e, true), curve.Y(t + e, true)]; 1180 return -Math.abs((c2[1] - c1[1]) / (c2[0] - c1[0])); 1181 }; 1182 1183 return Numerics.fminbr(max_func, [ta, tb], curve); 1184 }, 1185 1186 /** 1187 * 1188 * @param {JXG.Curve} curve JSXGraph curve element 1189 * @param {Number} t 1190 * @private 1191 */ 1192 _getLimits: function (curve, t) { 1193 var res, 1194 step = 2 / (curve.maxX() - curve.minX()), 1195 x_l, 1196 x_r, 1197 y_l, 1198 y_r; 1199 1200 // From left 1201 res = Extrapolate.limit(t, -step, curve.X); 1202 x_l = res[0]; 1203 if (res[1] === "infinite") { 1204 x_l = Math.sign(x_l) * Infinity; 1205 } 1206 1207 res = Extrapolate.limit(t, -step, curve.Y); 1208 y_l = res[0]; 1209 if (res[1] === "infinite") { 1210 y_l = Math.sign(y_l) * Infinity; 1211 } 1212 1213 // From right 1214 res = Extrapolate.limit(t, step, curve.X); 1215 x_r = res[0]; 1216 if (res[1] === "infinite") { 1217 x_r = Math.sign(x_r) * Infinity; 1218 } 1219 1220 res = Extrapolate.limit(t, step, curve.Y); 1221 y_r = res[0]; 1222 if (res[1] === "infinite") { 1223 y_r = Math.sign(y_r) * Infinity; 1224 } 1225 1226 return { 1227 left_x: x_l, 1228 left_y: y_l, 1229 right_x: x_r, 1230 right_y: y_r, 1231 t: t 1232 }; 1233 }, 1234 1235 /** 1236 * 1237 * @param {JXG.Curve} curve JSXGraph curve element 1238 * @param {Array} a 1239 * @param {Number} tc 1240 * @param {Array} c 1241 * @param {Number} tb 1242 * @param {Array} b 1243 * @param {String} may_be_special 1244 * @param {Number} depth 1245 * @private 1246 */ 1247 _getLimes: function (curve, ta, a, tc, c, tb, b, may_be_special, depth) { 1248 var t; 1249 1250 if (may_be_special === "border") { 1251 t = this._getBorderPos(curve, ta, a, tc, c, tb, b); 1252 } else if (may_be_special === "cusp") { 1253 t = this._getCuspPos(curve, ta, tb); 1254 } else if (may_be_special === "jump") { 1255 t = this._getJumpPos(curve, ta, tb); 1256 } 1257 return this._getLimits(curve, t); 1258 }, 1259 1260 /** 1261 * Recursive interval bisection algorithm for curve plotting. 1262 * Used in {@link JXG.Curve.updateParametricCurve}. 1263 * @private 1264 * @param {JXG.Curve} curve JSXGraph curve element 1265 * @param {Array} a Screen coordinates of the left interval bound 1266 * @param {Number} ta Parameter which evaluates to a, i.e. [1, X(ta), Y(ta)] = a in screen coordinates 1267 * @param {Array} b Screen coordinates of the right interval bound 1268 * @param {Number} tb Parameter which evaluates to b, i.e. [1, X(tb), Y(tb)] = b in screen coordinates 1269 * @param {Number} depth Actual recursion depth. The recursion stops if depth is equal to 0. 1270 * @param {Number} delta If the distance of the bisection point at (ta + tb) / 2 from the point (a + b) / 2 is less then delta, 1271 * the segment [a,b] is regarded as straight line. 1272 * @returns {JXG.Curve} Reference to the curve object. 1273 */ 1274 _plotNonRecursive: function (curve, a, ta, b, tb, d) { 1275 var tc, 1276 c, 1277 ds, 1278 mindepth = 0, 1279 limes = null, 1280 a_nan, 1281 b_nan, 1282 isSmooth = false, 1283 may_be_special = "", 1284 x, 1285 y, 1286 oc, 1287 depth, 1288 ds0, 1289 stack = [], 1290 stack_length = 0, 1291 item; 1292 1293 oc = curve.board.origin.scrCoords; 1294 stack[stack_length++] = [a, ta, b, tb, d, Infinity]; 1295 while (stack_length > 0) { 1296 // item = stack.pop(); 1297 item = stack[--stack_length]; 1298 a = item[0]; 1299 ta = item[1]; 1300 b = item[2]; 1301 tb = item[3]; 1302 depth = item[4]; 1303 ds0 = item[5]; 1304 1305 isSmooth = false; 1306 may_be_special = ""; 1307 limes = null; 1308 //console.log(stack.length, item) 1309 1310 if (curve.points.length > 65536) { 1311 return; 1312 } 1313 1314 if (depth < this.nanLevel) { 1315 // Test if the function is undefined in the whole interval [ta, tb] 1316 if (this._isUndefined(curve, a, ta, b, tb)) { 1317 continue; 1318 } 1319 // Test if the graph is far outside the visible are for the interval [ta, tb] 1320 if (this._isOutside(a, ta, b, tb, curve.board)) { 1321 continue; 1322 } 1323 } 1324 1325 tc = (ta + tb) * 0.5; 1326 1327 // Screen coordinates of point at tc 1328 x = curve.X(tc, true); 1329 y = curve.Y(tc, true); 1330 c = [1, oc[1] + x * curve.board.unitX, oc[2] - y * curve.board.unitY]; 1331 ds = this._triangleDists(a, b, c); // returns [d_ab, d_ac, d_cb, d_cd] 1332 1333 a_nan = isNaN(a[1] + a[2]); 1334 b_nan = isNaN(b[1] + b[2]); 1335 if ((a_nan && !b_nan) || (!a_nan && b_nan)) { 1336 may_be_special = "border"; 1337 } else if ( 1338 ds[0] > 0.66 * ds0 || 1339 ds[0] < this.cusp_threshold * (ds[1] + ds[2]) || 1340 ds[1] > 5 * ds[2] || 1341 ds[2] > 5 * ds[1] 1342 ) { 1343 may_be_special = "cusp"; 1344 } else if ( 1345 ds[2] > this.jump_threshold * ds[0] || 1346 ds[1] > this.jump_threshold * ds[0] || 1347 ds[0] === Infinity || 1348 ds[1] === Infinity || 1349 ds[2] === Infinity 1350 ) { 1351 may_be_special = "jump"; 1352 } 1353 isSmooth = 1354 may_be_special === "" && 1355 depth < this.smoothLevel && 1356 ds[3] < this.smooth_threshold; 1357 1358 if (depth < this.testLevel && !isSmooth) { 1359 if (may_be_special === "") { 1360 isSmooth = true; 1361 } else { 1362 limes = this._getLimes(curve, ta, a, tc, c, tb, b, may_be_special, depth); 1363 } 1364 } 1365 1366 if (limes !== null) { 1367 c = [1, NaN, NaN]; 1368 this._insertPoint(curve, c, tc, depth, limes); 1369 } else if (depth <= mindepth || isSmooth) { 1370 this._insertPoint(curve, c, tc, depth, null); 1371 } else { 1372 stack[stack_length++] = [c, tc, b, tb, depth - 1, ds[0]]; 1373 stack[stack_length++] = [a, ta, c, tc, depth - 1, ds[0]]; 1374 } 1375 } 1376 1377 return this; 1378 }, 1379 1380 /** 1381 * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#plotVersion} is <tt>3</tt>. 1382 * This is an experimental plot version, <b>not recommended</b> to be used. 1383 * @param {JXG.Curve} curve JSXGraph curve element 1384 * @param {Number} mi Left bound of curve 1385 * @param {Number} ma Right bound of curve 1386 * @returns {JXG.Curve} Reference to the curve object. 1387 */ 1388 updateParametricCurve_v3: function (curve, mi, ma) { 1389 var ta, 1390 tb, 1391 a, 1392 b, 1393 suspendUpdate = false, 1394 pa = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false), 1395 pb = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false), 1396 depth, 1397 w2, // h2, 1398 bbox, 1399 ret_arr; 1400 1401 // console.log("-----------------------------------------------------------"); 1402 // console.time("plot"); 1403 if (curve.board.updateQuality === curve.board.BOARD_QUALITY_LOW) { 1404 depth = curve.evalVisProp('recursiondepthlow') || 14; 1405 } else { 1406 depth = curve.evalVisProp('recursiondepthhigh') || 17; 1407 } 1408 1409 // smoothLevel has to be small for graphs in a huge interval. 1410 this.smoothLevel = 7; //depth - 10; 1411 this.nanLevel = depth - 4; 1412 this.testLevel = 4; 1413 this.cusp_threshold = 0.5; 1414 this.jump_threshold = 0.99; 1415 this.smooth_threshold = 2; 1416 1417 curve.points = []; 1418 1419 if (curve.xterm === "x") { 1420 // For function graphs we can restrict the plot interval 1421 // to the visible area +plus margin 1422 bbox = curve.board.getBoundingBox(); 1423 w2 = (bbox[2] - bbox[0]) * 0.3; 1424 //h2 = (bbox[1] - bbox[3]) * 0.3; 1425 ta = Math.max(mi, bbox[0] - w2); 1426 tb = Math.min(ma, bbox[2] + w2); 1427 } else { 1428 ta = mi; 1429 tb = ma; 1430 } 1431 pa.setCoordinates( 1432 Const.COORDS_BY_USER, 1433 [curve.X(ta, suspendUpdate), curve.Y(ta, suspendUpdate)], 1434 false 1435 ); 1436 1437 // The first function calls of X() and Y() are done. We can now 1438 // switch `suspendUpdate` on. If supported by the functions, this 1439 // avoids for the rest of the plotting algorithm, evaluation of any 1440 // parent elements. 1441 suspendUpdate = true; 1442 1443 pb.setCoordinates( 1444 Const.COORDS_BY_USER, 1445 [curve.X(tb, suspendUpdate), curve.Y(tb, suspendUpdate)], 1446 false 1447 ); 1448 1449 // Find start and end points of the visible area (plus a certain margin) 1450 ret_arr = this._findStartPoint(curve, pa.scrCoords, ta, pb.scrCoords, tb); 1451 pa.setCoordinates(Const.COORDS_BY_SCREEN, ret_arr[0], false); 1452 ta = ret_arr[1]; 1453 ret_arr = this._findStartPoint(curve, pb.scrCoords, tb, pa.scrCoords, ta); 1454 pb.setCoordinates(Const.COORDS_BY_SCREEN, ret_arr[0], false); 1455 tb = ret_arr[1]; 1456 1457 // Save the visible area. 1458 // This can be used in Curve.hasPoint(). 1459 this._visibleArea = [ta, tb]; 1460 1461 // Start recursive plotting algorithm 1462 a = pa.copy("scrCoords"); 1463 b = pb.copy("scrCoords"); 1464 pa._t = ta; 1465 curve.points.push(pa); 1466 this._lastScrCrds = pa.copy("scrCoords"); // Used in _insertPoint 1467 this._lastUsrCrds = pa.copy("usrCoords"); // Used in _insertPoint 1468 1469 this._plotNonRecursive(curve, a, ta, b, tb, depth); 1470 1471 pb._t = tb; 1472 curve.points.push(pb); 1473 1474 curve.numberPoints = curve.points.length; 1475 // console.timeEnd("plot"); 1476 // console.log("number of points:", this.numberPoints); 1477 1478 return curve; 1479 }, 1480 1481 //---------------------------------------------------------------------- 1482 // Plot algorithm v4 1483 //---------------------------------------------------------------------- 1484 1485 /** 1486 * TODO 1487 * @param {Array} vec 1488 * @param {Number} le 1489 * @param {Number} level 1490 * @returns Object 1491 * @private 1492 */ 1493 _criticalInterval: function (vec, le, level) { 1494 var i, 1495 j, 1496 le1, 1497 med, 1498 sgn, 1499 sgnChange, 1500 isGroup = false, 1501 abs_vec, 1502 last = -Infinity, 1503 very_small = false, 1504 smooth = false, 1505 group = 0, 1506 groups = [], 1507 types = [], 1508 positions = []; 1509 1510 abs_vec = Statistics.abs(vec); 1511 med = Statistics.median(abs_vec); 1512 1513 if (med < 1.0e-7) { 1514 med = 1.0e-7; 1515 very_small = true; 1516 } else { 1517 med *= this.criticalThreshold; 1518 } 1519 1520 //console.log("Median", med); 1521 for (i = 0; i < le; i++) { 1522 // Start a group if not yet done and 1523 // add position to group 1524 if (abs_vec[i] > med /*&& abs_vec[i] > 0.01*/) { 1525 positions.push({ i: i, v: vec[i], group: group }); 1526 last = i; 1527 if (!isGroup) { 1528 isGroup = true; 1529 } 1530 } else { 1531 if (isGroup && i > last + 4) { 1532 // End the group 1533 if (positions.length > 0) { 1534 groups.push(positions.slice(0)); 1535 } 1536 positions = []; 1537 isGroup = false; 1538 group++; 1539 } 1540 } 1541 } 1542 if (isGroup) { 1543 if (positions.length > 1) { 1544 groups.push(positions.slice(0)); 1545 } 1546 } 1547 1548 if (very_small && groups.length === 0) { 1549 smooth = true; 1550 } 1551 1552 // Decide if there is a singular critical point 1553 // or if a whole interval is problematic. 1554 // The latter is the case if the differences have many sign changes. 1555 for (j = 0; j < groups.length; j++) { 1556 types[j] = "point"; 1557 le1 = groups[j].length; 1558 if (le1 < 64) { 1559 continue; 1560 } 1561 sgnChange = 0; 1562 sgn = Math.sign(groups[j][0].v); 1563 for (i = 1; i < le1; i++) { 1564 if (Math.sign(groups[j][i].v) !== sgn) { 1565 sgnChange++; 1566 sgn = Math.sign(groups[j][i].v); 1567 } 1568 } 1569 if (sgnChange * 6 > le1) { 1570 types[j] = "interval"; 1571 } 1572 } 1573 1574 return { smooth: smooth, groups: groups, types: types }; 1575 }, 1576 1577 Component: function () { 1578 this.left_isNaN = false; 1579 this.right_isNaN = false; 1580 this.left_t = null; 1581 this.right_t = null; 1582 this.t_values = []; 1583 this.x_values = []; 1584 this.y_values = []; 1585 this.len = 0; 1586 }, 1587 1588 findComponents: function (curve, mi, ma, steps) { 1589 var i, t, h, 1590 x, y, 1591 components = [], 1592 comp, 1593 comp_nr = 0, 1594 cnt = 0, 1595 cntNaNs = 0, 1596 comp_started = false, 1597 suspended = false; 1598 1599 h = (ma - mi) / steps; 1600 components[comp_nr] = new this.Component(); 1601 comp = components[comp_nr]; 1602 1603 for (i = 0, t = mi; i <= steps; i++, t += h) { 1604 x = curve.X(t, suspended); 1605 y = curve.Y(t, suspended); 1606 1607 if (isNaN(x) || isNaN(y)) { 1608 cntNaNs++; 1609 // Wait for - at least - two consecutive NaNs 1610 // This avoids starting a new component if 1611 // the function value has infinity as intermediate value. 1612 if (cntNaNs > 1 && comp_started) { 1613 // Finalize a component 1614 comp.right_isNaN = true; 1615 comp.right_t = t - h; 1616 comp.len = cnt; 1617 1618 // Prepare a new component 1619 comp_started = false; 1620 comp_nr++; 1621 components[comp_nr] = new this.Component(); 1622 comp = components[comp_nr]; 1623 cntNaNs = 0; 1624 } 1625 } else { 1626 // Now there is a non-NaN entry. 1627 if (!comp_started) { 1628 // Start the component 1629 comp_started = true; 1630 cnt = 0; 1631 if (cntNaNs > 0) { 1632 comp.left_t = t - h; 1633 comp.left_isNaN = true; 1634 } 1635 } 1636 cntNaNs = 0; 1637 // Add the value to the component 1638 comp.t_values[cnt] = t; 1639 comp.x_values[cnt] = x; 1640 comp.y_values[cnt] = y; 1641 cnt++; 1642 } 1643 if (i === 0) { 1644 suspended = true; 1645 } 1646 } 1647 if (comp_started) { 1648 comp.len = cnt; 1649 } else { 1650 components.pop(); 1651 } 1652 1653 return components; 1654 }, 1655 1656 getPointType: function (curve, pos, t_approx, t_values, x_table, y_table, len) { 1657 var x_values = x_table[0], 1658 y_values = y_table[0], 1659 full_len = t_values.length, 1660 result = { 1661 idx: pos, 1662 t: t_approx, //t_values[pos], 1663 x: x_values[pos], 1664 y: y_values[pos], 1665 type: "other" 1666 }; 1667 1668 if (pos < 5) { 1669 result.type = "borderleft"; 1670 result.idx = 0; 1671 result.t = t_values[0]; 1672 result.x = x_values[0]; 1673 result.y = y_values[0]; 1674 1675 // console.log('Border left', result.t); 1676 return result; 1677 } 1678 if (pos > len - 6) { 1679 result.type = "borderright"; 1680 result.idx = full_len - 1; 1681 result.t = t_values[full_len - 1]; 1682 result.x = x_values[full_len - 1]; 1683 result.y = y_values[full_len - 1]; 1684 1685 // console.log('Border right', result.t, full_len - 1); 1686 return result; 1687 } 1688 1689 return result; 1690 }, 1691 1692 newtonApprox: function (idx, t, h, level, table) { 1693 var i, 1694 s = 0.0; 1695 for (i = level; i > 0; i--) { 1696 s = ((s + table[i][idx]) * (t - (i - 1) * h)) / i; 1697 } 1698 return s + table[0][idx]; 1699 }, 1700 1701 // Thiele's interpolation formula, 1702 // https://en.wikipedia.org/wiki/Thiele%27s_interpolation_formula 1703 // unused 1704 thiele: function (t, recip, t_values, idx, degree) { 1705 var i, 1706 v = 0.0; 1707 for (i = degree; i > 1; i--) { 1708 v = (t - t_values[idx + i]) / (recip[i][idx + 1] - recip[i - 2][idx + 1] + v); 1709 } 1710 return recip[0][idx + 1] + (t - t_values[idx + 1]) / (recip[1][idx + 1] + v); 1711 }, 1712 1713 differenceMethodExperiments: function (component, curve) { 1714 var i, 1715 level, 1716 le, 1717 up, 1718 t_values = component.t_values, 1719 x_values = component.x_values, 1720 y_values = component.y_values, 1721 x_diffs = [], 1722 y_diffs = [], 1723 x_slopes = [], 1724 y_slopes = [], 1725 x_table = [], 1726 y_table = [], 1727 x_recip = [], 1728 y_recip = [], 1729 h, 1730 numerator, 1731 // x_med, y_med, 1732 foundCriticalPoint = 0, 1733 pos, 1734 ma, 1735 j, 1736 v, 1737 groups, 1738 criticalPoints = []; 1739 1740 h = t_values[1] - t_values[0]; 1741 x_table.push([]); 1742 y_table.push([]); 1743 x_recip.push([]); 1744 y_recip.push([]); 1745 le = y_values.length; 1746 for (i = 0; i < le; i++) { 1747 x_table[0][i] = x_values[i]; 1748 y_table[0][i] = y_values[i]; 1749 x_recip[0][i] = x_values[i]; 1750 y_recip[0][i] = y_values[i]; 1751 } 1752 1753 x_table.push([]); 1754 y_table.push([]); 1755 x_recip.push([]); 1756 y_recip.push([]); 1757 numerator = h; 1758 le = y_values.length - 1; 1759 for (i = 0; i < le; i++) { 1760 x_diffs[i] = x_values[i + 1] - x_values[i]; 1761 y_diffs[i] = y_values[i + 1] - y_values[i]; 1762 x_slopes[i] = x_diffs[i]; 1763 y_slopes[i] = y_diffs[i]; 1764 x_table[1][i] = x_diffs[i]; 1765 y_table[1][i] = y_diffs[i]; 1766 x_recip[1][i] = numerator / x_diffs[i]; 1767 y_recip[1][i] = numerator / y_diffs[i]; 1768 } 1769 le--; 1770 1771 up = Math.min(8, y_values.length - 1); 1772 for (level = 1; level < up; level++) { 1773 x_table.push([]); 1774 y_table.push([]); 1775 x_recip.push([]); 1776 y_recip.push([]); 1777 numerator *= h; 1778 for (i = 0; i < le; i++) { 1779 x_diffs[i] = x_diffs[i + 1] - x_diffs[i]; 1780 y_diffs[i] = y_diffs[i + 1] - y_diffs[i]; 1781 x_table[level + 1][i] = x_diffs[i]; 1782 y_table[level + 1][i] = y_diffs[i]; 1783 x_recip[level + 1][i] = 1784 numerator / (x_recip[level][i + 1] - x_recip[level][i]) + 1785 x_recip[level - 1][i + 1]; 1786 y_recip[level + 1][i] = 1787 numerator / (y_recip[level][i + 1] - y_recip[level][i]) + 1788 y_recip[level - 1][i + 1]; 1789 } 1790 1791 // if (level == 1) { 1792 // console.log("bends level=", level, y_diffs.toString()); 1793 // } 1794 1795 // Store point location which may be centered around 1796 // critical points. 1797 // If the level is suitable, step out of the loop. 1798 groups = this._criticalPoints(y_diffs, le, level); 1799 if (groups === false) { 1800 // Its seems, the degree of the polynomial is equal to level 1801 console.log("Polynomial of degree", level); 1802 groups = []; 1803 break; 1804 } 1805 if (groups.length > 0) { 1806 foundCriticalPoint++; 1807 if (foundCriticalPoint > 1 && level % 2 === 0) { 1808 break; 1809 } 1810 } 1811 le--; 1812 } 1813 1814 // console.log("Last diffs", y_diffs, "level", level); 1815 1816 // Analyze the groups which have been found. 1817 for (i = 0; i < groups.length; i++) { 1818 // console.log("Group", i, groups[i]) 1819 // Identify the maximum difference, i.e. the center of the "problem" 1820 ma = -Infinity; 1821 for (j = 0; j < groups[i].length; j++) { 1822 v = Math.abs(groups[i][j].v); 1823 if (v > ma) { 1824 ma = v; 1825 pos = j; 1826 } 1827 } 1828 pos = Math.floor(groups[i][pos].i + level / 2); 1829 // Analyze the critical point 1830 criticalPoints.push( 1831 this.getPointType( 1832 curve, 1833 pos, 1834 t_values, 1835 x_values, 1836 y_values, 1837 x_slopes, 1838 y_slopes, 1839 le + 1 1840 ) 1841 ); 1842 } 1843 1844 return [criticalPoints, x_table, y_table, x_recip, y_recip]; 1845 }, 1846 1847 getCenterOfCriticalInterval: function (group, degree, t_values) { 1848 var ma, 1849 j, 1850 pos, 1851 v, 1852 num = 0.0, 1853 den = 0.0, 1854 h = t_values[1] - t_values[0], 1855 pos_mean, 1856 range = []; 1857 1858 // Identify the maximum difference, i.e. the center of the "problem" 1859 // If there are several equal maxima, store the positions 1860 // in the array range and determine the center of the array. 1861 1862 ma = -Infinity; 1863 range = []; 1864 for (j = 0; j < group.length; j++) { 1865 v = Math.abs(group[j].v); 1866 if (v > ma) { 1867 range = [j]; 1868 ma = v; 1869 pos = j; 1870 } else if (ma === v) { 1871 range.push(j); 1872 } 1873 } 1874 if (range.length > 0) { 1875 pos_mean = 1876 range.reduce(function (total, val) { 1877 return total + val; 1878 }, 0) / range.length; 1879 pos = Math.floor(pos_mean); 1880 pos_mean += group[0].i; 1881 } 1882 1883 if (ma < Infinity) { 1884 for (j = 0; j < group.length; j++) { 1885 num += Math.abs(group[j].v) * group[j].i; 1886 den += Math.abs(group[j].v); 1887 } 1888 pos_mean = num / den; 1889 } 1890 pos_mean += degree / 2; 1891 return [ 1892 group[pos].i + degree / 2, 1893 pos_mean, 1894 t_values[Math.floor(pos_mean)] + h * (pos_mean - Math.floor(pos_mean)) 1895 ]; 1896 }, 1897 1898 differenceMethod: function (component, curve) { 1899 var i, 1900 level, 1901 le, 1902 up, 1903 t_values = component.t_values, 1904 x_values = component.x_values, 1905 y_values = component.y_values, 1906 x_table = [], 1907 y_table = [], 1908 foundCriticalPoint = 0, 1909 degree_x = -1, 1910 degree_y = -1, 1911 pos, 1912 res, 1913 res_x, 1914 res_y, 1915 t_approx, 1916 groups = [], 1917 types, 1918 criticalPoints = []; 1919 1920 le = y_values.length; 1921 // x_table.push([]); 1922 // y_table.push([]); 1923 // for (i = 0; i < le; i++) { 1924 // x_table[0][i] = x_values[i]; 1925 // y_table[0][i] = y_values[i]; 1926 // } 1927 x_table.push(new Float64Array(x_values)); 1928 y_table.push(new Float64Array(y_values)); 1929 1930 le--; 1931 up = Math.min(12, le); 1932 for (level = 0; level < up; level++) { 1933 // Old style method: 1934 // x_table.push([]); 1935 // y_table.push([]); 1936 // for (i = 0; i < le; i++) { 1937 // x_table[level + 1][i] = x_table[level][i + 1] - x_table[level][i]; 1938 // y_table[level + 1][i] = y_table[level][i + 1] - y_table[level][i]; 1939 // } 1940 // New method: 1941 x_table.push(new Float64Array(le)); 1942 y_table.push(new Float64Array(le)); 1943 x_table[level + 1] = x_table[level].map(function (v, idx, arr) { 1944 return arr[idx + 1] - v; 1945 }); 1946 y_table[level + 1] = y_table[level].map(function (v, idx, arr) { 1947 return arr[idx + 1] - v; 1948 }); 1949 1950 // Store point location which may be centered around critical points. 1951 // If the level is suitable, step out of the loop. 1952 res_y = this._criticalInterval(y_table[level + 1], le, level); 1953 if (res_y.smooth === true) { 1954 // Its seems, the degree of the polynomial is equal to level 1955 // If the values in level + 1 are zero, it might be a polynomial of degree level. 1956 // Seems to work numerically stable until degree 6. 1957 degree_y = level; 1958 groups = []; 1959 } 1960 res_x = this._criticalInterval(x_table[level + 1], le, level); 1961 if (degree_x === -1 && res_x.smooth === true) { 1962 // Its seems, the degree of the polynomial is equal to level 1963 // If the values in level + 1 are zero, it might be a polynomial of degree level. 1964 // Seems to work numerically stable until degree 6. 1965 degree_x = level; 1966 } 1967 if (degree_y >= 0) { 1968 break; 1969 } 1970 1971 if (res_y.groups.length > 0) { 1972 foundCriticalPoint++; 1973 if (foundCriticalPoint > 2 && (level + 1) % 2 === 0) { 1974 groups = res_y.groups; 1975 types = res_y.types; 1976 break; 1977 } 1978 } 1979 le--; 1980 } 1981 1982 // console.log("Last diffs", y_table[Math.min(level + 1, up)], "level", level + 1); 1983 // Analyze the groups which have been found. 1984 for (i = 0; i < groups.length; i++) { 1985 if (types[i] === "interval") { 1986 continue; 1987 } 1988 // console.log("Group", i, groups[i], types[i], level + 1) 1989 res = this.getCenterOfCriticalInterval(groups[i], level + 1, t_values); 1990 pos = res_y[0]; 1991 pos = Math.floor(res[1]); 1992 t_approx = res[2]; 1993 // console.log("Critical points:", groups, res, pos) 1994 1995 // Analyze the type of the critical point 1996 // Result is of type 'borderleft', borderright', 'other' 1997 criticalPoints.push( 1998 this.getPointType(curve, pos, t_approx, t_values, x_table, y_table, le + 1) 1999 ); 2000 } 2001 2002 // if (level === up) { 2003 // console.log("No convergence!"); 2004 // } else { 2005 // console.log("Convergence level", level); 2006 // } 2007 return [criticalPoints, x_table, y_table, degree_x, degree_y]; 2008 }, 2009 2010 _insertPoint_v4: function (curve, crds, t, doLog) { 2011 var p, 2012 prev = null, 2013 x, 2014 y, 2015 near = 0.8; 2016 2017 if (curve.points.length > 0) { 2018 prev = curve.points[curve.points.length - 1].scrCoords; 2019 } 2020 2021 // Add regular point 2022 p = new Coords(Const.COORDS_BY_USER, crds, curve.board); 2023 2024 if (prev !== null) { 2025 x = p.scrCoords[1] - prev[1]; 2026 y = p.scrCoords[2] - prev[2]; 2027 if (x * x + y * y < near * near) { 2028 // Math.abs(p.scrCoords[1] - prev[1]) < near && 2029 // Math.abs(p.scrCoords[2] - prev[2]) < near) { 2030 return; 2031 } 2032 } 2033 2034 p._t = t; 2035 curve.points.push(p); 2036 }, 2037 2038 getInterval: function (curve, ta, tb) { 2039 var t_int, 2040 // x_int, 2041 y_int; 2042 2043 //console.log('critical point', ta, tb); 2044 IntervalArithmetic.disable(); 2045 2046 t_int = IntervalArithmetic.Interval(ta, tb); 2047 curve.board.mathLib = IntervalArithmetic; 2048 curve.board.mathLibJXG = IntervalArithmetic; 2049 // x_int = curve.X(t_int, true); 2050 y_int = curve.Y(t_int, true); 2051 curve.board.mathLib = Math; 2052 curve.board.mathLibJXG = JXG.Math; 2053 2054 //console.log(x_int, y_int); 2055 return y_int; 2056 }, 2057 2058 sign: function (v) { 2059 if (v < 0) { 2060 return -1; 2061 } 2062 if (v > 0) { 2063 return 1; 2064 } 2065 return 0; 2066 }, 2067 2068 handleBorder: function (curve, comp, group, x_table, y_table) { 2069 var idx = group.idx, 2070 t, 2071 t1, 2072 t2, 2073 size = 32, 2074 y_int, 2075 x, 2076 y, 2077 lo, 2078 hi, 2079 i, 2080 components2, 2081 le, 2082 h; 2083 2084 // console.log("HandleBorder at t =", t_approx); 2085 // console.log("component:", comp) 2086 // console.log("Group:", group); 2087 2088 h = comp.t_values[1] - comp.t_values[0]; 2089 if (group.type === "borderleft") { 2090 t = comp.left_isNaN ? comp.left_t : group.t - h; 2091 t1 = t; 2092 t2 = t1 + h; 2093 } else if (group.type === "borderright") { 2094 t = comp.right_isNaN ? comp.right_t : group.t + h; 2095 t2 = t; 2096 t1 = t2 - h; 2097 } else { 2098 console.log("No bordercase!!!"); 2099 } 2100 2101 components2 = this.findComponents(curve, t1, t2, size); 2102 if (components2.length === 0) { 2103 return; 2104 } 2105 if (group.type === "borderleft") { 2106 t1 = components2[0].left_t; 2107 t2 = components2[0].t_values[0]; 2108 h = components2[0].t_values[1] - components2[0].t_values[0]; 2109 t1 = t1 === null ? t2 - h : t1; 2110 t = t1; 2111 y_int = this.getInterval(curve, t1, t2); 2112 if (Type.isObject(y_int)) { 2113 lo = y_int.lo; 2114 hi = y_int.hi; 2115 2116 x = curve.X(t, true); 2117 y = y_table[1][idx] < 0 ? hi : lo; 2118 this._insertPoint_v4(curve, [1, x, y], t); 2119 } 2120 } 2121 2122 le = components2[0].t_values.length; 2123 for (i = 0; i < le; i++) { 2124 t = components2[0].t_values[i]; 2125 x = components2[0].x_values[i]; 2126 y = components2[0].y_values[i]; 2127 this._insertPoint_v4(curve, [1, x, y], t); 2128 } 2129 2130 if (group.type === "borderright") { 2131 t1 = components2[0].t_values[le - 1]; 2132 t2 = components2[0].right_t; 2133 h = components2[0].t_values[1] - components2[0].t_values[0]; 2134 t2 = t2 === null ? t1 + h : t2; 2135 2136 t = t2; 2137 y_int = this.getInterval(curve, t1, t2); 2138 if (Type.isObject(y_int)) { 2139 lo = y_int.lo; 2140 hi = y_int.hi; 2141 x = curve.X(t, true); 2142 y = y_table[1][idx] > 0 ? hi : lo; 2143 this._insertPoint_v4(curve, [1, x, y], t); 2144 } 2145 } 2146 }, 2147 2148 _seconditeration_v4: function (curve, comp, group, x_table, y_table) { 2149 var i, t1, t2, ret, components2, comp2, idx, groups2, g, x_table2, y_table2, start, le; 2150 2151 // Look at two points, hopefully left and right from the critical point 2152 t1 = comp.t_values[group.idx - 2]; 2153 t2 = comp.t_values[group.idx + 2]; 2154 components2 = this.findComponents(curve, t1, t2, 64); 2155 for (idx = 0; idx < components2.length; idx++) { 2156 comp2 = components2[idx]; 2157 ret = this.differenceMethod(comp2, curve); 2158 groups2 = ret[0]; 2159 x_table2 = ret[1]; 2160 y_table2 = ret[2]; 2161 start = 0; 2162 for (g = 0; g <= groups2.length; g++) { 2163 if (g === groups2.length) { 2164 le = comp2.len; 2165 } else { 2166 le = groups2[g].idx; 2167 } 2168 2169 // Insert all uncritical points until next critical point 2170 for (i = start; i < le; i++) { 2171 if (!isNaN(comp2.x_values[i]) && !isNaN(comp2.y_values[i])) { 2172 this._insertPoint_v4( 2173 curve, 2174 [1, comp2.x_values[i], comp2.y_values[i]], 2175 comp2.t_values[i] 2176 ); 2177 } 2178 } 2179 // Handle next critical point 2180 if (g < groups2.length) { 2181 this.handleSingularity(curve, comp2, groups2[g], x_table2, y_table2); 2182 start = groups2[g].idx + 1; 2183 } 2184 } 2185 le = comp2.len; 2186 if (idx < components2.length - 1) { 2187 this._insertPoint_v4(curve, [1, NaN, NaN], comp2.right_t); 2188 } 2189 } 2190 return this; 2191 }, 2192 2193 _recurse_v4: function (curve, t1, t2, x1, y1, x2, y2, level) { 2194 var tol = 2, 2195 t = (t1 + t2) * 0.5, 2196 x = curve.X(t, true), 2197 y = curve.Y(t, true), 2198 dx, 2199 dy; 2200 2201 //console.log("Level", level) 2202 if (level === 0) { 2203 this._insertPoint_v4(curve, [1, NaN, NaN], t); 2204 return; 2205 } 2206 // console.log("R", t1, t2) 2207 dx = (x - x1) * curve.board.unitX; 2208 dy = (y - y1) * curve.board.unitY; 2209 // console.log("D1", Math.sqrt(dx * dx + dy * dy)) 2210 if (Mat.hypot(dx, dy) > tol) { 2211 this._recurse_v4(curve, t1, t, x1, y1, x, y, level - 1); 2212 } else { 2213 this._insertPoint_v4(curve, [1, x, y], t); 2214 } 2215 dx = (x - x2) * curve.board.unitX; 2216 dy = (y - y2) * curve.board.unitY; 2217 // console.log("D2", Math.sqrt(dx * dx + dy * dy), x-x2, y-y2) 2218 if (Mat.hypot(dx, dy) > tol) { 2219 this._recurse_v4(curve, t, t2, x, y, x2, y2, level - 1); 2220 } else { 2221 this._insertPoint_v4(curve, [1, x, y], t); 2222 } 2223 }, 2224 2225 handleSingularity: function (curve, comp, group, x_table, y_table) { 2226 var idx = group.idx, 2227 t, 2228 t1, 2229 t2, 2230 y_int, 2231 i1, 2232 i2, 2233 x, 2234 // y, 2235 lo, 2236 hi, 2237 d_lft, 2238 d_rgt, 2239 d_thresh = 100, 2240 // d1, 2241 // d2, 2242 di1 = 5, 2243 di2 = 3; 2244 2245 t = group.t; 2246 console.log("HandleSingularity at t =", t); 2247 // console.log(comp.t_values[idx - 1], comp.y_values[idx - 1], comp.t_values[idx + 1], comp.y_values[idx + 1]); 2248 // console.log(group); 2249 2250 // Look at two points, hopefully left and right from the critical point 2251 t1 = comp.t_values[idx - di1]; 2252 t2 = comp.t_values[idx + di1]; 2253 2254 y_int = this.getInterval(curve, t1, t2); 2255 if (Type.isObject(y_int)) { 2256 lo = y_int.lo; 2257 hi = y_int.hi; 2258 } else { 2259 if (y_table[0][idx - 1] < y_table[0][idx + 1]) { 2260 lo = y_table[0][idx - 1]; 2261 hi = y_table[0][idx + 1]; 2262 } else { 2263 lo = y_table[0][idx + 1]; 2264 hi = y_table[0][idx - 1]; 2265 } 2266 } 2267 2268 x = curve.X(t, true); 2269 2270 d_lft = 2271 (y_table[0][idx - di2] - y_table[0][idx - di1]) / 2272 (comp.t_values[idx - di2] - comp.t_values[idx - di1]); 2273 d_rgt = 2274 (y_table[0][idx + di2] - y_table[0][idx + di1]) / 2275 (comp.t_values[idx + di2] - comp.t_values[idx + di1]); 2276 2277 console.log(":::", d_lft, d_rgt); 2278 2279 //this._insertPoint_v4(curve, [1, NaN, NaN], 0); 2280 2281 if (d_lft < -d_thresh) { 2282 // Left branch very steep downwards -> add the minimum 2283 this._insertPoint_v4(curve, [1, x, lo], t, true); 2284 if (d_rgt <= d_thresh) { 2285 // Right branch not very steep upwards -> interrupt the curve 2286 // I.e. it looks like -infty / (finite or infty) and not like -infty / -infty 2287 this._insertPoint_v4(curve, [1, NaN, NaN], t); 2288 } 2289 } else if (d_lft > d_thresh) { 2290 // Left branch very steep upwards -> add the maximum 2291 this._insertPoint_v4(curve, [1, x, hi], t); 2292 if (d_rgt >= -d_thresh) { 2293 // Right branch not very steep downwards -> interrupt the curve 2294 // I.e. it looks like infty / (finite or -infty) and not like infty / infty 2295 this._insertPoint_v4(curve, [1, NaN, NaN], t); 2296 } 2297 } else { 2298 if (lo === -Infinity) { 2299 this._insertPoint_v4(curve, [1, x, lo], t, true); 2300 this._insertPoint_v4(curve, [1, NaN, NaN], t); 2301 } 2302 if (hi === Infinity) { 2303 this._insertPoint_v4(curve, [1, NaN, NaN], t); 2304 this._insertPoint_v4(curve, [1, x, hi], t, true); 2305 } 2306 2307 if (group.t < comp.t_values[idx]) { 2308 i1 = idx - 1; 2309 i2 = idx; 2310 } else { 2311 i1 = idx; 2312 i2 = idx + 1; 2313 } 2314 t1 = comp.t_values[i1]; 2315 t2 = comp.t_values[i2]; 2316 this._recurse_v4( 2317 curve, 2318 t1, 2319 t2, 2320 x_table[0][i1], 2321 y_table[0][i1], 2322 x_table[0][i2], 2323 y_table[0][i2], 2324 10 2325 ); 2326 2327 // x = (x_table[0][idx] - x_table[0][idx - 1]) * curve.board.unitX; 2328 // y = (y_table[0][idx] - y_table[0][idx - 1]) * curve.board.unitY; 2329 // d1 = Math.sqrt(x * x + y * y); 2330 // x = (x_table[0][idx + 1] - x_table[0][idx]) * curve.board.unitX; 2331 // y = (y_table[0][idx + 1] - y_table[0][idx]) * curve.board.unitY; 2332 // d2 = Math.sqrt(x * x + y * y); 2333 2334 // console.log("end", t1, t2, t); 2335 // if (true || (d1 > 2 || d2 > 2)) { 2336 2337 // console.log(d1, d2, y_table[0][idx]) 2338 // // Finite jump 2339 // this._insertPoint_v4(curve, [1, NaN, NaN], t); 2340 // } else { 2341 // if (lo !== -Infinity && hi !== Infinity) { 2342 // // Critical point which can be ignored 2343 // this._insertPoint_v4(curve, [1, x_table[0][idx], y_table[0][idx]], comp.t_values[idx]); 2344 // } else { 2345 // if (lo === -Infinity) { 2346 // this._insertPoint_v4(curve, [1, x, lo], t, true); 2347 // this._insertPoint_v4(curve, [1, NaN, NaN], t); 2348 // } 2349 // if (hi === Infinity) { 2350 // this._insertPoint_v4(curve, [1, NaN, NaN], t); 2351 // this._insertPoint_v4(curve, [1, x, hi], t, true); 2352 // } 2353 // } 2354 // } 2355 } 2356 if (d_rgt < -d_thresh) { 2357 // Right branch very steep downwards -> add the maximum 2358 this._insertPoint_v4(curve, [1, x, hi], t); 2359 } else if (d_rgt > d_thresh) { 2360 // Right branch very steep upwards -> add the minimum 2361 this._insertPoint_v4(curve, [1, x, lo], t); 2362 } 2363 }, 2364 2365 /** 2366 * Number of equidistant points where the function is evaluated 2367 */ 2368 steps: 1021, //2053, // 1021, 2369 2370 /** 2371 * If the absolute maximum of the set of differences is larger than 2372 * criticalThreshold * median of these values, it is regarded as critical point. 2373 * @see JXG.Math.Plot._criticalInterval 2374 */ 2375 criticalThreshold: 1000, 2376 2377 plot_v4: function (curve, ta, tb, steps) { 2378 var i, 2379 // j, 2380 le, 2381 components, 2382 idx, 2383 comp, 2384 groups, 2385 g, 2386 start, 2387 ret, 2388 x_table, y_table, 2389 t, t1, t2, 2390 // good, 2391 // bad, 2392 // x_int, 2393 y_int, 2394 // degree_x, 2395 // degree_y, 2396 h = (tb - ta) / steps, 2397 Ypl = function (x) { 2398 return curve.Y(x, true); 2399 }, 2400 Ymi = function (x) { 2401 return -curve.Y(x, true); 2402 }, 2403 h2 = h * 0.5; 2404 2405 components = this.findComponents(curve, ta, tb, steps); 2406 for (idx = 0; idx < components.length; idx++) { 2407 comp = components[idx]; 2408 ret = this.differenceMethod(comp, curve); 2409 groups = ret[0]; 2410 x_table = ret[1]; 2411 y_table = ret[2]; 2412 2413 // degree_x = ret[3]; 2414 // degree_y = ret[4]; 2415 // if (degree_x >= 0) { 2416 // console.log("x polynomial of degree", degree_x); 2417 // } 2418 // if (degree_y >= 0) { 2419 // console.log("y polynomial of degree", degree_y); 2420 // } 2421 if (groups.length === 0 || groups[0].type !== "borderleft") { 2422 groups.unshift({ 2423 idx: 0, 2424 t: comp.t_values[0], 2425 x: comp.x_values[0], 2426 y: comp.y_values[0], 2427 type: "borderleft" 2428 }); 2429 } 2430 if (groups[groups.length - 1].type !== "borderright") { 2431 le = comp.t_values.length; 2432 groups.push({ 2433 idx: le - 1, 2434 t: comp.t_values[le - 1], 2435 x: comp.x_values[le - 1], 2436 y: comp.y_values[le - 1], 2437 type: "borderright" 2438 }); 2439 } 2440 2441 start = 0; 2442 for (g = 0; g <= groups.length; g++) { 2443 if (g === groups.length) { 2444 le = comp.len; 2445 } else { 2446 le = groups[g].idx - 1; 2447 } 2448 2449 // good = 0; 2450 // bad = 0; 2451 // Insert all uncritical points until next critical point 2452 for (i = start; i < le - 2; i++) { 2453 this._insertPoint_v4( 2454 curve, 2455 [1, comp.x_values[i], comp.y_values[i]], 2456 comp.t_values[i] 2457 ); 2458 // j = Math.max(0, i - 2); 2459 // Add more points in critical intervals 2460 if ( 2461 //degree_y === -1 && // No polynomial 2462 i >= start + 3 && 2463 i < le - 3 && // Do not do this if too close to a critical point 2464 y_table.length > 3 && 2465 Math.abs(y_table[2][i]) > 0.2 * Math.abs(y_table[0][i]) 2466 ) { 2467 t = comp.t_values[i]; 2468 h2 = h * 0.25; 2469 y_int = this.getInterval(curve, t, t + h); 2470 if (Type.isObject(y_int)) { 2471 if (y_table[2][i] > 0) { 2472 this._insertPoint_v4(curve, [1, t + h2, y_int.lo], t + h2); 2473 } else { 2474 this._insertPoint_v4( 2475 curve, 2476 [1, t + h - h2, y_int.hi], 2477 t + h - h2 2478 ); 2479 } 2480 } else { 2481 t1 = Numerics.fminbr(Ypl, [t, t + h]); 2482 t2 = Numerics.fminbr(Ymi, [t, t + h]); 2483 if (t1 < t2) { 2484 this._insertPoint_v4( 2485 curve, 2486 [1, curve.X(t1, true), curve.Y(t1, true)], 2487 t1 2488 ); 2489 this._insertPoint_v4( 2490 curve, 2491 [1, curve.X(t2, true), curve.Y(t2, true)], 2492 t2 2493 ); 2494 } else { 2495 this._insertPoint_v4( 2496 curve, 2497 [1, curve.X(t2, true), curve.Y(t2, true)], 2498 t2 2499 ); 2500 this._insertPoint_v4( 2501 curve, 2502 [1, curve.X(t1, true), curve.Y(t1, true)], 2503 t1 2504 ); 2505 } 2506 } 2507 // bad++; 2508 // } else { 2509 // good++; 2510 } 2511 } 2512 // console.log("GOOD", good, "BAD", bad); 2513 2514 // Handle next critical point 2515 if (g < groups.length) { 2516 //console.log("critical point / interval", groups[g]); 2517 2518 i = groups[g].idx; 2519 if (groups[g].type === "borderleft" || groups[g].type === "borderright") { 2520 this.handleBorder(curve, comp, groups[g], x_table, y_table); 2521 } else { 2522 this._seconditeration_v4(curve, comp, groups[g], x_table, y_table); 2523 } 2524 2525 start = groups[g].idx + 1 + 1; 2526 } 2527 } 2528 2529 le = comp.len; 2530 if (idx < components.length - 1) { 2531 this._insertPoint_v4(curve, [1, NaN, NaN], comp.right_t); 2532 } 2533 } 2534 }, 2535 2536 /** 2537 * Updates the data points of a parametric curve, plotVersion 4. This version is used if {@link JXG.Curve#plotVersion} is <tt>4</tt>. 2538 * @param {JXG.Curve} curve JSXGraph curve element 2539 * @param {Number} mi Left bound of curve 2540 * @param {Number} ma Right bound of curve 2541 * @returns {JXG.Curve} Reference to the curve object. 2542 */ 2543 updateParametricCurve_v4: function (curve, mi, ma) { 2544 var ta, tb, w2, bbox; 2545 2546 if (curve.xterm === "x") { 2547 // For function graphs we can restrict the plot interval 2548 // to the visible area +plus margin 2549 bbox = curve.board.getBoundingBox(); 2550 w2 = (bbox[2] - bbox[0]) * 0.3; 2551 // h2 = (bbox[1] - bbox[3]) * 0.3; 2552 ta = Math.max(mi, bbox[0] - w2); 2553 tb = Math.min(ma, bbox[2] + w2); 2554 } else { 2555 ta = mi; 2556 tb = ma; 2557 } 2558 2559 curve.points = []; 2560 2561 //console.log("--------------------"); 2562 this.plot_v4(curve, ta, tb, this.steps); 2563 2564 curve.numberPoints = curve.points.length; 2565 //console.log(curve.numberPoints); 2566 }, 2567 2568 //---------------------------------------------------------------------- 2569 // Plot algorithm alias 2570 //---------------------------------------------------------------------- 2571 2572 /** 2573 * Updates the data points of a parametric curve, alias for {@link JXG.Curve#updateParametricCurve_v2}. 2574 * This is needed for backwards compatibility, if this method has been 2575 * used directly in an application. 2576 * @param {JXG.Curve} curve JSXGraph curve element 2577 * @param {Number} mi Left bound of curve 2578 * @param {Number} ma Right bound of curve 2579 * @returns {JXG.Curve} Reference to the curve object. 2580 * 2581 * @see JXG.Curve#updateParametricCurve_v2 2582 */ 2583 updateParametricCurve: function (curve, mi, ma) { 2584 return this.updateParametricCurve_v2(curve, mi, ma); 2585 } 2586 }; 2587 2588 export default Mat.Plot; 2589