1 /* 2 Copyright 2008-2017 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 = Type.evaluate(this.visProp.numberpointshigh); 83 84 this.bezierDegree = 1; 85 86 /** 87 * Array holding the x-coordinates of a data plot. 88 * This array can be updated during run time by overwriting 89 * the method {@link JXG.Curve#updateDataArray}. 90 * @type {array} 91 */ 92 this.dataX = null; 93 /** 94 * Array holding the y-coordinates of a data plot. 95 * This array can be updated during run time by overwriting 96 * the method {@link JXG.Curve#updateDataArray}. 97 * @type {array} 98 */ 99 this.dataY = null; 100 101 /** 102 * Stores a quad tree if it is required. The quad tree is generated in the curve 103 * updates and can be used to speed up the hasPoint method. 104 * @type {JXG.Math.Quadtree} 105 */ 106 this.qdt = null; 107 108 if (Type.exists(parents[0])) { 109 this.varname = parents[0]; 110 } else { 111 this.varname = 'x'; 112 } 113 114 // function graphs: "x" 115 this.xterm = parents[1]; 116 // function graphs: e.g. "x^2" 117 this.yterm = parents[2]; 118 119 // Converts GEONExT syntax into JavaScript syntax 120 this.generateTerm(this.varname, this.xterm, this.yterm, parents[3], parents[4]); 121 // First evaluation of the curve 122 this.updateCurve(); 123 124 this.id = this.board.setId(this, 'G'); 125 this.board.renderer.drawCurve(this); 126 127 this.board.finalizeAdding(this); 128 129 this.createGradient(); 130 this.elType = 'curve'; 131 this.createLabel(); 132 133 if (Type.isString(this.xterm)) { 134 this.notifyParents(this.xterm); 135 } 136 if (Type.isString(this.yterm)) { 137 this.notifyParents(this.yterm); 138 } 139 140 this.methodMap = Type.deepCopy(this.methodMap, { 141 generateTerm: 'generateTerm', 142 setTerm: 'generateTerm' 143 }); 144 }; 145 146 JXG.Curve.prototype = new GeometryElement(); 147 148 JXG.extend(JXG.Curve.prototype, /** @lends JXG.Curve.prototype */ { 149 150 /** 151 * Gives the default value of the left bound for the curve. 152 * May be overwritten in {@link JXG.Curve#generateTerm}. 153 * @returns {Number} Left bound for the curve. 154 */ 155 minX: function () { 156 var leftCoords; 157 158 if (Type.evaluate(this.visProp.curvetype) === 'polar') { 159 return 0; 160 } 161 162 leftCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board, false); 163 return leftCoords.usrCoords[1]; 164 }, 165 166 /** 167 * Gives the default value of the right bound for the curve. 168 * May be overwritten in {@link JXG.Curve#generateTerm}. 169 * @returns {Number} Right bound for the curve. 170 */ 171 maxX: function () { 172 var rightCoords; 173 174 if (Type.evaluate(this.visProp.curvetype) === 'polar') { 175 return 2 * Math.PI; 176 } 177 rightCoords = new Coords(Const.COORDS_BY_SCREEN, [this.board.canvasWidth, 0], this.board, false); 178 179 return rightCoords.usrCoords[1]; 180 }, 181 182 /** 183 * The parametric function which defines the x-coordinate of the curve. 184 * @param {Number} t A number between {@link JXG.Curve#minX} and {@link JXG.Curve#maxX}. 185 * @param {Boolean} suspendUpdate A boolean flag which is false for the 186 * first call of the function during a fresh plot of the curve and true 187 * for all other calss of the function. This may be used to speed up the 188 * plotting of the curve, if the e.g. the curve depends on some input elements. 189 * @returns {Number} x-coordinate of the curve at t. 190 */ 191 X: function (t) { 192 return NaN; 193 }, 194 /** 195 * The parametric function which defines the y-coordinate of the curve. 196 * @param {Number} t A number between {@link JXG.Curve#minX} and {@link JXG.Curve#maxX}. 197 * @param {Boolean} suspendUpdate A boolean flag which is false for the 198 * first call of the function during a fresh plot of the curve and true 199 * for all other calss of the function. This may be used to speed up the 200 * plotting of the curve, if the e.g. the curve depends on some input elements. 201 * @returns {Number} y-coordinate of the curve at t. 202 */ 203 Y: function (t) { 204 return NaN; 205 }, 206 207 /** 208 * Treat the curve as curve with homogeneous coordinates. 209 * @param {Number} t A number between {@link JXG.Curve#minX} and {@link JXG.Curve#maxX}. 210 * @returns {Number} Always 1.0 211 */ 212 Z: function (t) { 213 return 1; 214 }, 215 216 /** 217 * Checks whether (x,y) is near the curve. 218 * @param {Number} x Coordinate in x direction, screen coordinates. 219 * @param {Number} y Coordinate in y direction, screen coordinates. 220 * @param {Number} start Optional start index for search on data plots. 221 * @returns {Boolean} True if (x,y) is near the curve, False otherwise. 222 */ 223 hasPoint: function (x, y, start) { 224 var t, checkPoint, len, invMat, c, 225 i, j, tX, tY, 226 res = [], 227 points, qdt, 228 steps = Type.evaluate(this.visProp.numberpointslow), 229 d = (this.maxX() - this.minX()) / steps, 230 prec = this.board.options.precision.hasPoint, 231 dist = Infinity, 232 ux2, uy2, 233 ev_ct, 234 suspendUpdate = true; 235 236 checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board, false); 237 x = checkPoint.usrCoords[1]; 238 y = checkPoint.usrCoords[2]; 239 240 // We use usrCoords. Only in the final distance calculation 241 // screen coords are used 242 prec += Type.evaluate(this.visProp.strokewidth) * 0.5; 243 prec *= prec; // We do not want to take sqrt 244 ux2 = this.board.unitX * this.board.unitX; 245 uy2 = this.board.unitY * this.board.unitY; 246 247 if (this.transformations.length > 0) { 248 /** 249 * Transform the mouse/touch coordinates 250 * back to the original position of the curve. 251 */ 252 this.updateTransformMatrix(); 253 invMat = Mat.inverse(this.transformMat); 254 c = Mat.matVecMult(invMat, [1, x, y]); 255 x = c[1]; 256 y = c[2]; 257 } 258 259 ev_ct = Type.evaluate(this.visProp.curvetype); 260 if (ev_ct === 'parameter' || 261 ev_ct === 'polar') { 262 263 prec = prec * prec; 264 265 // Brute force search for a point on the curve close to the mouse pointer 266 for (i = 0, t = this.minX(); i < steps; i++) { 267 tX = this.X(t, suspendUpdate); 268 tY = this.Y(t, suspendUpdate); 269 270 dist = (x - tX) * (x - tX) * ux2 + (y - tY) * (y - tY) * uy2; 271 272 if (dist <= prec) { 273 return true; 274 } 275 276 t += d; 277 } 278 } else if (ev_ct === 'plot' || 279 ev_ct === 'functiongraph') { 280 281 if (!Type.exists(start) || start < 0) { 282 start = 0; 283 } 284 285 if (Type.exists(this.qdt) && Type.evaluate(this.visProp.useqdt) && this.bezierDegree !== 3) { 286 qdt = this.qdt.query(new Coords(Const.COORDS_BY_USER, [x, y], this.board)); 287 points = qdt.points; 288 len = points.length; 289 } else { 290 points = this.points; 291 len = this.numberPoints - 1; 292 } 293 294 for (i = start; i < len; i++) { 295 if (this.bezierDegree === 3) { 296 res.push(Geometry.projectCoordsToBeziersegment([1, x, y], this, i)); 297 } else { 298 if (qdt) { 299 if (points[i].prev) { 300 res = Geometry.projectCoordsToSegment( 301 [1, x, y], 302 points[i].prev.usrCoords, 303 points[i].usrCoords 304 ); 305 } 306 307 // If the next point in the array is the same as the current points 308 // next neighbor we don't have to project it onto that segment because 309 // that will already be done in the next iteration of this loop. 310 if (points[i].next && points[i + 1] !== points[i].next) { 311 res = Geometry.projectCoordsToSegment( 312 [1, x, y], 313 points[i].usrCoords, 314 points[i].next.usrCoords 315 ); 316 } 317 } else { 318 res = Geometry.projectCoordsToSegment( 319 [1, x, y], 320 points[i].usrCoords, 321 points[i + 1].usrCoords 322 ); 323 } 324 } 325 326 if (res[1] >= 0 && res[1] <= 1 && 327 (x - res[0][1]) * (x - res[0][1]) * ux2 + 328 (y - res[0][2]) * (y - res[0][2]) * uy2 <= prec) { 329 return true; 330 } 331 } 332 return false; 333 } 334 return (dist < prec); 335 }, 336 337 /** 338 * Allocate points in the Coords array this.points 339 */ 340 allocatePoints: function () { 341 var i, len; 342 343 len = this.numberPoints; 344 345 if (this.points.length < this.numberPoints) { 346 for (i = this.points.length; i < len; i++) { 347 this.points[i] = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false); 348 } 349 } 350 }, 351 352 /** 353 * Computes for equidistant points on the x-axis the values of the function 354 * @returns {JXG.Curve} Reference to the curve object. 355 * @see JXG.Curve#updateCurve 356 */ 357 update: function () { 358 if (this.needsUpdate) { 359 if (Type.evaluate(this.visProp.trace)) { 360 this.cloneToBackground(true); 361 } 362 this.updateCurve(); 363 } 364 365 return this; 366 }, 367 368 /** 369 * Updates the visual contents of the curve. 370 * @returns {JXG.Curve} Reference to the curve object. 371 */ 372 updateRenderer: function () { 373 var wasReal; 374 375 if (!this.needsUpdate) { 376 return this; 377 } 378 379 if (this.visPropCalc.visible) { 380 wasReal = this.isReal; 381 382 this.checkReal(); 383 384 if (wasReal && !this.isReal) { 385 this.updateVisibility(false); 386 } 387 } 388 389 if (this.visPropCalc.visible) { 390 this.board.renderer.updateCurve(this); 391 } 392 393 /* Update the label if visible. */ 394 if (this.hasLabel && this.visPropCalc.visible && this.label && 395 this.label.visPropCalc.visible && this.isReal) { 396 397 this.label.update(); 398 this.board.renderer.updateText(this.label); 399 } 400 401 // Update rendNode display 402 this.setDisplayRendNode(); 403 // if (this.visPropCalc.visible !== this.visPropOld.visible) { 404 // this.board.renderer.display(this, this.visPropCalc.visible); 405 // this.visPropOld.visible = this.visPropCalc.visible; 406 // 407 // if (this.hasLabel) { 408 // this.board.renderer.display(this.label, this.label.visPropCalc.visible); 409 // } 410 // } 411 412 this.needsUpdate = false; 413 return this; 414 }, 415 416 /** 417 * For dynamic dataplots updateCurve can be used to compute new entries 418 * for the arrays {@link JXG.Curve#dataX} and {@link JXG.Curve#dataY}. It 419 * is used in {@link JXG.Curve#updateCurve}. Default is an empty method, can 420 * be overwritten by the user. 421 * 422 * 423 * @example 424 * // This example overwrites the updateDataArray method. 425 * // There, new values for the arrays JXG.Curve.dataX and JXG.Curve.dataY 426 * // are computed from the value of the slider N 427 * 428 * var N = board.create('slider', [[0,1.5],[3,1.5],[1,3,40]], {name:'n',snapWidth:1}); 429 * var circ = board.create('circle',[[4,-1.5],1],{strokeWidth:1, strokecolor:'black', strokeWidth:2, 430 * fillColor:'#0055ff13'}); 431 * 432 * var c = board.create('curve', [[0],[0]],{strokecolor:'red', strokeWidth:2}); 433 * c.updateDataArray = function() { 434 * var r = 1, n = Math.floor(N.Value()), 435 * x = [0], y = [0], 436 * phi = Math.PI/n, 437 * h = r*Math.cos(phi), 438 * s = r*Math.sin(phi), 439 * i, j, 440 * px = 0, py = 0, sgn = 1, 441 * d = 16, 442 * dt = phi/d, 443 * pt; 444 * 445 * for (i = 0; i < n; i++) { 446 * for (j = -d; j <= d; j++) { 447 * pt = dt*j; 448 * x.push(px + r*Math.sin(pt)); 449 * y.push(sgn*r*Math.cos(pt) - (sgn-1)*h*0.5); 450 * } 451 * px += s; 452 * sgn *= (-1); 453 * } 454 * x.push((n - 1)*s); 455 * y.push(h + (sgn - 1)*h*0.5); 456 * this.dataX = x; 457 * this.dataY = y; 458 * } 459 * 460 * var c2 = board.create('curve', [[0],[0]],{strokecolor:'red', strokeWidth:1}); 461 * c2.updateDataArray = function() { 462 * var r = 1, n = Math.floor(N.Value()), 463 * px = circ.midpoint.X(), py = circ.midpoint.Y(), 464 * x = [px], y = [py], 465 * phi = Math.PI/n, 466 * s = r*Math.sin(phi), 467 * i, j, 468 * d = 16, 469 * dt = phi/d, 470 * pt = Math.PI*0.5+phi; 471 * 472 * for (i = 0; i < n; i++) { 473 * for (j= -d; j <= d; j++) { 474 * x.push(px + r*Math.cos(pt)); 475 * y.push(py + r*Math.sin(pt)); 476 * pt -= dt; 477 * } 478 * x.push(px); 479 * y.push(py); 480 * pt += dt; 481 * } 482 * this.dataX = x; 483 * this.dataY = y; 484 * } 485 * board.update(); 486 * 487 * </pre><div id="20bc7802-e69e-11e5-b1bf-901b0e1b8723" class="jxgbox" style="width: 600px; height: 400px;"></div> 488 * <script type="text/javascript"> 489 * (function() { 490 * var board = JXG.JSXGraph.initBoard('20bc7802-e69e-11e5-b1bf-901b0e1b8723', 491 * {boundingbox: [-1.5,2,8,-3], keepaspectratio: true, axis: true, showcopyright: false, shownavigation: false}); 492 * var N = board.create('slider', [[0,1.5],[3,1.5],[1,3,40]], {name:'n',snapWidth:1}); 493 * var circ = board.create('circle',[[4,-1.5],1],{strokeWidth:1, strokecolor:'black', 494 * strokeWidth:2, fillColor:'#0055ff13'}); 495 * 496 * var c = board.create('curve', [[0],[0]],{strokecolor:'red', strokeWidth:2}); 497 * c.updateDataArray = function() { 498 * var r = 1, n = Math.floor(N.Value()), 499 * x = [0], y = [0], 500 * phi = Math.PI/n, 501 * h = r*Math.cos(phi), 502 * s = r*Math.sin(phi), 503 * i, j, 504 * px = 0, py = 0, sgn = 1, 505 * d = 16, 506 * dt = phi/d, 507 * pt; 508 * 509 * for (i=0;i<n;i++) { 510 * for (j=-d;j<=d;j++) { 511 * pt = dt*j; 512 * x.push(px+r*Math.sin(pt)); 513 * y.push(sgn*r*Math.cos(pt)-(sgn-1)*h*0.5); 514 * } 515 * px += s; 516 * sgn *= (-1); 517 * } 518 * x.push((n-1)*s); 519 * y.push(h+(sgn-1)*h*0.5); 520 * this.dataX = x; 521 * this.dataY = y; 522 * } 523 * 524 * var c2 = board.create('curve', [[0],[0]],{strokecolor:'red', strokeWidth:1}); 525 * c2.updateDataArray = function() { 526 * var r = 1, n = Math.floor(N.Value()), 527 * px = circ.midpoint.X(), py = circ.midpoint.Y(), 528 * x = [px], y = [py], 529 * phi = Math.PI/n, 530 * s = r*Math.sin(phi), 531 * i, j, 532 * d = 16, 533 * dt = phi/d, 534 * pt = Math.PI*0.5+phi; 535 * 536 * for (i=0;i<n;i++) { 537 * for (j=-d;j<=d;j++) { 538 * x.push(px+r*Math.cos(pt)); 539 * y.push(py+r*Math.sin(pt)); 540 * pt -= dt; 541 * } 542 * x.push(px); 543 * y.push(py); 544 * pt += dt; 545 * } 546 * this.dataX = x; 547 * this.dataY = y; 548 * } 549 * board.update(); 550 * 551 * })(); 552 * 553 * </script><pre> 554 * 555 * @example 556 * // This is an example which overwrites updateDataArray and produces 557 * // a Bezier curve of degree three. 558 * var A = board.create('point', [-3,3]); 559 * var B = board.create('point', [3,-2]); 560 * var line = board.create('segment', [A,B]); 561 * 562 * var height = 0.5; // height of the curly brace 563 * 564 * // Curly brace 565 * var crl = board.create('curve', [[0],[0]], {strokeWidth:1, strokeColor:'black'}); 566 * crl.bezierDegree = 3; 567 * crl.updateDataArray = function() { 568 * var d = [B.X()-A.X(), B.Y()-A.Y()], 569 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 570 * mid = [(A.X()+B.X())*0.5, (A.Y()+B.Y())*0.5]; 571 * 572 * d[0] *= height/dl; 573 * d[1] *= height/dl; 574 * 575 * this.dataX = [ A.X(), A.X()-d[1], mid[0], mid[0]-d[1], mid[0], B.X()-d[1], B.X() ]; 576 * this.dataY = [ A.Y(), A.Y()+d[0], mid[1], mid[1]+d[0], mid[1], B.Y()+d[0], B.Y() ]; 577 * }; 578 * 579 * // Text 580 * var txt = board.create('text', [ 581 * function() { 582 * var d = [B.X()-A.X(), B.Y()-A.Y()], 583 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 584 * mid = (A.X()+B.X())*0.5; 585 * 586 * d[1] *= height/dl; 587 * return mid-d[1]+0.1; 588 * }, 589 * function() { 590 * var d = [B.X()-A.X(), B.Y()-A.Y()], 591 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 592 * mid = (A.Y()+B.Y())*0.5; 593 * 594 * d[0] *= height/dl; 595 * return mid+d[0]+0.1; 596 * }, 597 * function() { return "length=" + JXG.toFixed(B.Dist(A), 2); } 598 * ]); 599 * 600 * 601 * board.update(); // This update is necessary to call updateDataArray the first time. 602 * 603 * </pre><div id="a61a4d66-e69f-11e5-b1bf-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 604 * <script type="text/javascript"> 605 * (function() { 606 * var board = JXG.JSXGraph.initBoard('a61a4d66-e69f-11e5-b1bf-901b0e1b8723', 607 * {boundingbox: [-4, 4, 4,-4], axis: true, showcopyright: false, shownavigation: false}); 608 * var A = board.create('point', [-3,3]); 609 * var B = board.create('point', [3,-2]); 610 * var line = board.create('segment', [A,B]); 611 * 612 * var height = 0.5; // height of the curly brace 613 * 614 * // Curly brace 615 * var crl = board.create('curve', [[0],[0]], {strokeWidth:1, strokeColor:'black'}); 616 * crl.bezierDegree = 3; 617 * crl.updateDataArray = function() { 618 * var d = [B.X()-A.X(), B.Y()-A.Y()], 619 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 620 * mid = [(A.X()+B.X())*0.5, (A.Y()+B.Y())*0.5]; 621 * 622 * d[0] *= height/dl; 623 * d[1] *= height/dl; 624 * 625 * this.dataX = [ A.X(), A.X()-d[1], mid[0], mid[0]-d[1], mid[0], B.X()-d[1], B.X() ]; 626 * this.dataY = [ A.Y(), A.Y()+d[0], mid[1], mid[1]+d[0], mid[1], B.Y()+d[0], B.Y() ]; 627 * }; 628 * 629 * // Text 630 * var txt = board.create('text', [ 631 * function() { 632 * var d = [B.X()-A.X(), B.Y()-A.Y()], 633 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 634 * mid = (A.X()+B.X())*0.5; 635 * 636 * d[1] *= height/dl; 637 * return mid-d[1]+0.1; 638 * }, 639 * function() { 640 * var d = [B.X()-A.X(), B.Y()-A.Y()], 641 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 642 * mid = (A.Y()+B.Y())*0.5; 643 * 644 * d[0] *= height/dl; 645 * return mid+d[0]+0.1; 646 * }, 647 * function() { return "length="+JXG.toFixed(B.Dist(A), 2); } 648 * ]); 649 * 650 * 651 * board.update(); // This update is necessary to call updateDataArray the first time. 652 * 653 * })(); 654 * 655 * </script><pre> 656 * 657 * 658 */ 659 updateDataArray: function () { 660 // this used to return this, but we shouldn't rely on the user to implement it. 661 }, 662 663 /** 664 * Computes for equidistant points on the x-axis the values 665 * of the function. 666 * If the mousemove event triggers this update, we use only few 667 * points. Otherwise, e.g. on mouseup, many points are used. 668 * @see JXG.Curve#update 669 * @returns {JXG.Curve} Reference to the curve object. 670 */ 671 updateCurve: function () { 672 var len, mi, ma, x, y, i, 673 //t1, t2, l1, 674 suspendUpdate = false; 675 676 this.updateTransformMatrix(); 677 this.updateDataArray(); 678 mi = this.minX(); 679 ma = this.maxX(); 680 681 // Discrete data points 682 // x-coordinates are in an array 683 if (Type.exists(this.dataX)) { 684 this.numberPoints = this.dataX.length; 685 len = this.numberPoints; 686 687 // It is possible, that the array length has increased. 688 this.allocatePoints(); 689 690 for (i = 0; i < len; i++) { 691 x = i; 692 693 // y-coordinates are in an array 694 if (Type.exists(this.dataY)) { 695 y = i; 696 // The last parameter prevents rounding in usr2screen(). 697 this.points[i].setCoordinates(Const.COORDS_BY_USER, [this.dataX[i], this.dataY[i]], false); 698 } else { 699 // discrete x data, continuous y data 700 y = this.X(x); 701 // The last parameter prevents rounding in usr2screen(). 702 this.points[i].setCoordinates(Const.COORDS_BY_USER, [this.dataX[i], this.Y(y, suspendUpdate)], false); 703 } 704 705 this.updateTransform(this.points[i]); 706 suspendUpdate = true; 707 } 708 // continuous x data 709 } else { 710 if (Type.evaluate(this.visProp.doadvancedplot)) { 711 this.updateParametricCurve(mi, ma, len); 712 } else if (Type.evaluate(this.visProp.doadvancedplotold)) { 713 this.updateParametricCurveOld(mi, ma, len); 714 } else { 715 if (this.board.updateQuality === this.board.BOARD_QUALITY_HIGH) { 716 this.numberPoints = Type.evaluate(this.visProp.numberpointshigh); 717 } else { 718 this.numberPoints = Type.evaluate(this.visProp.numberpointslow); 719 } 720 721 // It is possible, that the array length has increased. 722 this.allocatePoints(); 723 this.updateParametricCurveNaive(mi, ma, this.numberPoints); 724 } 725 len = this.numberPoints; 726 727 if (Type.evaluate(this.visProp.useqdt) && this.board.updateQuality === this.board.BOARD_QUALITY_HIGH) { 728 this.qdt = new QDT(this.board.getBoundingBox()); 729 for (i = 0; i < this.points.length; i++) { 730 this.qdt.insert(this.points[i]); 731 732 if (i > 0) { 733 this.points[i].prev = this.points[i - 1]; 734 } 735 736 if (i < len - 1) { 737 this.points[i].next = this.points[i + 1]; 738 } 739 } 740 } 741 742 for (i = 0; i < len; i++) { 743 this.updateTransform(this.points[i]); 744 } 745 } 746 747 if (Type.evaluate(this.visProp.curvetype) !== 'plot' && Type.evaluate(this.visProp.rdpsmoothing)) { 748 this.points = Numerics.RamerDouglasPeucker(this.points, 0.2); 749 this.numberPoints = this.points.length; 750 } 751 752 return this; 753 }, 754 755 updateTransformMatrix: function () { 756 var t, c, i, 757 len = this.transformations.length; 758 759 this.transformMat = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]; 760 761 for (i = 0; i < len; i++) { 762 t = this.transformations[i]; 763 t.update(); 764 this.transformMat = Mat.matMatMult(t.matrix, this.transformMat); 765 } 766 767 return this; 768 }, 769 770 /** 771 * Check if at least one point on the curve is finite and real. 772 **/ 773 checkReal: function () { 774 var b = false, i, p, 775 len = this.numberPoints; 776 777 for (i = 0; i < len; i++) { 778 p = this.points[i].usrCoords; 779 if (!isNaN(p[1]) && !isNaN(p[2]) && Math.abs(p[0]) > Mat.eps) { 780 b = true; 781 break; 782 } 783 } 784 this.isReal = b; 785 }, 786 787 /** 788 * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#doadvancedplot} is <tt>false</tt>. 789 * @param {Number} mi Left bound of curve 790 * @param {Number} ma Right bound of curve 791 * @param {Number} len Number of data points 792 * @returns {JXG.Curve} Reference to the curve object. 793 */ 794 updateParametricCurveNaive: function (mi, ma, len) { 795 var i, t, 796 suspendUpdate = false, 797 stepSize = (ma - mi) / len; 798 799 for (i = 0; i < len; i++) { 800 t = mi + i * stepSize; 801 // The last parameter prevents rounding in usr2screen(). 802 this.points[i].setCoordinates(Const.COORDS_BY_USER, [this.X(t, suspendUpdate), this.Y(t, suspendUpdate)], false); 803 suspendUpdate = true; 804 } 805 return this; 806 }, 807 808 /** 809 * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#doadvancedplot} is <tt>true</tt>. 810 * Since 0.99 this algorithm is deprecated. It still can be used if {@link JXG.Curve#doadvancedplotold} is <tt>true</tt>. 811 * 812 * @deprecated 813 * @param {Number} mi Left bound of curve 814 * @param {Number} ma Right bound of curve 815 * @returns {JXG.Curve} Reference to the curve object. 816 */ 817 updateParametricCurveOld: function (mi, ma) { 818 var i, t, t0, d, 819 x, y, x0, y0, top, depth, 820 MAX_DEPTH, MAX_XDIST, MAX_YDIST, 821 suspendUpdate = false, 822 po = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false), 823 dyadicStack = [], 824 depthStack = [], 825 pointStack = [], 826 divisors = [], 827 distOK = false, 828 j = 0, 829 distFromLine = function (p1, p2, p0) { 830 var lbda, d, 831 x0 = p0[1] - p1[1], 832 y0 = p0[2] - p1[2], 833 x1 = p2[0] - p1[1], 834 y1 = p2[1] - p1[2], 835 den = x1 * x1 + y1 * y1; 836 837 if (den >= Mat.eps) { 838 lbda = (x0 * x1 + y0 * y1) / den; 839 if (lbda > 0) { 840 if (lbda <= 1) { 841 x0 -= lbda * x1; 842 y0 -= lbda * y1; 843 // lbda = 1.0; 844 } else { 845 x0 -= x1; 846 y0 -= y1; 847 } 848 } 849 } 850 d = x0 * x0 + y0 * y0; 851 return Math.sqrt(d); 852 }; 853 854 JXG.deprecated('Curve.updateParametricCurveOld()'); 855 856 if (this.board.updateQuality === this.board.BOARD_QUALITY_LOW) { 857 MAX_DEPTH = 15; 858 MAX_XDIST = 10; // 10 859 MAX_YDIST = 10; // 10 860 } else { 861 MAX_DEPTH = 21; 862 MAX_XDIST = 0.7; // 0.7 863 MAX_YDIST = 0.7; // 0.7 864 } 865 866 divisors[0] = ma - mi; 867 for (i = 1; i < MAX_DEPTH; i++) { 868 divisors[i] = divisors[i - 1] * 0.5; 869 } 870 871 i = 1; 872 dyadicStack[0] = 1; 873 depthStack[0] = 0; 874 875 t = mi; 876 po.setCoordinates(Const.COORDS_BY_USER, [this.X(t, suspendUpdate), this.Y(t, suspendUpdate)], false); 877 878 // Now, there was a first call to the functions defining the curve. 879 // Defining elements like sliders have been evaluated. 880 // Therefore, we can set suspendUpdate to false, so that these defining elements 881 // need not be evaluated anymore for the rest of the plotting. 882 suspendUpdate = true; 883 x0 = po.scrCoords[1]; 884 y0 = po.scrCoords[2]; 885 t0 = t; 886 887 t = ma; 888 po.setCoordinates(Const.COORDS_BY_USER, [this.X(t, suspendUpdate), this.Y(t, suspendUpdate)], false); 889 x = po.scrCoords[1]; 890 y = po.scrCoords[2]; 891 892 pointStack[0] = [x, y]; 893 894 top = 1; 895 depth = 0; 896 897 this.points = []; 898 this.points[j++] = new Coords(Const.COORDS_BY_SCREEN, [x0, y0], this.board, false); 899 900 do { 901 distOK = this.isDistOK(x - x0, y - y0, MAX_XDIST, MAX_YDIST) || this.isSegmentOutside(x0, y0, x, y); 902 while (depth < MAX_DEPTH && (!distOK || depth < 6) && (depth <= 7 || this.isSegmentDefined(x0, y0, x, y))) { 903 // We jump out of the loop if 904 // * depth>=MAX_DEPTH or 905 // * (depth>=6 and distOK) or 906 // * (depth>7 and segment is not defined) 907 908 dyadicStack[top] = i; 909 depthStack[top] = depth; 910 pointStack[top] = [x, y]; 911 top += 1; 912 913 i = 2 * i - 1; 914 // Here, depth is increased and may reach MAX_DEPTH 915 depth++; 916 // In that case, t is undefined and we will see a jump in the curve. 917 t = mi + i * divisors[depth]; 918 919 po.setCoordinates(Const.COORDS_BY_USER, [this.X(t, suspendUpdate), this.Y(t, suspendUpdate)], false, true); 920 x = po.scrCoords[1]; 921 y = po.scrCoords[2]; 922 distOK = this.isDistOK(x - x0, y - y0, MAX_XDIST, MAX_YDIST) || this.isSegmentOutside(x0, y0, x, y); 923 } 924 925 if (j > 1) { 926 d = distFromLine(this.points[j - 2].scrCoords, [x, y], this.points[j - 1].scrCoords); 927 if (d < 0.015) { 928 j -= 1; 929 } 930 } 931 932 this.points[j] = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board, false); 933 j += 1; 934 935 x0 = x; 936 y0 = y; 937 t0 = t; 938 939 top -= 1; 940 x = pointStack[top][0]; 941 y = pointStack[top][1]; 942 depth = depthStack[top] + 1; 943 i = dyadicStack[top] * 2; 944 945 } while (top > 0 && j < 500000); 946 947 this.numberPoints = this.points.length; 948 949 return this; 950 }, 951 952 /** 953 * Crude and cheap test if the segment defined by the two points <tt>(x0, y0)</tt> and <tt>(x1, y1)</tt> is 954 * outside the viewport of the board. All parameters have to be given in screen coordinates. 955 * 956 * @private 957 * @param {Number} x0 958 * @param {Number} y0 959 * @param {Number} x1 960 * @param {Number} y1 961 * @returns {Boolean} <tt>true</tt> if the given segment is outside the visible area. 962 */ 963 isSegmentOutside: function (x0, y0, x1, y1) { 964 return (y0 < 0 && y1 < 0) || (y0 > this.board.canvasHeight && y1 > this.board.canvasHeight) || 965 (x0 < 0 && x1 < 0) || (x0 > this.board.canvasWidth && x1 > this.board.canvasWidth); 966 }, 967 968 /** 969 * Compares the absolute value of <tt>dx</tt> with <tt>MAXX</tt> and the absolute value of <tt>dy</tt> 970 * with <tt>MAXY</tt>. 971 * 972 * @private 973 * @param {Number} dx 974 * @param {Number} dy 975 * @param {Number} MAXX 976 * @param {Number} MAXY 977 * @returns {Boolean} <tt>true</tt>, if <tt>|dx| < MAXX</tt> and <tt>|dy| < MAXY</tt>. 978 */ 979 isDistOK: function (dx, dy, MAXX, MAXY) { 980 return (Math.abs(dx) < MAXX && Math.abs(dy) < MAXY) && !isNaN(dx + dy); 981 }, 982 983 /** 984 * @private 985 */ 986 isSegmentDefined: function (x0, y0, x1, y1) { 987 return !(isNaN(x0 + y0) && isNaN(x1 + y1)); 988 }, 989 990 /** 991 * Add a point to the curve plot. If the new point is too close to the previously inserted point, 992 * it is skipped. 993 * Used in {@link JXG.Curve._plotRecursive}. 994 * 995 * @private 996 * @param {JXG.Coords} pnt Coords to add to the list of points 997 */ 998 _insertPoint: function (pnt) { 999 var lastReal = !isNaN(this._lastCrds[1] + this._lastCrds[2]), // The last point was real 1000 newReal = !isNaN(pnt.scrCoords[1] + pnt.scrCoords[2]), // New point is real point 1001 cw = this.board.canvasWidth, 1002 ch = this.board.canvasHeight, 1003 off = 500; 1004 1005 newReal = newReal && 1006 (pnt.scrCoords[1] > -off && pnt.scrCoords[2] > -off && 1007 pnt.scrCoords[1] < cw + off && pnt.scrCoords[2] < ch + off); 1008 1009 /* 1010 * Prevents two consecutive NaNs or points wich are too close 1011 */ 1012 if ((!newReal && lastReal) || 1013 (newReal && (!lastReal || 1014 Math.abs(pnt.scrCoords[1] - this._lastCrds[1]) > 0.7 || 1015 Math.abs(pnt.scrCoords[2] - this._lastCrds[2]) > 0.7))) { 1016 this.points.push(pnt); 1017 this._lastCrds = pnt.copy('scrCoords'); 1018 } 1019 }, 1020 1021 /** 1022 * Find the intersection of the asymptote for e.g. a log function 1023 * with the canvas. 1024 * @private 1025 * @param {Array} asymptote Asymptote line in standard form 1026 * @param {Array} box Bounding box of the canavs 1027 * @param {Number} direction horizontal direction of the asymptote. If < 0 the asymptote 1028 * goes to the left, otherwise to the right. 1029 * @returns {Array} Homogeneous coordinate array of the intersection point. 1030 */ 1031 _intersectWithBorder: function(asymptote, box, direction) { 1032 var border, intersection, x, y; 1033 1034 if (direction <= 0) { // Intersect with left border 1035 border = [-box[0], 1, 0]; 1036 intersection = Mat.crossProduct(border, asymptote); 1037 if (intersection[0] !== 0.0) { 1038 x = intersection[1] / intersection[0]; 1039 y = intersection[2] / intersection[0]; 1040 } else { 1041 y = Infinity; 1042 } 1043 1044 if (y < box[3]) { // Intersect with bottom border 1045 border = [-box[3], 0, 1]; 1046 intersection = Mat.crossProduct(border, asymptote); 1047 if (intersection[0] !== 0.0) { 1048 x = intersection[1] / intersection[0]; 1049 y = intersection[2] / intersection[0]; 1050 } else { 1051 x = Infinity; 1052 } 1053 } else if (y > box[1]) { // Intersect with top border 1054 border = [-box[1], 0, 1]; 1055 intersection = Mat.crossProduct(border, asymptote); 1056 if (intersection[0] !== 0.0) { 1057 x = intersection[1] / intersection[0]; 1058 y = intersection[2] / intersection[0]; 1059 } else { 1060 x = Infinity; 1061 } 1062 } 1063 } else { // Intersect with right border 1064 border = [-box[2], 1, 0]; 1065 intersection = Mat.crossProduct(border, asymptote); 1066 if (intersection[0] !== 0.0) { 1067 x = intersection[1] / intersection[0]; 1068 y = intersection[2] / intersection[0]; 1069 } else { 1070 y = Infinity; 1071 } 1072 1073 if (y < box[3]) { // Intersect with bottom border 1074 border = [-box[3], 0, 1]; 1075 intersection = Mat.crossProduct(border, asymptote); 1076 if (intersection[0] !== 0.0) { 1077 x = intersection[1] / intersection[0]; 1078 y = intersection[2] / intersection[0]; 1079 } else { 1080 x = Infinity; 1081 } 1082 } else if (y > box[1]) { // Intersect with top border 1083 border = [-box[1], 0, 1]; 1084 intersection = Mat.crossProduct(border, asymptote); 1085 if (intersection[0] !== 0.0) { 1086 x = intersection[1] / intersection[0]; 1087 y = intersection[2] / intersection[0]; 1088 } else { 1089 x = Infinity; 1090 } 1091 } 1092 } 1093 return [1, x, y]; 1094 }, 1095 1096 /** 1097 * Investigate a function term at the bounds of intervals where 1098 * the function is not defined, e.g. log(x) at x = 0. 1099 * 1100 * c is inbetween a and b 1101 * @private 1102 * @param {Array} a Screen coordinates of the left interval bound 1103 * @param {Array} b Screen coordinates of the right interval bound 1104 * @param {Array} c Screen coordinates of the bisection point at (ta + tb) / 2 1105 * @param {Number} ta Parameter which evaluates to a, i.e. [1, X(ta), Y(ta)] = a in screen coordinates 1106 * @param {Number} tb Parameter which evaluates to b, i.e. [1, X(tb), Y(tb)] = b in screen coordinates 1107 * @param {Number} tc (ta + tb) / 2 = tc. Parameter which evaluates to b, i.e. [1, X(tc), Y(tc)] = c in screen coordinates 1108 * @param {Number} depth Actual recursion depth. The recursion stops if depth is equal to 0. 1109 * @returns {JXG.Boolean} true if the point is inserted and the recursion should stop, false otherwise. 1110 */ 1111 _borderCase: function (a, b, c, ta, tb, tc, depth) { 1112 var t, pnt, p, 1113 p_good = null, 1114 j, 1115 max_it = 30, 1116 is_undef = false, 1117 t_nan, t_real, t_real2, 1118 box, 1119 vx, vy, vx2, vy2, dx, dy, 1120 x, y, 1121 asymptote, border, intersection; 1122 1123 1124 if (depth <= 1) { 1125 pnt = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false); 1126 j = 0; 1127 // Bisect a, b and c until the point t_real is inside of the definition interval 1128 // and as close as possible at the boundary. 1129 // t_real2 is the second closest point. 1130 do { 1131 // There are four cases: 1132 // a | c | b 1133 // --------------- 1134 // inf | R | R 1135 // R | R | inf 1136 // inf | inf | R 1137 // R | inf | inf 1138 // 1139 if (isNaN(a[1] + a[2]) && !isNaN(c[1] + c[2])) { 1140 t_nan = ta; 1141 t_real = tc; 1142 t_real2 = tb; 1143 } else if (isNaN(b[1] + b[2]) && !isNaN(c[1] + c[2])) { 1144 t_nan = tb; 1145 t_real = tc; 1146 t_real2 = ta; 1147 } else if (isNaN(c[1] + c[2]) && !isNaN(b[1] + b[2])) { 1148 t_nan = tc; 1149 t_real = tb; 1150 t_real2 = tb + (tb - tc); 1151 } else if (isNaN(c[1] + c[2]) && !isNaN(a[1] + a[2])) { 1152 t_nan = tc; 1153 t_real = ta; 1154 t_real2 = ta - (tc - ta); 1155 } else { 1156 return false; 1157 } 1158 t = 0.5 * (t_nan + t_real); 1159 pnt.setCoordinates(Const.COORDS_BY_USER, [this.X(t, true), this.Y(t, true)], false); 1160 p = pnt.usrCoords; 1161 1162 is_undef = isNaN(p[1] + p[2]); 1163 if (is_undef) { 1164 t_nan = t; 1165 } else { 1166 t_real2 = t_real; 1167 t_real = t; 1168 } 1169 ++j; 1170 } while (is_undef && j < max_it); 1171 1172 // If bisection was successful, take this point. 1173 // Usefule only for general curves, for function graph 1174 // the code below overwrite p_good from here. 1175 if (j < max_it) { 1176 p_good = p.slice(); 1177 c = p.slice(); 1178 t_real = t; 1179 } 1180 1181 // OK, bisection has been done now. 1182 // t_real contains the closest inner point to the border of the interval we could find. 1183 // t_real2 is the second nearest point to this boundary. 1184 // Now we approximate the derivative by computing the slope of the line through these two points 1185 // and test if it is "infinite", i.e larger than 400 in absolute values. 1186 // 1187 vx = this.X(t_real, true) ; 1188 vx2 = this.X(t_real2, true) ; 1189 dx = (vx - vx2) / (t_real - t_real2); 1190 vy = this.Y(t_real, true) ; 1191 vy2 = this.Y(t_real2, true) ; 1192 dy = (vy - vy2) / (t_real - t_real2); 1193 1194 // If the derivatives are large enough we draw the asymptote. 1195 box = this.board.getBoundingBox(); 1196 if (Math.sqrt(dx * dx + dy * dy) > 500.0) { 1197 1198 // The asymptote is a line of the form 1199 // [c, a, b] = [dx * vy - dy * vx, dy, -dx] 1200 // Now we have to find the intersection with the correct canvas border. 1201 asymptote = [dx * vy - dy * vx, dy, -dx]; 1202 1203 p_good = this._intersectWithBorder(asymptote, box, vx - vx2); 1204 } 1205 1206 if (p_good !== null) { 1207 this._insertPoint(new Coords(Const.COORDS_BY_USER, p_good, this.board, false)); 1208 return true; 1209 } 1210 } 1211 return false; 1212 }, 1213 1214 /** 1215 * Compute distances in screen coordinates between the points ab, 1216 * ac, cb, and cd, where d = (a + b)/2. 1217 * cd is used for the smoothness test, ab, ac, cb are used to detect jumps, cusps and poles. 1218 * 1219 * @private 1220 * @param {Array} a Screen coordinates of the left interval bound 1221 * @param {Array} b Screen coordinates of the right interval bound 1222 * @param {Array} c Screen coordinates of the bisection point at (ta + tb) / 2 1223 * @returns {Array} array of distances in screen coordinates between: ab, ac, cb, and cd. 1224 */ 1225 _triangleDists: function (a, b, c) { 1226 var d, d_ab, d_ac, d_cb, d_cd; 1227 1228 d = [a[0] * b[0], (a[1] + b[1]) * 0.5, (a[2] + b[2]) * 0.5]; 1229 1230 d_ab = Geometry.distance(a, b, 3); 1231 d_ac = Geometry.distance(a, c, 3); 1232 d_cb = Geometry.distance(c, b, 3); 1233 d_cd = Geometry.distance(c, d, 3); 1234 1235 return [d_ab, d_ac, d_cb, d_cd]; 1236 }, 1237 1238 /** 1239 * Test if the function is undefined on an interval: 1240 * If the interval borders a and b are undefined, 20 random values 1241 * are tested if they are undefined, too. 1242 * Only if all values are undefined, we declare the function to be undefined in this interval. 1243 * 1244 * @private 1245 * @param {Array} a Screen coordinates of the left interval bound 1246 * @param {Number} ta Parameter which evaluates to a, i.e. [1, X(ta), Y(ta)] = a in screen coordinates 1247 * @param {Array} b Screen coordinates of the right interval bound 1248 * @param {Number} tb Parameter which evaluates to b, i.e. [1, X(tb), Y(tb)] = b in screen coordinates 1249 */ 1250 _isUndefined: function (a, ta, b, tb) { 1251 var t, i, pnt; 1252 1253 if (!isNaN(a[1] + a[2]) || !isNaN(b[1] + b[2])) { 1254 return false; 1255 } 1256 1257 pnt = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false); 1258 1259 for (i = 0; i < 20; ++i) { 1260 t = ta + Math.random() * (tb - ta); 1261 pnt.setCoordinates(Const.COORDS_BY_USER, [this.X(t, true), this.Y(t, true)], false); 1262 if (!isNaN(pnt.scrCoords[0] + pnt.scrCoords[1] + pnt.scrCoords[2])) { 1263 return false; 1264 } 1265 } 1266 1267 return true; 1268 }, 1269 1270 /** 1271 * Decide if a path segment is too far from the canvas that we do not need to draw it. 1272 * @param {Array} a Screen coordinates of the start point of the segment 1273 * @param {Array} ta Curve parameter of a. 1274 * @param {Array} b Screen coordinates of the end point of the segment 1275 * @param {Array} tb Curve parameter of b. 1276 * @returns {Boolean} True if the segment is too far away from the canvas, false otherwise. 1277 */ 1278 _isOutside: function (a, ta, b, tb) { 1279 var off = 500, 1280 cw = this.board.canvasWidth, 1281 ch = this.board.canvasHeight; 1282 1283 return !!((a[1] < -off && b[1] < -off) || 1284 (a[2] < -off && b[2] < -off) || 1285 (a[1] > cw + off && b[1] > cw + off) || 1286 (a[2] > ch + off && b[2] > ch + off)); 1287 }, 1288 1289 /** 1290 * Recursive interval bisection algorithm for curve plotting. 1291 * Used in {@link JXG.Curve.updateParametricCurve}. 1292 * @private 1293 * @param {Array} a Screen coordinates of the left interval bound 1294 * @param {Number} ta Parameter which evaluates to a, i.e. [1, X(ta), Y(ta)] = a in screen coordinates 1295 * @param {Array} b Screen coordinates of the right interval bound 1296 * @param {Number} tb Parameter which evaluates to b, i.e. [1, X(tb), Y(tb)] = b in screen coordinates 1297 * @param {Number} depth Actual recursion depth. The recursion stops if depth is equal to 0. 1298 * @param {Number} delta If the distance of the bisection point at (ta + tb) / 2 from the point (a + b) / 2 is less then delta, 1299 * the segment [a,b] is regarded as straight line. 1300 * @returns {JXG.Curve} Reference to the curve object. 1301 */ 1302 _plotRecursive: function (a, ta, b, tb, depth, delta) { 1303 var tc, c, 1304 ds, mindepth = 0, 1305 isSmooth, isJump, isCusp, 1306 cusp_threshold = 0.5, 1307 pnt = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false); 1308 1309 if (this.numberPoints > 65536) { 1310 return; 1311 } 1312 1313 // Test if the function is undefined on an interval 1314 if (depth < this.nanLevel && this._isUndefined(a, ta, b, tb)) { 1315 return this; 1316 } 1317 1318 if (depth < this.nanLevel && this._isOutside(a, ta, b, tb)) { 1319 return this; 1320 } 1321 1322 tc = 0.5 * (ta + tb); 1323 pnt.setCoordinates(Const.COORDS_BY_USER, [this.X(tc, true), this.Y(tc, true)], false); 1324 c = pnt.scrCoords; 1325 1326 if (this._borderCase(a, b, c, ta, tb, tc, depth)) { 1327 return this; 1328 } 1329 1330 ds = this._triangleDists(a, b, c); // returns [d_ab, d_ac, d_cb, d_cd] 1331 isSmooth = (depth < this.smoothLevel) && (ds[3] < delta); 1332 1333 isJump = (depth < this.jumpLevel) && 1334 ((ds[2] > 0.99 * ds[0]) || (ds[1] > 0.99 * ds[0]) || 1335 ds[0] === Infinity || ds[1] === Infinity || ds[2] === Infinity); 1336 isCusp = (depth < this.smoothLevel + 2) && (ds[0] < cusp_threshold * (ds[1] + ds[2])); 1337 1338 if (isCusp) { 1339 mindepth = 0; 1340 isSmooth = false; 1341 } 1342 1343 --depth; 1344 1345 if (isJump) { 1346 this._insertPoint(new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], this.board, false)); 1347 } else if (depth <= mindepth || isSmooth) { 1348 this._insertPoint(pnt); 1349 //if (this._borderCase(a, b, c, ta, tb, tc, depth)) {} 1350 } else { 1351 this._plotRecursive(a, ta, c, tc, depth, delta); 1352 this._insertPoint(pnt); 1353 this._plotRecursive(c, tc, b, tb, depth, delta); 1354 } 1355 1356 return this; 1357 }, 1358 1359 /** 1360 * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#doadvancedplot} is <tt>true</tt>. 1361 * @param {Number} mi Left bound of curve 1362 * @param {Number} ma Right bound of curve 1363 * @returns {JXG.Curve} Reference to the curve object. 1364 */ 1365 updateParametricCurve: function (mi, ma) { 1366 var ta, tb, a, b, 1367 suspendUpdate = false, 1368 pa = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false), 1369 pb = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false), 1370 depth, delta; 1371 if (this.board.updateQuality === this.board.BOARD_QUALITY_LOW) { 1372 depth = 13; 1373 delta = 2; 1374 this.smoothLevel = depth - 7; 1375 this.jumpLevel = 5; 1376 } else { 1377 depth = 17; 1378 delta = 2; 1379 this.smoothLevel = depth - 7; // 9 1380 this.jumpLevel = 3; 1381 } 1382 this.nanLevel = depth - 4; 1383 1384 this.points = []; 1385 1386 ta = mi; 1387 pa.setCoordinates(Const.COORDS_BY_USER, [this.X(ta, suspendUpdate), this.Y(ta, suspendUpdate)], false); 1388 a = pa.copy('scrCoords'); 1389 suspendUpdate = true; 1390 1391 tb = ma; 1392 pb.setCoordinates(Const.COORDS_BY_USER, [this.X(tb, suspendUpdate), this.Y(tb, suspendUpdate)], false); 1393 b = pb.copy('scrCoords'); 1394 1395 this.points.push(pa); 1396 this._lastCrds = pa.copy('scrCoords'); //Used in _insertPoint 1397 this._plotRecursive(a, ta, b, tb, depth, delta); 1398 this.points.push(pb); 1399 1400 this.numberPoints = this.points.length; 1401 1402 return this; 1403 }, 1404 1405 /** 1406 * Applies the transformations of the curve to the given point <tt>p</tt>. 1407 * Before using it, {@link JXG.Curve#updateTransformMatrix} has to be called. 1408 * @param {JXG.Point} p 1409 * @returns {JXG.Point} The given point. 1410 */ 1411 updateTransform: function (p) { 1412 var c, 1413 len = this.transformations.length; 1414 1415 if (len > 0) { 1416 c = Mat.matVecMult(this.transformMat, p.usrCoords); 1417 p.setCoordinates(Const.COORDS_BY_USER, [c[1], c[2]], false, true); 1418 } 1419 1420 return p; 1421 }, 1422 1423 /** 1424 * Add transformations to this curve. 1425 * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of {@link JXG.Transformation}s. 1426 * @returns {JXG.Curve} Reference to the curve object. 1427 */ 1428 addTransform: function (transform) { 1429 var i, 1430 list = Type.isArray(transform) ? transform : [transform], 1431 len = list.length; 1432 1433 for (i = 0; i < len; i++) { 1434 this.transformations.push(list[i]); 1435 } 1436 1437 return this; 1438 }, 1439 1440 /** 1441 * Generate the method curve.X() in case curve.dataX is an array 1442 * and generate the method curve.Y() in case curve.dataY is an array. 1443 * @private 1444 * @param {String} which Either 'X' or 'Y' 1445 * @returns {function} 1446 **/ 1447 interpolationFunctionFromArray: function (which) { 1448 var data = 'data' + which; 1449 1450 return function (t, suspendedUpdate) { 1451 var i, j, f1, f2, z, t0, t1, 1452 arr = this[data], 1453 len = arr.length, 1454 f = []; 1455 1456 if (isNaN(t)) { 1457 return NaN; 1458 } 1459 1460 if (t < 0) { 1461 if (Type.isFunction(arr[0])) { 1462 return arr[0](); 1463 } 1464 1465 return arr[0]; 1466 } 1467 1468 if (this.bezierDegree === 3) { 1469 len /= 3; 1470 if (t >= len) { 1471 if (Type.isFunction(arr[arr.length - 1])) { 1472 return arr[arr.length - 1](); 1473 } 1474 1475 return arr[arr.length - 1]; 1476 } 1477 1478 i = Math.floor(t) * 3; 1479 t0 = t % 1; 1480 t1 = 1 - t0; 1481 1482 for (j = 0; j < 4; j++) { 1483 if (Type.isFunction(arr[i + j])) { 1484 f[j] = arr[i + j](); 1485 } else { 1486 f[j] = arr[i + j]; 1487 } 1488 } 1489 1490 return t1 * t1 * (t1 * f[0] + 3 * t0 * f[1]) + (3 * t1 * f[2] + t0 * f[3]) * t0 * t0; 1491 } 1492 1493 if (t > len - 2) { 1494 i = len - 2; 1495 } else { 1496 i = parseInt(Math.floor(t), 10); 1497 } 1498 1499 if (i === t) { 1500 if (Type.isFunction(arr[i])) { 1501 return arr[i](); 1502 } 1503 return arr[i]; 1504 } 1505 1506 for (j = 0; j < 2; j++) { 1507 if (Type.isFunction(arr[i + j])) { 1508 f[j] = arr[i + j](); 1509 } else { 1510 f[j] = arr[i + j]; 1511 } 1512 } 1513 return f[0] + (f[1] - f[0]) * (t - i); 1514 }; 1515 }, 1516 1517 /** 1518 * Converts the GEONExT syntax of the defining function term into JavaScript. 1519 * New methods X() and Y() for the Curve object are generated, further 1520 * new methods for minX() and maxX(). 1521 * @see JXG.GeonextParser.geonext2JS. 1522 */ 1523 generateTerm: function (varname, xterm, yterm, mi, ma) { 1524 var fx, fy; 1525 1526 // Generate the methods X() and Y() 1527 if (Type.isArray(xterm)) { 1528 // Discrete data 1529 this.dataX = xterm; 1530 1531 this.numberPoints = this.dataX.length; 1532 this.X = this.interpolationFunctionFromArray('X'); 1533 this.visProp.curvetype = 'plot'; 1534 this.isDraggable = true; 1535 } else { 1536 // Continuous data 1537 this.X = Type.createFunction(xterm, this.board, varname); 1538 if (Type.isString(xterm)) { 1539 this.visProp.curvetype = 'functiongraph'; 1540 } else if (Type.isFunction(xterm) || Type.isNumber(xterm)) { 1541 this.visProp.curvetype = 'parameter'; 1542 } 1543 1544 this.isDraggable = true; 1545 } 1546 1547 if (Type.isArray(yterm)) { 1548 this.dataY = yterm; 1549 this.Y = this.interpolationFunctionFromArray('Y'); 1550 } else { 1551 this.Y = Type.createFunction(yterm, this.board, varname); 1552 } 1553 1554 /** 1555 * Polar form 1556 * Input data is function xterm() and offset coordinates yterm 1557 */ 1558 if (Type.isFunction(xterm) && Type.isArray(yterm)) { 1559 // Xoffset, Yoffset 1560 fx = Type.createFunction(yterm[0], this.board, ''); 1561 fy = Type.createFunction(yterm[1], this.board, ''); 1562 1563 this.X = function (phi) { 1564 return xterm(phi) * Math.cos(phi) + fx(); 1565 }; 1566 1567 this.Y = function (phi) { 1568 return xterm(phi) * Math.sin(phi) + fy(); 1569 }; 1570 1571 this.visProp.curvetype = 'polar'; 1572 } 1573 1574 // Set the bounds lower bound 1575 if (Type.exists(mi)) { 1576 this.minX = Type.createFunction(mi, this.board, ''); 1577 } 1578 if (Type.exists(ma)) { 1579 this.maxX = Type.createFunction(ma, this.board, ''); 1580 } 1581 }, 1582 1583 /** 1584 * Finds dependencies in a given term and notifies the parents by adding the 1585 * dependent object to the found objects child elements. 1586 * @param {String} contentStr String containing dependencies for the given object. 1587 */ 1588 notifyParents: function (contentStr) { 1589 var fstr, dep, 1590 isJessieCode = false; 1591 1592 // Read dependencies found by the JessieCode parser 1593 for (fstr in {'xterm': 1, 'yterm': 1}) { 1594 if (this.hasOwnProperty(fstr) && this[fstr].origin) { 1595 isJessieCode = true; 1596 for (dep in this[fstr].origin.deps) { 1597 if (this[fstr].origin.deps.hasOwnProperty(dep)) { 1598 this[fstr].origin.deps[dep].addChild(this); 1599 } 1600 } 1601 } 1602 } 1603 1604 if (!isJessieCode) { 1605 GeonextParser.findDependencies(this, contentStr, this.board); 1606 } 1607 }, 1608 1609 // documented in geometry element 1610 getLabelAnchor: function () { 1611 var c, x, y, 1612 ax = 0.05 * this.board.canvasWidth, 1613 ay = 0.05 * this.board.canvasHeight, 1614 bx = 0.95 * this.board.canvasWidth, 1615 by = 0.95 * this.board.canvasHeight; 1616 1617 switch (Type.evaluate(this.visProp.label.position)) { 1618 case 'ulft': 1619 x = ax; 1620 y = ay; 1621 break; 1622 case 'llft': 1623 x = ax; 1624 y = by; 1625 break; 1626 case 'rt': 1627 x = bx; 1628 y = 0.5 * by; 1629 break; 1630 case 'lrt': 1631 x = bx; 1632 y = by; 1633 break; 1634 case 'urt': 1635 x = bx; 1636 y = ay; 1637 break; 1638 case 'top': 1639 x = 0.5 * bx; 1640 y = ay; 1641 break; 1642 case 'bot': 1643 x = 0.5 * bx; 1644 y = by; 1645 break; 1646 default: 1647 // includes case 'lft' 1648 x = ax; 1649 y = 0.5 * by; 1650 } 1651 1652 c = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board, false); 1653 return Geometry.projectCoordsToCurve(c.usrCoords[1], c.usrCoords[2], 0, this, this.board)[0]; 1654 }, 1655 1656 // documented in geometry element 1657 cloneToBackground: function () { 1658 var er, 1659 copy = { 1660 id: this.id + 'T' + this.numTraces, 1661 elementClass: Const.OBJECT_CLASS_CURVE, 1662 1663 points: this.points.slice(0), 1664 bezierDegree: this.bezierDegree, 1665 numberPoints: this.numberPoints, 1666 board: this.board, 1667 visProp: Type.deepCopy(this.visProp, this.visProp.traceattributes, true) 1668 }; 1669 1670 copy.visProp.layer = this.board.options.layer.trace; 1671 copy.visProp.curvetype = this.visProp.curvetype; 1672 this.numTraces++; 1673 1674 Type.clearVisPropOld(copy); 1675 1676 er = this.board.renderer.enhancedRendering; 1677 this.board.renderer.enhancedRendering = true; 1678 this.board.renderer.drawCurve(copy); 1679 this.board.renderer.enhancedRendering = er; 1680 this.traces[copy.id] = copy.rendNode; 1681 1682 return this; 1683 }, 1684 1685 // already documented in GeometryElement 1686 bounds: function () { 1687 var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity, 1688 l = this.points.length, i; 1689 1690 if (this.bezierDegree === 3) { 1691 // Add methods X(), Y() 1692 for (i = 0; i < l; i++) { 1693 this.points[i].X = Type.bind(function() { return this.usrCoords[1]; }, this.points[i]); 1694 this.points[i].Y = Type.bind(function() { return this.usrCoords[2]; }, this.points[i]); 1695 } 1696 var bezier = Numerics.bezier(this.points); 1697 var up = bezier[3](); 1698 minX = Numerics.fminbr(function(t) { return bezier[0](t); }, [0, up]); 1699 maxX = Numerics.fminbr(function(t) { return -bezier[0](t); }, [0, up]); 1700 minY = Numerics.fminbr(function(t) { return bezier[1](t); }, [0, up]); 1701 maxY = Numerics.fminbr(function(t) { return -bezier[1](t); }, [0, up]); 1702 1703 minX = bezier[0](minX); 1704 maxX = bezier[0](maxX); 1705 minY = bezier[1](minY); 1706 maxY = bezier[1](maxY); 1707 return [minX, maxY, maxX, minY]; 1708 } 1709 1710 // Linear segments 1711 for (i = 0; i < l; i++) { 1712 if (minX > this.points[i].usrCoords[1]) { 1713 minX = this.points[i].usrCoords[1]; 1714 } 1715 1716 if (maxX < this.points[i].usrCoords[1]) { 1717 maxX = this.points[i].usrCoords[1]; 1718 } 1719 1720 if (minY > this.points[i].usrCoords[2]) { 1721 minY = this.points[i].usrCoords[2]; 1722 } 1723 1724 if (maxY < this.points[i].usrCoords[2]) { 1725 maxY = this.points[i].usrCoords[2]; 1726 } 1727 } 1728 1729 return [minX, maxY, maxX, minY]; 1730 }, 1731 1732 // documented in element.js 1733 getParents: function () { 1734 var p = [this.xterm, this.yterm, this.minX(), this.maxX()]; 1735 1736 if (this.parents.length !== 0) { 1737 p = this.parents; 1738 } 1739 1740 return p; 1741 } 1742 }); 1743 1744 1745 /** 1746 * @class This element is used to provide a constructor for curve, which is just a wrapper for element {@link Curve}. 1747 * 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]. 1748 * <p> 1749 * The following types of curves can be plotted: 1750 * <ul> 1751 * <li> parametric curves: t mapsto (x(t),y(t)), where x() and y() are univariate functions. 1752 * <li> polar curves: curves commonly written with polar equations like spirals and cardioids. 1753 * <li> data plots: plot linbe segments through a given list of coordinates. 1754 * </ul> 1755 * @pseudo 1756 * @description 1757 * @name Curve 1758 * @augments JXG.Curve 1759 * @constructor 1760 * @type JXG.Curve 1761 * 1762 * @param {function,number_function,number_function,number_function,number} x,y,a_,b_ Parent elements for Parametric Curves. 1763 * <p> 1764 * x describes the x-coordinate of the curve. It may be a function term in one variable, e.g. x(t). 1765 * In case of x being of type number, x(t) is set to a constant function. 1766 * this function at the values of the array. 1767 * </p> 1768 * <p> 1769 * y describes the y-coordinate of the curve. In case of a number, y(t) is set to the constant function 1770 * returning this number. 1771 * </p> 1772 * <p> 1773 * Further parameters are an optional number or function for the left interval border a, 1774 * and an optional number or function for the right interval border b. 1775 * </p> 1776 * <p> 1777 * Default values are a=-10 and b=10. 1778 * </p> 1779 * @param {array_array,function,number} x,y Parent elements for Data Plots. 1780 * <p> 1781 * x and y are arrays contining the x and y coordinates of the data points which are connected by 1782 * line segments. The individual entries of x and y may also be functions. 1783 * In case of x being an array the curve type is data plot, regardless of the second parameter and 1784 * if additionally the second parameter y is a function term the data plot evaluates. 1785 * </p> 1786 * @param {function_array,function,number_function,number_function,number} r,offset_,a_,b_ Parent elements for Polar Curves. 1787 * <p> 1788 * The first parameter is a function term r(phi) describing the polar curve. 1789 * </p> 1790 * <p> 1791 * The second parameter is the offset of the curve. It has to be 1792 * an array containing numbers or functions describing the offset. Default value is the origin [0,0]. 1793 * </p> 1794 * <p> 1795 * Further parameters are an optional number or function for the left interval border a, 1796 * and an optional number or function for the right interval border b. 1797 * </p> 1798 * <p> 1799 * Default values are a=-10 and b=10. 1800 * </p> 1801 * @see JXG.Curve 1802 * @example 1803 * // Parametric curve 1804 * // Create a curve of the form (t-sin(t), 1-cos(t), i.e. 1805 * // the cycloid curve. 1806 * var graph = board.create('curve', 1807 * [function(t){ return t-Math.sin(t);}, 1808 * function(t){ return 1-Math.cos(t);}, 1809 * 0, 2*Math.PI] 1810 * ); 1811 * </pre><div class="jxgbox" id="af9f818b-f3b6-4c4d-8c4c-e4a4078b726d" style="width: 300px; height: 300px;"></div> 1812 * <script type="text/javascript"> 1813 * var c1_board = JXG.JSXGraph.initBoard('af9f818b-f3b6-4c4d-8c4c-e4a4078b726d', {boundingbox: [-1, 5, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1814 * var graph1 = c1_board.create('curve', [function(t){ return t-Math.sin(t);},function(t){ return 1-Math.cos(t);},0, 2*Math.PI]); 1815 * </script><pre> 1816 * @example 1817 * // Data plots 1818 * // Connect a set of points given by coordinates with dashed line segments. 1819 * // The x- and y-coordinates of the points are given in two separate 1820 * // arrays. 1821 * var x = [0,1,2,3,4,5,6,7,8,9]; 1822 * var y = [9.2,1.3,7.2,-1.2,4.0,5.3,0.2,6.5,1.1,0.0]; 1823 * var graph = board.create('curve', [x,y], {dash:2}); 1824 * </pre><div class="jxgbox" id="7dcbb00e-b6ff-481d-b4a8-887f5d8c6a83" style="width: 300px; height: 300px;"></div> 1825 * <script type="text/javascript"> 1826 * var c3_board = JXG.JSXGraph.initBoard('7dcbb00e-b6ff-481d-b4a8-887f5d8c6a83', {boundingbox: [-1,10,10,-1], axis: true, showcopyright: false, shownavigation: false}); 1827 * var x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 1828 * var y = [9.2, 1.3, 7.2, -1.2, 4.0, 5.3, 0.2, 6.5, 1.1, 0.0]; 1829 * var graph3 = c3_board.create('curve', [x,y], {dash:2}); 1830 * </script><pre> 1831 * @example 1832 * // Polar plot 1833 * // Create a curve with the equation r(phi)= a*(1+phi), i.e. 1834 * // a cardioid. 1835 * var a = board.create('slider',[[0,2],[2,2],[0,1,2]]); 1836 * var graph = board.create('curve', 1837 * [function(phi){ return a.Value()*(1-Math.cos(phi));}, 1838 * [1,0], 1839 * 0, 2*Math.PI] 1840 * ); 1841 * </pre><div class="jxgbox" id="d0bc7a2a-8124-45ca-a6e7-142321a8f8c2" style="width: 300px; height: 300px;"></div> 1842 * <script type="text/javascript"> 1843 * var c2_board = JXG.JSXGraph.initBoard('d0bc7a2a-8124-45ca-a6e7-142321a8f8c2', {boundingbox: [-3,3,3,-3], axis: true, showcopyright: false, shownavigation: false}); 1844 * var a = c2_board.create('slider',[[0,2],[2,2],[0,1,2]]); 1845 * var graph2 = c2_board.create('curve', [function(phi){ return a.Value()*(1-Math.cos(phi));}, [1,0], 0, 2*Math.PI]); 1846 * </script><pre> 1847 * 1848 * @example 1849 * // Draggable Bezier curve 1850 * var col, p, c; 1851 * col = 'blue'; 1852 * p = []; 1853 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 1854 * p.push(board.create('point',[1, 2.5 ], {size: 5, strokeColor:col, fillColor:col})); 1855 * p.push(board.create('point',[-1, -2.5 ], {size: 5, strokeColor:col, fillColor:col})); 1856 * p.push(board.create('point',[2, -2], {size: 5, strokeColor:col, fillColor:col})); 1857 * 1858 * c = board.create('curve', JXG.Math.Numerics.bezier(p), 1859 * {strokeColor:'red', name:"curve", strokeWidth:5, fixed: false}); // Draggable curve 1860 * c.addParents(p); 1861 * </pre><div class="jxgbox" id="7bcc6280-f6eb-433e-8281-c837c3387849" style="width: 300px; height: 300px;"></div> 1862 * <script type="text/javascript"> 1863 * (function(){ 1864 * var board, col, p, c; 1865 * board = JXG.JSXGraph.initBoard('7bcc6280-f6eb-433e-8281-c837c3387849', {boundingbox: [-3,3,3,-3], axis: true, showcopyright: false, shownavigation: false}); 1866 * col = 'blue'; 1867 * p = []; 1868 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 1869 * p.push(board.create('point',[1, 2.5 ], {size: 5, strokeColor:col, fillColor:col})); 1870 * p.push(board.create('point',[-1, -2.5 ], {size: 5, strokeColor:col, fillColor:col})); 1871 * p.push(board.create('point',[2, -2], {size: 5, strokeColor:col, fillColor:col})); 1872 * 1873 * c = board.create('curve', JXG.Math.Numerics.bezier(p), 1874 * {strokeColor:'red', name:"curve", strokeWidth:5, fixed: false}); // Draggable curve 1875 * c.addParents(p); 1876 * })(); 1877 * </script><pre> 1878 * 1879 * 1880 */ 1881 JXG.createCurve = function (board, parents, attributes) { 1882 var attr = Type.copyAttributes(attributes, board.options, 'curve'); 1883 return new JXG.Curve(board, ['x'].concat(parents), attr); 1884 }; 1885 1886 JXG.registerElement('curve', JXG.createCurve); 1887 1888 /** 1889 * @class This element is used to provide a constructor for functiongraph, 1890 * which is just a wrapper for element {@link Curve} with {@link JXG.Curve#X}() 1891 * set to x. The graph is drawn for x in the interval [a,b]. 1892 * @pseudo 1893 * @description 1894 * @name Functiongraph 1895 * @augments JXG.Curve 1896 * @constructor 1897 * @type JXG.Curve 1898 * @param {function_number,function_number,function} f,a_,b_ Parent elements are a function term f(x) describing the function graph. 1899 * <p> 1900 * Further, an optional number or function for the left interval border a, 1901 * and an optional number or function for the right interval border b. 1902 * <p> 1903 * Default values are a=-10 and b=10. 1904 * @see JXG.Curve 1905 * @example 1906 * // Create a function graph for f(x) = 0.5*x*x-2*x 1907 * var graph = board.create('functiongraph', 1908 * [function(x){ return 0.5*x*x-2*x;}, -2, 4] 1909 * ); 1910 * </pre><div class="jxgbox" id="efd432b5-23a3-4846-ac5b-b471e668b437" style="width: 300px; height: 300px;"></div> 1911 * <script type="text/javascript"> 1912 * var alex1_board = JXG.JSXGraph.initBoard('efd432b5-23a3-4846-ac5b-b471e668b437', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 1913 * var graph = alex1_board.create('functiongraph', [function(x){ return 0.5*x*x-2*x;}, -2, 4]); 1914 * </script><pre> 1915 * @example 1916 * // Create a function graph for f(x) = 0.5*x*x-2*x with variable interval 1917 * var s = board.create('slider',[[0,4],[3,4],[-2,4,5]]); 1918 * var graph = board.create('functiongraph', 1919 * [function(x){ return 0.5*x*x-2*x;}, 1920 * -2, 1921 * function(){return s.Value();}] 1922 * ); 1923 * </pre><div class="jxgbox" id="4a203a84-bde5-4371-ad56-44619690bb50" style="width: 300px; height: 300px;"></div> 1924 * <script type="text/javascript"> 1925 * var alex2_board = JXG.JSXGraph.initBoard('4a203a84-bde5-4371-ad56-44619690bb50', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 1926 * var s = alex2_board.create('slider',[[0,4],[3,4],[-2,4,5]]); 1927 * var graph = alex2_board.create('functiongraph', [function(x){ return 0.5*x*x-2*x;}, -2, function(){return s.Value();}]); 1928 * </script><pre> 1929 */ 1930 JXG.createFunctiongraph = function (board, parents, attributes) { 1931 var attr, 1932 par = ['x', 'x'].concat(parents); 1933 1934 attr = Type.copyAttributes(attributes, board.options, 'curve'); 1935 attr.curvetype = 'functiongraph'; 1936 return new JXG.Curve(board, par, attr); 1937 }; 1938 1939 JXG.registerElement('functiongraph', JXG.createFunctiongraph); 1940 JXG.registerElement('plot', JXG.createFunctiongraph); 1941 1942 /** 1943 * @class This element is used to provide a constructor for (natural) cubic spline curves. 1944 * Create a dynamic spline interpolated curve given by sample points p_1 to p_n. 1945 * @pseudo 1946 * @description 1947 * @name Spline 1948 * @augments JXG.Curve 1949 * @constructor 1950 * @type JXG.Curve 1951 * @param {JXG.Board} board Reference to the board the spline is drawn on. 1952 * @param {Array} parents Array of points the spline interpolates. This can be 1953 * <ul> 1954 * <li> an array of JXGGraph points</li> 1955 * <li> an array of coordinate pairs</li> 1956 * <li> an array of functions returning coordinate pairs</li> 1957 * <li> an array consisting of an array with x-coordinates and an array of y-coordinates</li> 1958 * </ul> 1959 * All individual entries of coordinates arrays may be numbers or functions returing numbers. 1960 * @param {Object} attributes Define color, width, ... of the spline 1961 * @returns {JXG.Curve} Returns reference to an object of type JXG.Curve. 1962 * @see JXG.Curve 1963 * @example 1964 * 1965 * var p = []; 1966 * p[0] = board.create('point', [-2,2], {size: 4, face: 'o'}); 1967 * p[1] = board.create('point', [0,-1], {size: 4, face: 'o'}); 1968 * p[2] = board.create('point', [2,0], {size: 4, face: 'o'}); 1969 * p[3] = board.create('point', [4,1], {size: 4, face: 'o'}); 1970 * 1971 * var c = board.create('spline', p, {strokeWidth:3}); 1972 * </pre><div id="6c197afc-e482-11e5-b1bf-901b0e1b8723" style="width: 300px; height: 300px;"></div> 1973 * <script type="text/javascript"> 1974 * (function() { 1975 * var board = JXG.JSXGraph.initBoard('6c197afc-e482-11e5-b1bf-901b0e1b8723', 1976 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1977 * 1978 * var p = []; 1979 * p[0] = board.create('point', [-2,2], {size: 4, face: 'o'}); 1980 * p[1] = board.create('point', [0,-1], {size: 4, face: 'o'}); 1981 * p[2] = board.create('point', [2,0], {size: 4, face: 'o'}); 1982 * p[3] = board.create('point', [4,1], {size: 4, face: 'o'}); 1983 * 1984 * var c = board.create('spline', p, {strokeWidth:3}); 1985 * })(); 1986 * 1987 * </script><pre> 1988 * 1989 */ 1990 JXG.createSpline = function (board, parents, attributes) { 1991 var el, f; 1992 1993 f = function () { 1994 var D, x = [], y = []; 1995 1996 return function (t, suspended) { 1997 var i, j, c; 1998 1999 if (!suspended) { 2000 x = []; 2001 y = []; 2002 2003 // given as [x[], y[]] 2004 if (parents.length === 2 && Type.isArray(parents[0]) && Type.isArray(parents[1]) && parents[0].length === parents[1].length) { 2005 for (i = 0; i < parents[0].length; i++) { 2006 if (Type.isFunction(parents[0][i])) { 2007 x.push(parents[0][i]()); 2008 } else { 2009 x.push(parents[0][i]); 2010 } 2011 2012 if (Type.isFunction(parents[1][i])) { 2013 y.push(parents[1][i]()); 2014 } else { 2015 y.push(parents[1][i]); 2016 } 2017 } 2018 } else { 2019 for (i = 0; i < parents.length; i++) { 2020 if (Type.isPoint(parents[i])) { 2021 x.push(parents[i].X()); 2022 y.push(parents[i].Y()); 2023 // given as [[x1,y1], [x2, y2], ...] 2024 } else if (Type.isArray(parents[i]) && parents[i].length === 2) { 2025 for (j = 0; j < parents.length; j++) { 2026 if (Type.isFunction(parents[j][0])) { 2027 x.push(parents[j][0]()); 2028 } else { 2029 x.push(parents[j][0]); 2030 } 2031 2032 if (Type.isFunction(parents[j][1])) { 2033 y.push(parents[j][1]()); 2034 } else { 2035 y.push(parents[j][1]); 2036 } 2037 } 2038 } else if (Type.isFunction(parents[i]) && parents[i]().length === 2) { 2039 c = parents[i](); 2040 x.push(c[0]); 2041 y.push(c[1]); 2042 } 2043 } 2044 } 2045 2046 // The array D has only to be calculated when the position of one or more sample point 2047 // changes. otherwise D is always the same for all points on the spline. 2048 D = Numerics.splineDef(x, y); 2049 } 2050 return Numerics.splineEval(t, x, y, D); 2051 }; 2052 }; 2053 2054 attributes = Type.copyAttributes(attributes, board.options, 'curve'); 2055 attributes.curvetype = 'functiongraph'; 2056 el = new JXG.Curve(board, ['x', 'x', f()], attributes); 2057 el.setParents(parents); 2058 el.elType = 'spline'; 2059 2060 return el; 2061 }; 2062 2063 /** 2064 * Register the element type spline at JSXGraph 2065 * @private 2066 */ 2067 JXG.registerElement('spline', JXG.createSpline); 2068 2069 /** 2070 * @class This element is used to provide a constructor for Riemann sums, which is realized as a special curve. 2071 * The returned element has the method Value() which returns the sum of the areas of the bars. 2072 * @pseudo 2073 * @description 2074 * @name Riemannsum 2075 * @augments JXG.Curve 2076 * @constructor 2077 * @type JXG.Curve 2078 * @param {function,array_number,function_string,function_function,number_function,number} f,n,type_,a_,b_ Parent elements of Riemannsum are a 2079 * Either a function term f(x) describing the function graph which is filled by the Riemann bars, or 2080 * an array consisting of two functions and the area between is filled by the Riemann bars. 2081 * <p> 2082 * n determines the number of bars, it is either a fixed number or a function. 2083 * <p> 2084 * type is a string or function returning one of the values: 'left', 'right', 'middle', 'lower', 'upper', 'random', 'simpson', or 'trapezodial'. 2085 * Default value is 'left'. 2086 * <p> 2087 * Further parameters are an optional number or function for the left interval border a, 2088 * and an optional number or function for the right interval border b. 2089 * <p> 2090 * Default values are a=-10 and b=10. 2091 * @see JXG.Curve 2092 * @example 2093 * // Create Riemann sums for f(x) = 0.5*x*x-2*x. 2094 * var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1}); 2095 * var f = function(x) { return 0.5*x*x-2*x; }; 2096 * var r = board.create('riemannsum', 2097 * [f, function(){return s.Value();}, 'upper', -2, 5], 2098 * {fillOpacity:0.4} 2099 * ); 2100 * var g = board.create('functiongraph',[f, -2, 5]); 2101 * var t = board.create('text',[-2,-2, function(){ return 'Sum=' + JXG.toFixed(r.Value(), 4); }]); 2102 * </pre><div class="jxgbox" id="940f40cc-2015-420d-9191-c5d83de988cf" style="width: 300px; height: 300px;"></div> 2103 * <script type="text/javascript"> 2104 * (function(){ 2105 * var board = JXG.JSXGraph.initBoard('940f40cc-2015-420d-9191-c5d83de988cf', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 2106 * var f = function(x) { return 0.5*x*x-2*x; }; 2107 * var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1}); 2108 * var r = board.create('riemannsum', [f, function(){return s.Value();}, 'upper', -2, 5], {fillOpacity:0.4}); 2109 * var g = board.create('functiongraph', [f, -2, 5]); 2110 * var t = board.create('text',[-2,-2, function(){ return 'Sum=' + JXG.toFixed(r.Value(), 4); }]); 2111 * })(); 2112 * </script><pre> 2113 * 2114 * @example 2115 * // Riemann sum between two functions 2116 * var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1}); 2117 * var g = function(x) { return 0.5*x*x-2*x; }; 2118 * var f = function(x) { return -x*(x-4); }; 2119 * var r = board.create('riemannsum', 2120 * [[g,f], function(){return s.Value();}, 'lower', 0, 4], 2121 * {fillOpacity:0.4} 2122 * ); 2123 * var f = board.create('functiongraph',[f, -2, 5]); 2124 * var g = board.create('functiongraph',[g, -2, 5]); 2125 * var t = board.create('text',[-2,-2, function(){ return 'Sum=' + JXG.toFixed(r.Value(), 4); }]); 2126 * </pre><div class="jxgbox" id="f9a7ba38-b50f-4a32-a873-2f3bf9caee79" style="width: 300px; height: 300px;"></div> 2127 * <script type="text/javascript"> 2128 * (function(){ 2129 * var board = JXG.JSXGraph.initBoard('f9a7ba38-b50f-4a32-a873-2f3bf9caee79', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 2130 * var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1}); 2131 * var g = function(x) { return 0.5*x*x-2*x; }; 2132 * var f = function(x) { return -x*(x-4); }; 2133 * var r = board.create('riemannsum', 2134 * [[g,f], function(){return s.Value();}, 'lower', 0, 4], 2135 * {fillOpacity:0.4} 2136 * ); 2137 * var f = board.create('functiongraph',[f, -2, 5]); 2138 * var g = board.create('functiongraph',[g, -2, 5]); 2139 * var t = board.create('text',[-2,-2, function(){ return 'Sum=' + JXG.toFixed(r.Value(), 4); }]); 2140 * })(); 2141 * </script><pre> 2142 */ 2143 JXG.createRiemannsum = function (board, parents, attributes) { 2144 var n, type, f, par, c, attr; 2145 2146 attr = Type.copyAttributes(attributes, board.options, 'riemannsum'); 2147 attr.curvetype = 'plot'; 2148 2149 f = parents[0]; 2150 n = Type.createFunction(parents[1], board, ''); 2151 2152 if (!Type.exists(n)) { 2153 throw new Error("JSXGraph: JXG.createRiemannsum: argument '2' n has to be number or function." + 2154 "\nPossible parent types: [function,n:number|function,type,start:number|function,end:number|function]"); 2155 } 2156 2157 type = Type.createFunction(parents[2], board, '', false); 2158 if (!Type.exists(type)) { 2159 throw new Error("JSXGraph: JXG.createRiemannsum: argument 3 'type' has to be string or function." + 2160 "\nPossible parent types: [function,n:number|function,type,start:number|function,end:number|function]"); 2161 } 2162 2163 par = [[0], [0]].concat(parents.slice(3)); 2164 2165 c = board.create('curve', par, attr); 2166 2167 c.sum = 0.0; 2168 c.Value = function () { 2169 return this.sum; 2170 }; 2171 2172 c.updateDataArray = function () { 2173 var u = Numerics.riemann(f, n(), type(), this.minX(), this.maxX()); 2174 this.dataX = u[0]; 2175 this.dataY = u[1]; 2176 2177 // Update "Riemann sum" 2178 this.sum = u[2]; 2179 }; 2180 2181 return c; 2182 }; 2183 2184 JXG.registerElement('riemannsum', JXG.createRiemannsum); 2185 2186 /** 2187 * @class This element is used to provide a constructor for trace curve (simple locus curve), which is realized as a special curve. 2188 * @pseudo 2189 * @description 2190 * @name Tracecurve 2191 * @augments JXG.Curve 2192 * @constructor 2193 * @type JXG.Curve 2194 * @param {Point,Point} Parent elements of Tracecurve are a 2195 * glider point and a point whose locus is traced. 2196 * @see JXG.Curve 2197 * @example 2198 * // Create trace curve. 2199 * var c1 = board.create('circle',[[0, 0], [2, 0]]), 2200 * p1 = board.create('point',[-3, 1]), 2201 * g1 = board.create('glider',[2, 1, c1]), 2202 * s1 = board.create('segment',[g1, p1]), 2203 * p2 = board.create('midpoint',[s1]), 2204 * curve = board.create('tracecurve', [g1, p2]); 2205 * 2206 * </pre><div class="jxgbox" id="5749fb7d-04fc-44d2-973e-45c1951e29ad" style="width: 300px; height: 300px;"></div> 2207 * <script type="text/javascript"> 2208 * var tc1_board = JXG.JSXGraph.initBoard('5749fb7d-04fc-44d2-973e-45c1951e29ad', {boundingbox: [-4, 4, 4, -4], axis: false, showcopyright: false, shownavigation: false}); 2209 * var c1 = tc1_board.create('circle',[[0, 0], [2, 0]]), 2210 * p1 = tc1_board.create('point',[-3, 1]), 2211 * g1 = tc1_board.create('glider',[2, 1, c1]), 2212 * s1 = tc1_board.create('segment',[g1, p1]), 2213 * p2 = tc1_board.create('midpoint',[s1]), 2214 * curve = tc1_board.create('tracecurve', [g1, p2]); 2215 * </script><pre> 2216 */ 2217 JXG.createTracecurve = function (board, parents, attributes) { 2218 var c, glider, tracepoint, attr; 2219 2220 if (parents.length !== 2) { 2221 throw new Error("JSXGraph: Can't create trace curve with given parent'" + 2222 "\nPossible parent types: [glider, point]"); 2223 } 2224 2225 glider = board.select(parents[0]); 2226 tracepoint = board.select(parents[1]); 2227 2228 if (glider.type !== Const.OBJECT_TYPE_GLIDER || !Type.isPoint(tracepoint)) { 2229 throw new Error("JSXGraph: Can't create trace curve with parent types '" + 2230 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 2231 "\nPossible parent types: [glider, point]"); 2232 } 2233 2234 attr = Type.copyAttributes(attributes, board.options, 'tracecurve'); 2235 attr.curvetype = 'plot'; 2236 c = board.create('curve', [[0], [0]], attr); 2237 2238 c.updateDataArray = function () { 2239 var i, step, t, el, pEl, x, y, v, from, savetrace, 2240 le = attr.numberpoints, 2241 savePos = glider.position, 2242 slideObj = glider.slideObject, 2243 mi = slideObj.minX(), 2244 ma = slideObj.maxX(); 2245 2246 // set step width 2247 step = (ma - mi) / le; 2248 this.dataX = []; 2249 this.dataY = []; 2250 2251 /* 2252 * For gliders on circles and lines a closed curve is computed. 2253 * For gliders on curves the curve is not closed. 2254 */ 2255 if (slideObj.elementClass !== Const.OBJECT_CLASS_CURVE) { 2256 le++; 2257 } 2258 2259 // Loop over all steps 2260 for (i = 0; i < le; i++) { 2261 t = mi + i * step; 2262 x = slideObj.X(t) / slideObj.Z(t); 2263 y = slideObj.Y(t) / slideObj.Z(t); 2264 2265 // Position the glider 2266 glider.setPositionDirectly(Const.COORDS_BY_USER, [x, y]); 2267 from = false; 2268 2269 // Update all elements from the glider up to the trace element 2270 for (el in this.board.objects) { 2271 if (this.board.objects.hasOwnProperty(el)) { 2272 pEl = this.board.objects[el]; 2273 2274 if (pEl === glider) { 2275 from = true; 2276 } 2277 2278 if (from && pEl.needsRegularUpdate) { 2279 // Save the trace mode of the element 2280 savetrace = pEl.visProp.trace; 2281 pEl.visProp.trace = false; 2282 pEl.needsUpdate = true; 2283 pEl.update(true); 2284 2285 // Restore the trace mode 2286 pEl.visProp.trace = savetrace; 2287 if (pEl === tracepoint) { 2288 break; 2289 } 2290 } 2291 } 2292 } 2293 2294 // Store the position of the trace point 2295 this.dataX[i] = tracepoint.X(); 2296 this.dataY[i] = tracepoint.Y(); 2297 } 2298 2299 // Restore the original position of the glider 2300 glider.position = savePos; 2301 from = false; 2302 2303 // Update all elements from the glider to the trace point 2304 for (el in this.board.objects) { 2305 if (this.board.objects.hasOwnProperty(el)) { 2306 pEl = this.board.objects[el]; 2307 if (pEl === glider) { 2308 from = true; 2309 } 2310 2311 if (from && pEl.needsRegularUpdate) { 2312 savetrace = pEl.visProp.trace; 2313 pEl.visProp.trace = false; 2314 pEl.needsUpdate = true; 2315 pEl.update(true); 2316 pEl.visProp.trace = savetrace; 2317 2318 if (pEl === tracepoint) { 2319 break; 2320 } 2321 } 2322 } 2323 } 2324 }; 2325 2326 return c; 2327 }; 2328 2329 JXG.registerElement('tracecurve', JXG.createTracecurve); 2330 2331 /** 2332 * @class This element is used to provide a constructor for step function, which is realized as a special curve. 2333 * 2334 * In case the data points should be updated after creation time, they can be accessed by curve.xterm and curve.yterm. 2335 * @pseudo 2336 * @description 2337 * @name Stepfunction 2338 * @augments JXG.Curve 2339 * @constructor 2340 * @type JXG.Curve 2341 * @param {Array,Array|Function} Parent elements of Stepfunction are two arrays containing the coordinates. 2342 * @see JXG.Curve 2343 * @example 2344 * // Create step function. 2345 var curve = board.create('stepfunction', [[0,1,2,3,4,5], [1,3,0,2,2,1]]); 2346 2347 * </pre><div class="jxgbox" id="32342ec9-ad17-4339-8a97-ff23dc34f51a" style="width: 300px; height: 300px;"></div> 2348 * <script type="text/javascript"> 2349 * var sf1_board = JXG.JSXGraph.initBoard('32342ec9-ad17-4339-8a97-ff23dc34f51a', {boundingbox: [-1, 5, 6, -2], axis: true, showcopyright: false, shownavigation: false}); 2350 * var curve = sf1_board.create('stepfunction', [[0,1,2,3,4,5], [1,3,0,2,2,1]]); 2351 * </script><pre> 2352 */ 2353 JXG.createStepfunction = function (board, parents, attributes) { 2354 var c, attr; 2355 if (parents.length !== 2) { 2356 throw new Error("JSXGraph: Can't create step function with given parent'" + 2357 "\nPossible parent types: [array, array|function]"); 2358 } 2359 2360 attr = Type.copyAttributes(attributes, board.options, 'stepfunction'); 2361 c = board.create('curve', parents, attr); 2362 c.updateDataArray = function () { 2363 var i, j = 0, 2364 len = this.xterm.length; 2365 2366 this.dataX = []; 2367 this.dataY = []; 2368 2369 if (len === 0) { 2370 return; 2371 } 2372 2373 this.dataX[j] = this.xterm[0]; 2374 this.dataY[j] = this.yterm[0]; 2375 ++j; 2376 2377 for (i = 1; i < len; ++i) { 2378 this.dataX[j] = this.xterm[i]; 2379 this.dataY[j] = this.dataY[j - 1]; 2380 ++j; 2381 this.dataX[j] = this.xterm[i]; 2382 this.dataY[j] = this.yterm[i]; 2383 ++j; 2384 } 2385 }; 2386 2387 return c; 2388 }; 2389 2390 JXG.registerElement('stepfunction', JXG.createStepfunction); 2391 2392 return { 2393 Curve: JXG.Curve, 2394 createCurve: JXG.createCurve, 2395 createFunctiongraph: JXG.createFunctiongraph, 2396 createPlot: JXG.createPlot, 2397 createSpline: JXG.createSpline, 2398 createRiemannsum: JXG.createRiemannsum, 2399 createTracecurve: JXG.createTracecurve, 2400 createStepfunction: JXG.createStepfunction 2401 }; 2402 }); 2403