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