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