1 /* 2 Copyright 2008-2025 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 <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true*/ 33 /*jslint nomen: true, plusplus: true*/ 34 35 /** 36 * @fileoverview In this file the geometry element Curve is defined. 37 */ 38 39 import JXG from "../jxg.js"; 40 import Clip from "../math/clip.js"; 41 import Const from "./constants.js"; 42 import Coords from "./coords.js"; 43 import Geometry from "../math/geometry.js"; 44 import GeometryElement from "./element.js"; 45 import GeonextParser from "../parser/geonext.js"; 46 import ImplicitPlot from "../math/implicitplot.js"; 47 import Mat from "../math/math.js"; 48 import Metapost from "../math/metapost.js"; 49 import Numerics from "../math/numerics.js"; 50 import Plot from "../math/plot.js"; 51 import QDT from "../math/qdt.js"; 52 import Type from "../utils/type.js"; 53 54 /** 55 * Curves are the common object for function graphs, parametric curves, polar curves, and data plots. 56 * @class Creates a new curve object. Do not use this constructor to create a curve. Use {@link JXG.Board#create} with 57 * type {@link Curve}, or {@link Functiongraph} instead. 58 * @augments JXG.GeometryElement 59 * @param {String|JXG.Board} board The board the new curve is drawn on. 60 * @param {Array} parents defining terms An array with the function terms or the data points of the curve. 61 * @param {Object} attributes Defines the visual appearance of the curve. 62 * @see JXG.Board#generateName 63 * @see JXG.Board#addCurve 64 */ 65 JXG.Curve = function (board, parents, attributes) { 66 this.constructor(board, attributes, Const.OBJECT_TYPE_CURVE, Const.OBJECT_CLASS_CURVE); 67 68 this.points = []; 69 /** 70 * Number of points on curves. This value changes 71 * between numberPointsLow and numberPointsHigh. 72 * It is set in {@link JXG.Curve#updateCurve}. 73 */ 74 this.numberPoints = this.evalVisProp('numberpointshigh'); 75 76 this.bezierDegree = 1; 77 78 /** 79 * Array holding the x-coordinates of a data plot. 80 * This array can be updated during run time by overwriting 81 * the method {@link JXG.Curve#updateDataArray}. 82 * @type array 83 */ 84 this.dataX = null; 85 86 /** 87 * Array holding the y-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.dataY = null; 93 94 /** 95 * Array of ticks storing all the ticks on this curve. Do not set this field directly and use 96 * {@link JXG.Curve#addTicks} and {@link JXG.Curve#removeTicks} to add and remove ticks to and 97 * from the curve. 98 * @type Array 99 * @see JXG.Ticks 100 */ 101 this.ticks = []; 102 103 /** 104 * Stores a quadtree if it is required. The quadtree is generated in the curve 105 * updates and can be used to speed up the hasPoint method. 106 * @type JXG.Math.Quadtree 107 */ 108 this.qdt = null; 109 110 if (Type.exists(parents[0])) { 111 this.varname = parents[0]; 112 } else { 113 this.varname = "x"; 114 } 115 116 // function graphs: "x" 117 this.xterm = parents[1]; 118 // function graphs: e.g. "x^2" 119 this.yterm = parents[2]; 120 121 // Converts GEONExT syntax into JavaScript syntax 122 this.generateTerm(this.varname, this.xterm, this.yterm, parents[3], parents[4]); 123 // First evaluation of the curve 124 this.updateCurve(); 125 126 this.id = this.board.setId(this, "G"); 127 this.board.renderer.drawCurve(this); 128 129 this.board.finalizeAdding(this); 130 131 this.createGradient(); 132 this.elType = "curve"; 133 this.createLabel(); 134 135 if (Type.isString(this.xterm)) { 136 this.notifyParents(this.xterm); 137 } 138 if (Type.isString(this.yterm)) { 139 this.notifyParents(this.yterm); 140 } 141 142 this.methodMap = Type.deepCopy(this.methodMap, { 143 generateTerm: "generateTerm", 144 setTerm: "generateTerm", 145 move: "moveTo", 146 moveTo: "moveTo", 147 MinX: "minX", 148 MaxX: "maxX" 149 }); 150 }; 151 152 JXG.Curve.prototype = new GeometryElement(); 153 154 JXG.extend( 155 JXG.Curve.prototype, 156 /** @lends JXG.Curve.prototype */ { 157 /** 158 * Gives the default value of the left bound for the curve. 159 * May be overwritten in {@link JXG.Curve#generateTerm}. 160 * @returns {Number} Left bound for the curve. 161 */ 162 minX: function () { 163 var leftCoords; 164 165 if (this.evalVisProp('curvetype') === "polar") { 166 return 0; 167 } 168 169 leftCoords = new Coords( 170 Const.COORDS_BY_SCREEN, 171 [-this.board.canvasWidth * 0.1, 0], 172 this.board, 173 false 174 ); 175 return leftCoords.usrCoords[1]; 176 }, 177 178 /** 179 * Gives the default value of the right bound for the curve. 180 * May be overwritten in {@link JXG.Curve#generateTerm}. 181 * @returns {Number} Right bound for the curve. 182 */ 183 maxX: function () { 184 var rightCoords; 185 186 if (this.evalVisProp('curvetype') === "polar") { 187 return 2 * Math.PI; 188 } 189 rightCoords = new Coords( 190 Const.COORDS_BY_SCREEN, 191 [this.board.canvasWidth * 1.1, 0], 192 this.board, 193 false 194 ); 195 196 return rightCoords.usrCoords[1]; 197 }, 198 199 /** 200 * The parametric function which defines the x-coordinate of the curve. 201 * @param {Number} t A number between {@link JXG.Curve#minX} and {@link JXG.Curve#maxX}. 202 * @param {Boolean} suspendUpdate A boolean flag which is false for the 203 * first call of the function during a fresh plot of the curve and true 204 * for all subsequent calls of the function. This may be used to speed up the 205 * plotting of the curve, if the e.g. the curve depends on some input elements. 206 * @returns {Number} x-coordinate of the curve at t. 207 */ 208 X: function (t) { 209 return NaN; 210 }, 211 212 /** 213 * The parametric function which defines the y-coordinate of the curve. 214 * @param {Number} t A number between {@link JXG.Curve#minX} and {@link JXG.Curve#maxX}. 215 * @param {Boolean} suspendUpdate A boolean flag which is false for the 216 * first call of the function during a fresh plot of the curve and true 217 * for all subsequent calls of the function. This may be used to speed up the 218 * plotting of the curve, if the e.g. the curve depends on some input elements. 219 * @returns {Number} y-coordinate of the curve at t. 220 */ 221 Y: function (t) { 222 return NaN; 223 }, 224 225 /** 226 * Treat the curve as curve with homogeneous coordinates. 227 * @param {Number} t A number between {@link JXG.Curve#minX} and {@link JXG.Curve#maxX}. 228 * @returns {Number} Always 1.0 229 */ 230 Z: function (t) { 231 return 1; 232 }, 233 234 /** 235 * Checks whether (x,y) is near the curve. 236 * @param {Number} x Coordinate in x direction, screen coordinates. 237 * @param {Number} y Coordinate in y direction, screen coordinates. 238 * @param {Number} start Optional start index for search on data plots. 239 * @returns {Boolean} True if (x,y) is near the curve, False otherwise. 240 */ 241 hasPoint: function (x, y, start) { 242 var t, c, i, tX, tY, 243 checkPoint, len, invMat, isIn, 244 res = [], 245 points, 246 qdt, 247 steps = this.evalVisProp('numberpointslow'), 248 d = (this.maxX() - this.minX()) / steps, 249 prec, type, 250 dist = Infinity, 251 ux2, uy2, 252 ev_ct, 253 mi, ma, 254 suspendUpdate = true; 255 256 if (Type.isObject(this.evalVisProp('precision'))) { 257 type = this.board._inputDevice; 258 prec = this.evalVisProp('precision.' + type); 259 } else { 260 // 'inherit' 261 prec = this.board.options.precision.hasPoint; 262 } 263 264 // From now on, x,y are usrCoords 265 checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board, false); 266 x = checkPoint.usrCoords[1]; 267 y = checkPoint.usrCoords[2]; 268 269 // Handle inner points of the curve 270 if (this.bezierDegree === 1 && this.evalVisProp('hasinnerpoints')) { 271 isIn = Geometry.windingNumber([1, x, y], this.points, true); 272 if (isIn !== 0) { 273 return true; 274 } 275 } 276 277 // We use usrCoords. Only in the final distance calculation 278 // screen coords are used 279 prec += this.evalVisProp('strokewidth') * 0.5; 280 prec *= prec; // We do not want to take sqrt 281 ux2 = this.board.unitX * this.board.unitX; 282 uy2 = this.board.unitY * this.board.unitY; 283 284 mi = this.minX(); 285 ma = this.maxX(); 286 if (Type.exists(this._visibleArea)) { 287 mi = this._visibleArea[0]; 288 ma = this._visibleArea[1]; 289 d = (ma - mi) / steps; 290 } 291 292 ev_ct = this.evalVisProp('curvetype'); 293 if (ev_ct === "parameter" || ev_ct === "polar") { 294 // Transform the mouse/touch coordinates 295 // back to the original position of the curve. 296 // This is needed, because we work with the function terms, not the points. 297 if (this.transformations.length > 0) { 298 this.updateTransformMatrix(); 299 invMat = Mat.inverse(this.transformMat); 300 c = Mat.matVecMult(invMat, [1, x, y]); 301 x = c[1]; 302 y = c[2]; 303 } 304 305 // Brute force search for a point on the curve close to the mouse pointer 306 for (i = 0, t = mi; i < steps; i++) { 307 tX = this.X(t, suspendUpdate); 308 tY = this.Y(t, suspendUpdate); 309 310 dist = (x - tX) * (x - tX) * ux2 + (y - tY) * (y - tY) * uy2; 311 312 if (dist <= prec) { 313 return true; 314 } 315 316 t += d; 317 } 318 } else if (ev_ct === "plot" || ev_ct === "functiongraph") { 319 // Here, we can ignore transformations of the curve, 320 // since we are working directly with the points. 321 322 if (!Type.exists(start) || start < 0) { 323 start = 0; 324 } 325 326 if ( 327 Type.exists(this.qdt) && 328 this.evalVisProp('useqdt') && 329 this.bezierDegree !== 3 330 ) { 331 qdt = this.qdt.query(new Coords(Const.COORDS_BY_USER, [x, y], this.board)); 332 points = qdt.points; 333 len = points.length; 334 } else { 335 points = this.points; 336 len = this.numberPoints - 1; 337 } 338 339 for (i = start; i < len; i++) { 340 if (this.bezierDegree === 3) { 341 //res.push(Geometry.projectCoordsToBeziersegment([1, x, y], this, i)); 342 res = Geometry.projectCoordsToBeziersegment([1, x, y], this, i); 343 } else { 344 if (qdt) { 345 if (points[i].prev) { 346 res = Geometry.projectCoordsToSegment( 347 [1, x, y], 348 points[i].prev.usrCoords, 349 points[i].usrCoords 350 ); 351 } 352 353 // If the next point in the array is the same as the current points 354 // next neighbor we don't have to project it onto that segment because 355 // that will already be done in the next iteration of this loop. 356 if (points[i].next && points[i + 1] !== points[i].next) { 357 res = Geometry.projectCoordsToSegment( 358 [1, x, y], 359 points[i].usrCoords, 360 points[i].next.usrCoords 361 ); 362 } 363 } else { 364 res = Geometry.projectCoordsToSegment( 365 [1, x, y], 366 points[i].usrCoords, 367 points[i + 1].usrCoords 368 ); 369 } 370 } 371 372 if ( 373 res[1] >= 0 && 374 res[1] <= 1 && 375 (x - res[0][1]) * (x - res[0][1]) * ux2 + 376 (y - res[0][2]) * (y - res[0][2]) * uy2 <= 377 prec 378 ) { 379 return true; 380 } 381 } 382 return false; 383 } 384 return dist < prec; 385 }, 386 387 /** 388 * Allocate points in the Coords array this.points 389 */ 390 allocatePoints: function () { 391 var i, len; 392 393 len = this.numberPoints; 394 395 if (this.points.length < this.numberPoints) { 396 for (i = this.points.length; i < len; i++) { 397 this.points[i] = new Coords( 398 Const.COORDS_BY_USER, 399 [0, 0], 400 this.board, 401 false 402 ); 403 } 404 } 405 }, 406 407 /** 408 * Computes for equidistant points on the x-axis the values of the function 409 * @returns {JXG.Curve} Reference to the curve object. 410 * @see JXG.Curve#updateCurve 411 */ 412 update: function () { 413 if (this.needsUpdate) { 414 if (this.evalVisProp('trace')) { 415 this.cloneToBackground(true); 416 } 417 this.updateCurve(); 418 } 419 420 return this; 421 }, 422 423 /** 424 * Updates the visual contents of the curve. 425 * @returns {JXG.Curve} Reference to the curve object. 426 */ 427 updateRenderer: function () { 428 //var wasReal; 429 430 if (!this.needsUpdate) { 431 return this; 432 } 433 434 if (this.visPropCalc.visible) { 435 // wasReal = this.isReal; 436 437 this.isReal = Plot.checkReal(this.points); 438 439 if ( 440 //wasReal && 441 !this.isReal 442 ) { 443 this.updateVisibility(false); 444 } 445 } 446 447 if (this.visPropCalc.visible) { 448 this.board.renderer.updateCurve(this); 449 } 450 451 /* Update the label if visible. */ 452 if ( 453 this.hasLabel && 454 this.visPropCalc.visible && 455 this.label && 456 this.label.visPropCalc.visible && 457 this.isReal 458 ) { 459 this.label.update(); 460 this.board.renderer.updateText(this.label); 461 } 462 463 // Update rendNode display 464 this.setDisplayRendNode(); 465 // if (this.visPropCalc.visible !== this.visPropOld.visible) { 466 // this.board.renderer.display(this, this.visPropCalc.visible); 467 // this.visPropOld.visible = this.visPropCalc.visible; 468 // 469 // if (this.hasLabel) { 470 // this.board.renderer.display(this.label, this.label.visPropCalc.visible); 471 // } 472 // } 473 474 this.needsUpdate = false; 475 return this; 476 }, 477 478 /** 479 * For dynamic dataplots updateCurve can be used to compute new entries 480 * for the arrays {@link JXG.Curve#dataX} and {@link JXG.Curve#dataY}. It 481 * is used in {@link JXG.Curve#updateCurve}. Default is an empty method, can 482 * be overwritten by the user. 483 * 484 * 485 * @example 486 * // This example overwrites the updateDataArray method. 487 * // There, new values for the arrays JXG.Curve.dataX and JXG.Curve.dataY 488 * // are computed from the value of the slider N 489 * 490 * var N = board.create('slider', [[0,1.5],[3,1.5],[1,3,40]], {name:'n',snapWidth:1}); 491 * var circ = board.create('circle',[[4,-1.5],1],{strokeWidth:1, strokecolor:'black', strokeWidth:2, 492 * fillColor:'#0055ff13'}); 493 * 494 * var c = board.create('curve', [[0],[0]],{strokecolor:'red', strokeWidth:2}); 495 * c.updateDataArray = function() { 496 * var r = 1, n = Math.floor(N.Value()), 497 * x = [0], y = [0], 498 * phi = Math.PI/n, 499 * h = r*Math.cos(phi), 500 * s = r*Math.sin(phi), 501 * i, j, 502 * px = 0, py = 0, sgn = 1, 503 * d = 16, 504 * dt = phi/d, 505 * pt; 506 * 507 * for (i = 0; i < n; i++) { 508 * for (j = -d; j <= d; j++) { 509 * pt = dt*j; 510 * x.push(px + r*Math.sin(pt)); 511 * y.push(sgn*r*Math.cos(pt) - (sgn-1)*h*0.5); 512 * } 513 * px += s; 514 * sgn *= (-1); 515 * } 516 * x.push((n - 1)*s); 517 * y.push(h + (sgn - 1)*h*0.5); 518 * this.dataX = x; 519 * this.dataY = y; 520 * } 521 * 522 * var c2 = board.create('curve', [[0],[0]],{strokecolor:'red', strokeWidth:1}); 523 * c2.updateDataArray = function() { 524 * var r = 1, n = Math.floor(N.Value()), 525 * px = circ.midpoint.X(), py = circ.midpoint.Y(), 526 * x = [px], y = [py], 527 * phi = Math.PI/n, 528 * s = r*Math.sin(phi), 529 * i, j, 530 * d = 16, 531 * dt = phi/d, 532 * pt = Math.PI*0.5+phi; 533 * 534 * for (i = 0; i < n; i++) { 535 * for (j= -d; j <= d; j++) { 536 * x.push(px + r*Math.cos(pt)); 537 * y.push(py + r*Math.sin(pt)); 538 * pt -= dt; 539 * } 540 * x.push(px); 541 * y.push(py); 542 * pt += dt; 543 * } 544 * this.dataX = x; 545 * this.dataY = y; 546 * } 547 * board.update(); 548 * 549 * </pre><div id="JXG20bc7802-e69e-11e5-b1bf-901b0e1b8723" class="jxgbox" style="width: 600px; height: 400px;"></div> 550 * <script type="text/javascript"> 551 * (function() { 552 * var board = JXG.JSXGraph.initBoard('JXG20bc7802-e69e-11e5-b1bf-901b0e1b8723', 553 * {boundingbox: [-1.5,2,8,-3], keepaspectratio: true, axis: true, showcopyright: false, shownavigation: false}); 554 * var N = board.create('slider', [[0,1.5],[3,1.5],[1,3,40]], {name:'n',snapWidth:1}); 555 * var circ = board.create('circle',[[4,-1.5],1],{strokeWidth:1, strokecolor:'black', 556 * strokeWidth:2, fillColor:'#0055ff13'}); 557 * 558 * var c = board.create('curve', [[0],[0]],{strokecolor:'red', strokeWidth:2}); 559 * c.updateDataArray = function() { 560 * var r = 1, n = Math.floor(N.Value()), 561 * x = [0], y = [0], 562 * phi = Math.PI/n, 563 * h = r*Math.cos(phi), 564 * s = r*Math.sin(phi), 565 * i, j, 566 * px = 0, py = 0, sgn = 1, 567 * d = 16, 568 * dt = phi/d, 569 * pt; 570 * 571 * for (i=0;i<n;i++) { 572 * for (j=-d;j<=d;j++) { 573 * pt = dt*j; 574 * x.push(px+r*Math.sin(pt)); 575 * y.push(sgn*r*Math.cos(pt)-(sgn-1)*h*0.5); 576 * } 577 * px += s; 578 * sgn *= (-1); 579 * } 580 * x.push((n-1)*s); 581 * y.push(h+(sgn-1)*h*0.5); 582 * this.dataX = x; 583 * this.dataY = y; 584 * } 585 * 586 * var c2 = board.create('curve', [[0],[0]],{strokecolor:'red', strokeWidth:1}); 587 * c2.updateDataArray = function() { 588 * var r = 1, n = Math.floor(N.Value()), 589 * px = circ.midpoint.X(), py = circ.midpoint.Y(), 590 * x = [px], y = [py], 591 * phi = Math.PI/n, 592 * s = r*Math.sin(phi), 593 * i, j, 594 * d = 16, 595 * dt = phi/d, 596 * pt = Math.PI*0.5+phi; 597 * 598 * for (i=0;i<n;i++) { 599 * for (j=-d;j<=d;j++) { 600 * x.push(px+r*Math.cos(pt)); 601 * y.push(py+r*Math.sin(pt)); 602 * pt -= dt; 603 * } 604 * x.push(px); 605 * y.push(py); 606 * pt += dt; 607 * } 608 * this.dataX = x; 609 * this.dataY = y; 610 * } 611 * board.update(); 612 * 613 * })(); 614 * 615 * </script><pre> 616 * 617 * @example 618 * // This is an example which overwrites updateDataArray and produces 619 * // a Bezier curve of degree three. 620 * var A = board.create('point', [-3,3]); 621 * var B = board.create('point', [3,-2]); 622 * var line = board.create('segment', [A,B]); 623 * 624 * var height = 0.5; // height of the curly brace 625 * 626 * // Curly brace 627 * var crl = board.create('curve', [[0],[0]], {strokeWidth:1, strokeColor:'black'}); 628 * crl.bezierDegree = 3; 629 * crl.updateDataArray = function() { 630 * var d = [B.X()-A.X(), B.Y()-A.Y()], 631 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 632 * mid = [(A.X()+B.X())*0.5, (A.Y()+B.Y())*0.5]; 633 * 634 * d[0] *= height/dl; 635 * d[1] *= height/dl; 636 * 637 * this.dataX = [ A.X(), A.X()-d[1], mid[0], mid[0]-d[1], mid[0], B.X()-d[1], B.X() ]; 638 * this.dataY = [ A.Y(), A.Y()+d[0], mid[1], mid[1]+d[0], mid[1], B.Y()+d[0], B.Y() ]; 639 * }; 640 * 641 * // Text 642 * var txt = board.create('text', [ 643 * function() { 644 * var d = [B.X()-A.X(), B.Y()-A.Y()], 645 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 646 * mid = (A.X()+B.X())*0.5; 647 * 648 * d[1] *= height/dl; 649 * return mid-d[1]+0.1; 650 * }, 651 * function() { 652 * var d = [B.X()-A.X(), B.Y()-A.Y()], 653 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 654 * mid = (A.Y()+B.Y())*0.5; 655 * 656 * d[0] *= height/dl; 657 * return mid+d[0]+0.1; 658 * }, 659 * function() { return "length=" + JXG.toFixed(B.Dist(A), 2); } 660 * ]); 661 * 662 * 663 * board.update(); // This update is necessary to call updateDataArray the first time. 664 * 665 * </pre><div id="JXGa61a4d66-e69f-11e5-b1bf-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 666 * <script type="text/javascript"> 667 * (function() { 668 * var board = JXG.JSXGraph.initBoard('JXGa61a4d66-e69f-11e5-b1bf-901b0e1b8723', 669 * {boundingbox: [-4, 4, 4,-4], axis: true, showcopyright: false, shownavigation: false}); 670 * var A = board.create('point', [-3,3]); 671 * var B = board.create('point', [3,-2]); 672 * var line = board.create('segment', [A,B]); 673 * 674 * var height = 0.5; // height of the curly brace 675 * 676 * // Curly brace 677 * var crl = board.create('curve', [[0],[0]], {strokeWidth:1, strokeColor:'black'}); 678 * crl.bezierDegree = 3; 679 * crl.updateDataArray = function() { 680 * var d = [B.X()-A.X(), B.Y()-A.Y()], 681 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 682 * mid = [(A.X()+B.X())*0.5, (A.Y()+B.Y())*0.5]; 683 * 684 * d[0] *= height/dl; 685 * d[1] *= height/dl; 686 * 687 * this.dataX = [ A.X(), A.X()-d[1], mid[0], mid[0]-d[1], mid[0], B.X()-d[1], B.X() ]; 688 * this.dataY = [ A.Y(), A.Y()+d[0], mid[1], mid[1]+d[0], mid[1], B.Y()+d[0], B.Y() ]; 689 * }; 690 * 691 * // Text 692 * var txt = board.create('text', [ 693 * function() { 694 * var d = [B.X()-A.X(), B.Y()-A.Y()], 695 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 696 * mid = (A.X()+B.X())*0.5; 697 * 698 * d[1] *= height/dl; 699 * return mid-d[1]+0.1; 700 * }, 701 * function() { 702 * var d = [B.X()-A.X(), B.Y()-A.Y()], 703 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 704 * mid = (A.Y()+B.Y())*0.5; 705 * 706 * d[0] *= height/dl; 707 * return mid+d[0]+0.1; 708 * }, 709 * function() { return "length="+JXG.toFixed(B.Dist(A), 2); } 710 * ]); 711 * 712 * 713 * board.update(); // This update is necessary to call updateDataArray the first time. 714 * 715 * })(); 716 * 717 * </script><pre> 718 * 719 * 720 */ 721 updateDataArray: function () { 722 // this used to return this, but we shouldn't rely on the user to implement it. 723 }, 724 725 /** 726 * Computes the curve path 727 * @see JXG.Curve#update 728 * @returns {JXG.Curve} Reference to the curve object. 729 */ 730 updateCurve: function () { 731 var i, len, mi, ma, 732 x, y, 733 version = this.visProp.plotversion, 734 //t1, t2, l1, 735 suspendUpdate = false; 736 737 this.updateTransformMatrix(); 738 this.updateDataArray(); 739 mi = this.minX(); 740 ma = this.maxX(); 741 742 // Discrete data points 743 // x-coordinates are in an array 744 if (Type.exists(this.dataX)) { 745 this.numberPoints = this.dataX.length; 746 len = this.numberPoints; 747 748 // It is possible, that the array length has increased. 749 this.allocatePoints(); 750 751 for (i = 0; i < len; i++) { 752 x = i; 753 754 // y-coordinates are in an array 755 if (Type.exists(this.dataY)) { 756 y = i; 757 // The last parameter prevents rounding in usr2screen(). 758 this.points[i].setCoordinates( 759 Const.COORDS_BY_USER, 760 [this.dataX[i], this.dataY[i]], 761 false 762 ); 763 } else { 764 // discrete x data, continuous y data 765 y = this.X(x); 766 // The last parameter prevents rounding in usr2screen(). 767 this.points[i].setCoordinates( 768 Const.COORDS_BY_USER, 769 [this.dataX[i], this.Y(y, suspendUpdate)], 770 false 771 ); 772 } 773 this.points[i]._t = i; 774 775 // this.updateTransform(this.points[i]); 776 suspendUpdate = true; 777 } 778 // continuous x data 779 } else { 780 if (this.evalVisProp('doadvancedplot')) { 781 // console.time("plot"); 782 783 if (version === 1 || this.evalVisProp('doadvancedplotold')) { 784 Plot.updateParametricCurveOld(this, mi, ma); 785 } else if (version === 2) { 786 Plot.updateParametricCurve_v2(this, mi, ma); 787 } else if (version === 3) { 788 Plot.updateParametricCurve_v3(this, mi, ma); 789 } else if (version === 4) { 790 Plot.updateParametricCurve_v4(this, mi, ma); 791 } else { 792 Plot.updateParametricCurve_v2(this, mi, ma); 793 } 794 // console.timeEnd("plot"); 795 } else { 796 if (this.board.updateQuality === this.board.BOARD_QUALITY_HIGH) { 797 this.numberPoints = this.evalVisProp('numberpointshigh'); 798 } else { 799 this.numberPoints = this.evalVisProp('numberpointslow'); 800 } 801 802 // It is possible, that the array length has increased. 803 this.allocatePoints(); 804 Plot.updateParametricCurveNaive(this, mi, ma, this.numberPoints); 805 } 806 len = this.numberPoints; 807 808 if ( 809 this.evalVisProp('useqdt') && 810 this.board.updateQuality === this.board.BOARD_QUALITY_HIGH 811 ) { 812 this.qdt = new QDT(this.board.getBoundingBox()); 813 for (i = 0; i < this.points.length; i++) { 814 this.qdt.insert(this.points[i]); 815 816 if (i > 0) { 817 this.points[i].prev = this.points[i - 1]; 818 } 819 820 if (i < len - 1) { 821 this.points[i].next = this.points[i + 1]; 822 } 823 } 824 } 825 826 // for (i = 0; i < len; i++) { 827 // this.updateTransform(this.points[i]); 828 // } 829 } 830 831 if ( 832 this.evalVisProp('curvetype') !== "plot" && 833 this.evalVisProp('rdpsmoothing') 834 ) { 835 // console.time("rdp"); 836 this.points = Numerics.RamerDouglasPeucker(this.points, 0.2); 837 this.numberPoints = this.points.length; 838 // console.timeEnd("rdp"); 839 // console.log(this.numberPoints); 840 } 841 842 len = this.numberPoints; 843 for (i = 0; i < len; i++) { 844 this.updateTransform(this.points[i]); 845 } 846 847 return this; 848 }, 849 850 updateTransformMatrix: function () { 851 var t, 852 i, 853 len = this.transformations.length; 854 855 this.transformMat = [ 856 [1, 0, 0], 857 [0, 1, 0], 858 [0, 0, 1] 859 ]; 860 861 for (i = 0; i < len; i++) { 862 t = this.transformations[i]; 863 t.update(); 864 this.transformMat = Mat.matMatMult(t.matrix, this.transformMat); 865 } 866 867 return this; 868 }, 869 870 /** 871 * Applies the transformations of the curve to the given point <tt>p</tt>. 872 * Before using it, {@link JXG.Curve#updateTransformMatrix} has to be called. 873 * @param {JXG.Point} p 874 * @returns {JXG.Point} The given point. 875 */ 876 updateTransform: function (p) { 877 var c, 878 len = this.transformations.length; 879 880 if (len > 0) { 881 c = Mat.matVecMult(this.transformMat, p.usrCoords); 882 p.setCoordinates(Const.COORDS_BY_USER, c, false, true); 883 } 884 885 return p; 886 }, 887 888 /** 889 * Add transformations to this curve. 890 * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of {@link JXG.Transformation}s. 891 * @returns {JXG.Curve} Reference to the curve object. 892 */ 893 addTransform: function (transform) { 894 var i, 895 list = Type.isArray(transform) ? transform : [transform], 896 len = list.length; 897 898 for (i = 0; i < len; i++) { 899 this.transformations.push(list[i]); 900 } 901 902 return this; 903 }, 904 905 /** 906 * Generate the method curve.X() in case curve.dataX is an array 907 * and generate the method curve.Y() in case curve.dataY is an array. 908 * @private 909 * @param {String} which Either 'X' or 'Y' 910 * @returns {function} 911 **/ 912 interpolationFunctionFromArray: function (which) { 913 var data = "data" + which, 914 that = this; 915 916 return function (t, suspendedUpdate) { 917 var i, 918 j, 919 t0, 920 t1, 921 arr = that[data], 922 len = arr.length, 923 last, 924 f = []; 925 926 if (isNaN(t)) { 927 return NaN; 928 } 929 930 if (t < 0) { 931 if (Type.isFunction(arr[0])) { 932 return arr[0](); 933 } 934 935 return arr[0]; 936 } 937 938 if (that.bezierDegree === 3) { 939 last = (len - 1) / 3; 940 941 if (t >= last) { 942 if (Type.isFunction(arr[arr.length - 1])) { 943 return arr[arr.length - 1](); 944 } 945 946 return arr[arr.length - 1]; 947 } 948 949 i = Math.floor(t) * 3; 950 t0 = t % 1; 951 t1 = 1 - t0; 952 953 for (j = 0; j < 4; j++) { 954 if (Type.isFunction(arr[i + j])) { 955 f[j] = arr[i + j](); 956 } else { 957 f[j] = arr[i + j]; 958 } 959 } 960 961 return ( 962 t1 * t1 * (t1 * f[0] + 3 * t0 * f[1]) + 963 (3 * t1 * f[2] + t0 * f[3]) * t0 * t0 964 ); 965 } 966 967 if (t > len - 2) { 968 i = len - 2; 969 } else { 970 i = parseInt(Math.floor(t), 10); 971 } 972 973 if (i === t) { 974 if (Type.isFunction(arr[i])) { 975 return arr[i](); 976 } 977 return arr[i]; 978 } 979 980 for (j = 0; j < 2; j++) { 981 if (Type.isFunction(arr[i + j])) { 982 f[j] = arr[i + j](); 983 } else { 984 f[j] = arr[i + j]; 985 } 986 } 987 return f[0] + (f[1] - f[0]) * (t - i); 988 }; 989 }, 990 991 /** 992 * Converts the JavaScript/JessieCode/GEONExT syntax of the defining function term into JavaScript. 993 * New methods X() and Y() for the Curve object are generated, further 994 * new methods for minX() and maxX(). 995 * If mi or ma are not supplied, default functions are set. 996 * 997 * @param {String} varname Name of the parameter in xterm and yterm, e.g. 'x' or 't' 998 * @param {String|Number|Function|Array} xterm Term for the x coordinate. Can also be an array consisting of discrete values. 999 * @param {String|Number|Function|Array} yterm Term for the y coordinate. Can also be an array consisting of discrete values. 1000 * @param {String|Number|Function} [mi] Lower bound on the parameter 1001 * @param {String|Number|Function} [ma] Upper bound on the parameter 1002 * @see JXG.GeonextParser.geonext2JS 1003 */ 1004 generateTerm: function (varname, xterm, yterm, mi, ma) { 1005 var fx, fy, mat; 1006 1007 // Generate the methods X() and Y() 1008 if (Type.isArray(xterm)) { 1009 // Discrete data 1010 this.dataX = xterm; 1011 1012 this.numberPoints = this.dataX.length; 1013 this.X = this.interpolationFunctionFromArray.apply(this, ["X"]); 1014 this.visProp.curvetype = "plot"; 1015 this.isDraggable = true; 1016 } else { 1017 // Continuous data 1018 this.X = Type.createFunction(xterm, this.board, varname); 1019 if (Type.isString(xterm)) { 1020 this.visProp.curvetype = "functiongraph"; 1021 } else if (Type.isFunction(xterm) || Type.isNumber(xterm)) { 1022 this.visProp.curvetype = "parameter"; 1023 } 1024 1025 this.isDraggable = true; 1026 } 1027 1028 if (Type.isArray(yterm)) { 1029 this.dataY = yterm; 1030 this.Y = this.interpolationFunctionFromArray.apply(this, ["Y"]); 1031 } else if (!Type.exists(yterm)) { 1032 // Discrete data as an array of coordinate pairs, 1033 // i.e. transposed input 1034 mat = Mat.transpose(xterm); 1035 this.dataX = mat[0]; 1036 this.dataY = mat[1]; 1037 this.numberPoints = this.dataX.length; 1038 this.Y = this.interpolationFunctionFromArray.apply(this, ["Y"]); 1039 } else { 1040 this.Y = Type.createFunction(yterm, this.board, varname); 1041 } 1042 1043 /** 1044 * Polar form 1045 * Input data is function xterm() and offset coordinates yterm 1046 */ 1047 if (Type.isFunction(xterm) && Type.isArray(yterm)) { 1048 // Xoffset, Yoffset 1049 fx = Type.createFunction(yterm[0], this.board, ""); 1050 fy = Type.createFunction(yterm[1], this.board, ""); 1051 1052 this.X = function (phi) { 1053 return xterm(phi) * Math.cos(phi) + fx(); 1054 }; 1055 this.X.deps = fx.deps; 1056 1057 this.Y = function (phi) { 1058 return xterm(phi) * Math.sin(phi) + fy(); 1059 }; 1060 this.Y.deps = fy.deps; 1061 1062 this.visProp.curvetype = "polar"; 1063 } 1064 1065 // Set the upper and lower bounds for the parameter of the curve. 1066 // If not defined, reset the bounds to the default values 1067 // given in Curve.prototype.minX, Curve.prototype.maxX 1068 if (Type.exists(mi)) { 1069 this.minX = Type.createFunction(mi, this.board, ""); 1070 } else { 1071 delete this.minX; 1072 } 1073 if (Type.exists(ma)) { 1074 this.maxX = Type.createFunction(ma, this.board, ""); 1075 } else { 1076 delete this.maxX; 1077 } 1078 1079 this.addParentsFromJCFunctions([this.X, this.Y, this.minX, this.maxX]); 1080 }, 1081 1082 /** 1083 * Finds dependencies in a given term and notifies the parents by adding the 1084 * dependent object to the found objects child elements. 1085 * @param {String} contentStr String containing dependencies for the given object. 1086 */ 1087 notifyParents: function (contentStr) { 1088 var fstr, 1089 dep, 1090 isJessieCode = false, 1091 obj; 1092 1093 // Read dependencies found by the JessieCode parser 1094 obj = { xterm: 1, yterm: 1 }; 1095 for (fstr in obj) { 1096 if ( 1097 obj.hasOwnProperty(fstr) && 1098 this.hasOwnProperty(fstr) && 1099 this[fstr].origin 1100 ) { 1101 isJessieCode = true; 1102 for (dep in this[fstr].origin.deps) { 1103 if (this[fstr].origin.deps.hasOwnProperty(dep)) { 1104 this[fstr].origin.deps[dep].addChild(this); 1105 } 1106 } 1107 } 1108 } 1109 1110 if (!isJessieCode) { 1111 GeonextParser.findDependencies(this, contentStr, this.board); 1112 } 1113 }, 1114 1115 /** 1116 * Position a curve label according to the attributes "position" and distance. 1117 * This function is also used for angle, arc and sector. 1118 * 1119 * @param {String} pos 1120 * @param {Number} distance 1121 * @returns {JXG.Coords} 1122 */ 1123 getLabelPosition: function(pos, distance) { 1124 var x, y, xy, 1125 c, d, e, 1126 lbda, 1127 mi, ma, ar, 1128 t, dx, dy, 1129 dist = 1.5; 1130 1131 // Shrink domain if necessary 1132 mi = this.minX(); 1133 ma = this.maxX(); 1134 ar = Numerics.findDomain(this.X, [mi, ma], null, false); 1135 ar = Numerics.findDomain(this.Y, ar, null, false); 1136 mi = Math.max(ar[0], ar[0]); 1137 ma = Math.min(ar[1], ar[1]); 1138 1139 xy = Type.parsePosition(pos); 1140 lbda = Type.parseNumber(xy.pos, ma - mi, 1); 1141 1142 if (xy.pos.indexOf('fr') < 0 && 1143 xy.pos.indexOf('%') < 0) { 1144 // 'px' or numbers are not supported 1145 lbda = 0; 1146 } 1147 1148 t = mi + lbda; 1149 1150 x = this.X(t); 1151 y = this.Y(t); 1152 // If x or y are NaN, the label is set to the line 1153 // between the first and last point. 1154 if (isNaN(x + y)) { 1155 lbda /= (ma - mi); 1156 t = mi + lbda; 1157 x = this.X(mi) + lbda * (this.X(ma) - this.X(mi)); 1158 y = this.Y(mi) + lbda * (this.Y(ma) - this.Y(mi)); 1159 } 1160 c = (new Coords(Const.COORDS_BY_USER, [x, y], this.board)).scrCoords; 1161 1162 e = Mat.eps; 1163 if (t < mi + e) { 1164 dx = (this.X(t + e) - this.X(t)) / e; 1165 dy = (this.Y(t + e) - this.Y(t)) / e; 1166 } else if (t > ma - e) { 1167 dx = (this.X(t) - this.X(t - e)) / e; 1168 dy = (this.Y(t) - this.Y(t - e)) / e; 1169 } else { 1170 dx = 0.5 * (this.X(t + e) - this.X(t - e)) / e; 1171 dy = 0.5 * (this.Y(t + e) - this.Y(t - e)) / e; 1172 } 1173 dx = isNaN(dx) ? 1. : dx; 1174 dy = isNaN(dy) ? 1. : dy; 1175 d = Mat.hypot(dx, dy); 1176 1177 if (xy.side === 'left') { 1178 dy *= -1; 1179 } else { 1180 dx *= -1; 1181 } 1182 1183 // Position left or right 1184 1185 if (Type.exists(this.label)) { 1186 dist = 0.5 * distance / d; 1187 } 1188 1189 x = c[1] + dy * this.label.size[0] * dist; 1190 y = c[2] - dx * this.label.size[1] * dist; 1191 1192 return new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board); 1193 }, 1194 1195 // documented in geometryElement 1196 getLabelAnchor: function () { 1197 var x, y, pos, 1198 // xy, lbda, e, 1199 // t, dx, dy, d, 1200 // dist = 1.5, 1201 c, 1202 ax = 0.05 * this.board.canvasWidth, 1203 ay = 0.05 * this.board.canvasHeight, 1204 bx = 0.95 * this.board.canvasWidth, 1205 by = 0.95 * this.board.canvasHeight; 1206 1207 if (!Type.exists(this.label)) { 1208 return new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], this.board); 1209 } 1210 pos = this.label.evalVisProp('position'); 1211 if (!Type.isString(pos)) { 1212 return new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], this.board); 1213 } 1214 1215 if (pos.indexOf('right') < 0 && pos.indexOf('left') < 0) { 1216 switch (this.evalVisProp('label.position')) { 1217 case "ulft": 1218 x = ax; 1219 y = ay; 1220 break; 1221 case "llft": 1222 x = ax; 1223 y = by; 1224 break; 1225 case "rt": 1226 x = bx; 1227 y = 0.5 * by; 1228 break; 1229 case "lrt": 1230 x = bx; 1231 y = by; 1232 break; 1233 case "urt": 1234 x = bx; 1235 y = ay; 1236 break; 1237 case "top": 1238 x = 0.5 * bx; 1239 y = ay; 1240 break; 1241 case "bot": 1242 x = 0.5 * bx; 1243 y = by; 1244 break; 1245 default: 1246 // includes case 'lft' 1247 x = ax; 1248 y = 0.5 * by; 1249 } 1250 } else { 1251 // New positioning 1252 return this.getLabelPosition(pos, this.label.evalVisProp('distance')); 1253 // xy = Type.parsePosition(pos); 1254 // lbda = Type.parseNumber(xy.pos, this.maxX() - this.minX(), 1); 1255 1256 // if (xy.pos.indexOf('fr') < 0 && 1257 // xy.pos.indexOf('%') < 0) { 1258 // // 'px' or numbers are not supported 1259 // lbda = 0; 1260 // } 1261 1262 // t = this.minX() + lbda; 1263 // x = this.X(t); 1264 // y = this.Y(t); 1265 // c = (new Coords(Const.COORDS_BY_USER, [x, y], this.board)).scrCoords; 1266 1267 // e = Mat.eps; 1268 // if (t < this.minX() + e) { 1269 // dx = (this.X(t + e) - this.X(t)) / e; 1270 // dy = (this.Y(t + e) - this.Y(t)) / e; 1271 // } else if (t > this.maxX() - e) { 1272 // dx = (this.X(t) - this.X(t - e)) / e; 1273 // dy = (this.Y(t) - this.Y(t - e)) / e; 1274 // } else { 1275 // dx = 0.5 * (this.X(t + e) - this.X(t - e)) / e; 1276 // dy = 0.5 * (this.Y(t + e) - this.Y(t - e)) / e; 1277 // } 1278 // d = Mat.hypot(dx, dy); 1279 1280 // if (xy.side === 'left') { 1281 // dy *= -1; 1282 // } else { 1283 // dx *= -1; 1284 // } 1285 1286 // // Position left or right 1287 1288 // if (Type.exists(this.label)) { 1289 // dist = 0.5 * this.label.evalVisProp('distance') / d; 1290 // } 1291 1292 // x = c[1] + dy * this.label.size[0] * dist; 1293 // y = c[2] - dx * this.label.size[1] * dist; 1294 1295 // return new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board); 1296 1297 } 1298 c = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board, false); 1299 return Geometry.projectCoordsToCurve( 1300 c.usrCoords[1], c.usrCoords[2], 0, this, this.board 1301 )[0]; 1302 }, 1303 1304 // documented in geometry element 1305 cloneToBackground: function () { 1306 var er, 1307 copy = Type.getCloneObject(this); 1308 1309 copy.points = this.points.slice(0); 1310 copy.bezierDegree = this.bezierDegree; 1311 copy.numberPoints = this.numberPoints; 1312 1313 er = this.board.renderer.enhancedRendering; 1314 this.board.renderer.enhancedRendering = true; 1315 this.board.renderer.drawCurve(copy); 1316 this.board.renderer.enhancedRendering = er; 1317 this.traces[copy.id] = copy.rendNode; 1318 1319 return this; 1320 }, 1321 1322 // Already documented in GeometryElement 1323 bounds: function () { 1324 var minX = Infinity, 1325 maxX = -Infinity, 1326 minY = Infinity, 1327 maxY = -Infinity, 1328 l = this.points.length, 1329 i, 1330 bezier, 1331 up; 1332 1333 if (this.bezierDegree === 3) { 1334 // Add methods X(), Y() 1335 for (i = 0; i < l; i++) { 1336 this.points[i].X = Type.bind(function () { 1337 return this.usrCoords[1]; 1338 }, this.points[i]); 1339 this.points[i].Y = Type.bind(function () { 1340 return this.usrCoords[2]; 1341 }, this.points[i]); 1342 } 1343 bezier = Numerics.bezier(this.points); 1344 up = bezier[3](); 1345 minX = Numerics.fminbr( 1346 function (t) { 1347 return bezier[0](t); 1348 }, 1349 [0, up] 1350 ); 1351 maxX = Numerics.fminbr( 1352 function (t) { 1353 return -bezier[0](t); 1354 }, 1355 [0, up] 1356 ); 1357 minY = Numerics.fminbr( 1358 function (t) { 1359 return bezier[1](t); 1360 }, 1361 [0, up] 1362 ); 1363 maxY = Numerics.fminbr( 1364 function (t) { 1365 return -bezier[1](t); 1366 }, 1367 [0, up] 1368 ); 1369 1370 minX = bezier[0](minX); 1371 maxX = bezier[0](maxX); 1372 minY = bezier[1](minY); 1373 maxY = bezier[1](maxY); 1374 return [minX, maxY, maxX, minY]; 1375 } 1376 1377 // Linear segments 1378 for (i = 0; i < l; i++) { 1379 if (minX > this.points[i].usrCoords[1]) { 1380 minX = this.points[i].usrCoords[1]; 1381 } 1382 1383 if (maxX < this.points[i].usrCoords[1]) { 1384 maxX = this.points[i].usrCoords[1]; 1385 } 1386 1387 if (minY > this.points[i].usrCoords[2]) { 1388 minY = this.points[i].usrCoords[2]; 1389 } 1390 1391 if (maxY < this.points[i].usrCoords[2]) { 1392 maxY = this.points[i].usrCoords[2]; 1393 } 1394 } 1395 1396 return [minX, maxY, maxX, minY]; 1397 }, 1398 1399 // documented in element.js 1400 getParents: function () { 1401 var p = [this.xterm, this.yterm, this.minX(), this.maxX()]; 1402 1403 if (this.parents.length !== 0) { 1404 p = this.parents; 1405 } 1406 1407 return p; 1408 }, 1409 1410 /** 1411 * Shift the curve by the vector 'where'. 1412 * 1413 * @param {Array} where Array containing the x and y coordinate of the target location. 1414 * @returns {JXG.Curve} Reference to itself. 1415 */ 1416 moveTo: function (where) { 1417 // TODO add animation 1418 var delta = [], 1419 p; 1420 if (this.points.length > 0 && !this.evalVisProp('fixed')) { 1421 p = this.points[0]; 1422 if (where.length === 3) { 1423 delta = [ 1424 where[0] - p.usrCoords[0], 1425 where[1] - p.usrCoords[1], 1426 where[2] - p.usrCoords[2] 1427 ]; 1428 } else { 1429 delta = [where[0] - p.usrCoords[1], where[1] - p.usrCoords[2]]; 1430 } 1431 this.setPosition(Const.COORDS_BY_USER, delta); 1432 return this.board.update(this); 1433 } 1434 return this; 1435 }, 1436 1437 /** 1438 * If the curve is the result of a transformation applied 1439 * to a continuous curve, the glider projection has to be done 1440 * on the original curve. Otherwise there will be problems 1441 * when changing between high and low precision plotting, 1442 * since there number of points changes. 1443 * 1444 * @private 1445 * @returns {Array} [Boolean, curve]: Array contining 'true' if curve is result of a transformation, 1446 * and the source curve of the transformation. 1447 */ 1448 getTransformationSource: function () { 1449 var isTransformed, curve_org; 1450 if (Type.exists(this._transformationSource)) { 1451 curve_org = this._transformationSource; 1452 if ( 1453 curve_org.elementClass === Const.OBJECT_CLASS_CURVE //&& 1454 //curve_org.evalVisProp('curvetype') !== 'plot' 1455 ) { 1456 isTransformed = true; 1457 } 1458 } 1459 return [isTransformed, curve_org]; 1460 } 1461 1462 // See JXG.Math.Geometry.pnpoly 1463 // pnpoly: function (x_in, y_in, coord_type) { 1464 // var i, 1465 // j, 1466 // len, 1467 // x, 1468 // y, 1469 // crds, 1470 // v = this.points, 1471 // isIn = false; 1472 1473 // if (coord_type === Const.COORDS_BY_USER) { 1474 // crds = new Coords(Const.COORDS_BY_USER, [x_in, y_in], this.board); 1475 // x = crds.scrCoords[1]; 1476 // y = crds.scrCoords[2]; 1477 // } else { 1478 // x = x_in; 1479 // y = y_in; 1480 // } 1481 1482 // len = this.points.length; 1483 // for (i = 0, j = len - 2; i < len - 1; j = i++) { 1484 // if ( 1485 // v[i].scrCoords[2] > y !== v[j].scrCoords[2] > y && 1486 // x < 1487 // ((v[j].scrCoords[1] - v[i].scrCoords[1]) * (y - v[i].scrCoords[2])) / 1488 // (v[j].scrCoords[2] - v[i].scrCoords[2]) + 1489 // v[i].scrCoords[1] 1490 // ) { 1491 // isIn = !isIn; 1492 // } 1493 // } 1494 1495 // return isIn; 1496 // } 1497 } 1498 ); 1499 1500 /** 1501 * @class Curves can be defined by mappings or by discrete data sets. 1502 * In general, a curve is a mapping from R to R^2, where t maps to (x(t),y(t)). The graph is drawn for t in the interval [a,b]. 1503 * <p> 1504 * The following types of curves can be plotted: 1505 * <ul> 1506 * <li> parametric curves: t mapsto (x(t),y(t)), where x() and y() are univariate functions. 1507 * <li> polar curves: curves commonly written with polar equations like spirals and cardioids. 1508 * <li> data plots: plot line segments through a given list of coordinates. 1509 * </ul> 1510 * @pseudo 1511 * @name Curve 1512 * @augments JXG.Curve 1513 * @constructor 1514 * @type Object 1515 * @description JXG.Curve 1516 1517 * @param {function,number_function,number_function,number_function,number} x,y,a_,b_ Parent elements for Parametric Curves. 1518 * <p> 1519 * x describes the x-coordinate of the curve. It may be a function term in one variable, e.g. x(t). 1520 * In case of x being of type number, x(t) is set to a constant function. 1521 * this function at the values of the array. 1522 * </p> 1523 * <p> 1524 * y describes the y-coordinate of the curve. In case of a number, y(t) is set to the constant function 1525 * returning this number. 1526 * </p> 1527 * <p> 1528 * Further parameters are an optional number or function for the left interval border a, 1529 * and an optional number or function for the right interval border b. 1530 * </p> 1531 * <p> 1532 * Default values are a=-10 and b=10. 1533 * </p> 1534 * 1535 * @param {array_array,function,number} 1536 * 1537 * @description x,y Parent elements for Data Plots. 1538 * <p> 1539 * x and y are arrays contining the x and y coordinates of the data points which are connected by 1540 * line segments. The individual entries of x and y may also be functions. 1541 * In case of x being an array the curve type is data plot, regardless of the second parameter and 1542 * if additionally the second parameter y is a function term the data plot evaluates. 1543 * </p> 1544 * @param {function_array,function,number_function,number_function,number} 1545 * @description r,offset_,a_,b_ Parent elements for Polar Curves. 1546 * <p> 1547 * The first parameter is a function term r(phi) describing the polar curve. 1548 * </p> 1549 * <p> 1550 * The second parameter is the offset of the curve. It has to be 1551 * an array containing numbers or functions describing the offset. Default value is the origin [0,0]. 1552 * </p> 1553 * <p> 1554 * Further parameters are an optional number or function for the left interval border a, 1555 * and an optional number or function for the right interval border b. 1556 * </p> 1557 * <p> 1558 * Default values are a=-10 and b=10. 1559 * </p> 1560 * <p> 1561 * Additionally, a curve can be created by providing a curve and a transformation (or an array of transformations). 1562 * The result is a curve which is the transformation of the supplied curve. 1563 * 1564 * @see JXG.Curve 1565 * @example 1566 * // Parametric curve 1567 * // Create a curve of the form (t-sin(t), 1-cos(t), i.e. 1568 * // the cycloid curve. 1569 * var graph = board.create('curve', 1570 * [function(t){ return t-Math.sin(t);}, 1571 * function(t){ return 1-Math.cos(t);}, 1572 * 0, 2*Math.PI] 1573 * ); 1574 * </pre><div class="jxgbox" id="JXGaf9f818b-f3b6-4c4d-8c4c-e4a4078b726d" style="width: 300px; height: 300px;"></div> 1575 * <script type="text/javascript"> 1576 * var c1_board = JXG.JSXGraph.initBoard('JXGaf9f818b-f3b6-4c4d-8c4c-e4a4078b726d', {boundingbox: [-1, 5, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1577 * var graph1 = c1_board.create('curve', [function(t){ return t-Math.sin(t);},function(t){ return 1-Math.cos(t);},0, 2*Math.PI]); 1578 * </script><pre> 1579 * @example 1580 * // Data plots 1581 * // Connect a set of points given by coordinates with dashed line segments. 1582 * // The x- and y-coordinates of the points are given in two separate 1583 * // arrays. 1584 * var x = [0,1,2,3,4,5,6,7,8,9]; 1585 * var y = [9.2,1.3,7.2,-1.2,4.0,5.3,0.2,6.5,1.1,0.0]; 1586 * var graph = board.create('curve', [x,y], {dash:2}); 1587 * </pre><div class="jxgbox" id="JXG7dcbb00e-b6ff-481d-b4a8-887f5d8c6a83" style="width: 300px; height: 300px;"></div> 1588 * <script type="text/javascript"> 1589 * var c3_board = JXG.JSXGraph.initBoard('JXG7dcbb00e-b6ff-481d-b4a8-887f5d8c6a83', {boundingbox: [-1,10,10,-1], axis: true, showcopyright: false, shownavigation: false}); 1590 * var x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 1591 * var y = [9.2, 1.3, 7.2, -1.2, 4.0, 5.3, 0.2, 6.5, 1.1, 0.0]; 1592 * var graph3 = c3_board.create('curve', [x,y], {dash:2}); 1593 * </script><pre> 1594 * @example 1595 * // Polar plot 1596 * // Create a curve with the equation r(phi)= a*(1+phi), i.e. 1597 * // a cardioid. 1598 * var a = board.create('slider',[[0,2],[2,2],[0,1,2]]); 1599 * var graph = board.create('curve', 1600 * [function(phi){ return a.Value()*(1-Math.cos(phi));}, 1601 * [1,0], 1602 * 0, 2*Math.PI], 1603 * {curveType: 'polar'} 1604 * ); 1605 * </pre><div class="jxgbox" id="JXGd0bc7a2a-8124-45ca-a6e7-142321a8f8c2" style="width: 300px; height: 300px;"></div> 1606 * <script type="text/javascript"> 1607 * var c2_board = JXG.JSXGraph.initBoard('JXGd0bc7a2a-8124-45ca-a6e7-142321a8f8c2', {boundingbox: [-3,3,3,-3], axis: true, showcopyright: false, shownavigation: false}); 1608 * var a = c2_board.create('slider',[[0,2],[2,2],[0,1,2]]); 1609 * var graph2 = c2_board.create('curve', [function(phi){ return a.Value()*(1-Math.cos(phi));}, [1,0], 0, 2*Math.PI], {curveType: 'polar'}); 1610 * </script><pre> 1611 * 1612 * @example 1613 * // Draggable Bezier curve 1614 * var col, p, c; 1615 * col = 'blue'; 1616 * p = []; 1617 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 1618 * p.push(board.create('point',[1, 2.5 ], {size: 5, strokeColor:col, fillColor:col})); 1619 * p.push(board.create('point',[-1, -2.5 ], {size: 5, strokeColor:col, fillColor:col})); 1620 * p.push(board.create('point',[2, -2], {size: 5, strokeColor:col, fillColor:col})); 1621 * 1622 * c = board.create('curve', JXG.Math.Numerics.bezier(p), 1623 * {strokeColor:'red', name:"curve", strokeWidth:5, fixed: false}); // Draggable curve 1624 * c.addParents(p); 1625 * </pre><div class="jxgbox" id="JXG7bcc6280-f6eb-433e-8281-c837c3387849" style="width: 300px; height: 300px;"></div> 1626 * <script type="text/javascript"> 1627 * (function(){ 1628 * var board, col, p, c; 1629 * board = JXG.JSXGraph.initBoard('JXG7bcc6280-f6eb-433e-8281-c837c3387849', {boundingbox: [-3,3,3,-3], axis: true, showcopyright: false, shownavigation: false}); 1630 * col = 'blue'; 1631 * p = []; 1632 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 1633 * p.push(board.create('point',[1, 2.5 ], {size: 5, strokeColor:col, fillColor:col})); 1634 * p.push(board.create('point',[-1, -2.5 ], {size: 5, strokeColor:col, fillColor:col})); 1635 * p.push(board.create('point',[2, -2], {size: 5, strokeColor:col, fillColor:col})); 1636 * 1637 * c = board.create('curve', JXG.Math.Numerics.bezier(p), 1638 * {strokeColor:'red', name:"curve", strokeWidth:5, fixed: false}); // Draggable curve 1639 * c.addParents(p); 1640 * })(); 1641 * </script><pre> 1642 * 1643 * @example 1644 * // The curve cu2 is the reflection of cu1 against line li 1645 * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'}); 1646 * var reflect = board.create('transform', [li], {type: 'reflect'}); 1647 * var cu1 = board.create('curve', [[-1, -1, -0.5, -1, -1, -0.5], [-3, -2, -2, -2, -2.5, -2.5]]); 1648 * var cu2 = board.create('curve', [cu1, reflect], {strokeColor: 'red'}); 1649 * 1650 * </pre><div id="JXG866dc7a2-d448-11e7-93b3-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 1651 * <script type="text/javascript"> 1652 * (function() { 1653 * var board = JXG.JSXGraph.initBoard('JXG866dc7a2-d448-11e7-93b3-901b0e1b8723', 1654 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1655 * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'}); 1656 * var reflect = board.create('transform', [li], {type: 'reflect'}); 1657 * var cu1 = board.create('curve', [[-1, -1, -0.5, -1, -1, -0.5], [-3, -2, -2, -2, -2.5, -2.5]]); 1658 * var cu2 = board.create('curve', [cu1, reflect], {strokeColor: 'red'}); 1659 * 1660 * })(); 1661 * 1662 * </script><pre> 1663 */ 1664 JXG.createCurve = function (board, parents, attributes) { 1665 var obj, 1666 cu, 1667 attr = Type.copyAttributes(attributes, board.options, "curve"); 1668 1669 obj = board.select(parents[0], true); 1670 if ( 1671 Type.isTransformationOrArray(parents[1]) && 1672 Type.isObject(obj) && 1673 (obj.type === Const.OBJECT_TYPE_CURVE || 1674 obj.type === Const.OBJECT_TYPE_ANGLE || 1675 obj.type === Const.OBJECT_TYPE_ARC || 1676 obj.type === Const.OBJECT_TYPE_CONIC || 1677 obj.type === Const.OBJECT_TYPE_SECTOR) 1678 ) { 1679 if (obj.type === Const.OBJECT_TYPE_SECTOR) { 1680 attr = Type.copyAttributes(attributes, board.options, "sector"); 1681 } else if (obj.type === Const.OBJECT_TYPE_ARC) { 1682 attr = Type.copyAttributes(attributes, board.options, "arc"); 1683 } else if (obj.type === Const.OBJECT_TYPE_ANGLE) { 1684 if (!Type.exists(attributes.withLabel)) { 1685 attributes.withLabel = false; 1686 } 1687 attr = Type.copyAttributes(attributes, board.options, "angle"); 1688 } else { 1689 attr = Type.copyAttributes(attributes, board.options, "curve"); 1690 } 1691 attr = Type.copyAttributes(attr, board.options, "curve"); 1692 1693 cu = new JXG.Curve(board, ["x", [], []], attr); 1694 /** 1695 * @class 1696 * @ignore 1697 */ 1698 cu.updateDataArray = function () { 1699 var i, 1700 le = obj.numberPoints; 1701 this.bezierDegree = obj.bezierDegree; 1702 this.dataX = []; 1703 this.dataY = []; 1704 for (i = 0; i < le; i++) { 1705 this.dataX.push(obj.points[i].usrCoords[1]); 1706 this.dataY.push(obj.points[i].usrCoords[2]); 1707 } 1708 return this; 1709 }; 1710 cu.addTransform(parents[1]); 1711 obj.addChild(cu); 1712 cu.setParents([obj]); 1713 cu._transformationSource = obj; 1714 1715 return cu; 1716 } 1717 attr = Type.copyAttributes(attributes, board.options, "curve"); 1718 return new JXG.Curve(board, ["x"].concat(parents), attr); 1719 }; 1720 1721 JXG.registerElement("curve", JXG.createCurve); 1722 1723 /** 1724 * @class A functiongraph visualizes a map x → f(x). 1725 * The graph is displayed for x in the interval [a,b] and is a {@link Curve} element. 1726 * @pseudo 1727 * @name Functiongraph 1728 * @augments JXG.Curve 1729 * @constructor 1730 * @type JXG.Curve 1731 * @param {function_number,function_number,function} f,a_,b_ Parent elements are a function term f(x) describing the function graph. 1732 * <p> 1733 * Further, an optional number or function for the left interval border a, 1734 * and an optional number or function for the right interval border b. 1735 * <p> 1736 * Default values are a=-10 and b=10. 1737 * @see JXG.Curve 1738 * @example 1739 * // Create a function graph for f(x) = 0.5*x*x-2*x 1740 * var graph = board.create('functiongraph', 1741 * [function(x){ return 0.5*x*x-2*x;}, -2, 4] 1742 * ); 1743 * </pre><div class="jxgbox" id="JXGefd432b5-23a3-4846-ac5b-b471e668b437" style="width: 300px; height: 300px;"></div> 1744 * <script type="text/javascript"> 1745 * var alex1_board = JXG.JSXGraph.initBoard('JXGefd432b5-23a3-4846-ac5b-b471e668b437', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 1746 * var graph = alex1_board.create('functiongraph', [function(x){ return 0.5*x*x-2*x;}, -2, 4]); 1747 * </script><pre> 1748 * @example 1749 * // Create a function graph for f(x) = 0.5*x*x-2*x with variable interval 1750 * var s = board.create('slider',[[0,4],[3,4],[-2,4,5]]); 1751 * var graph = board.create('functiongraph', 1752 * [function(x){ return 0.5*x*x-2*x;}, 1753 * -2, 1754 * function(){return s.Value();}] 1755 * ); 1756 * </pre><div class="jxgbox" id="JXG4a203a84-bde5-4371-ad56-44619690bb50" style="width: 300px; height: 300px;"></div> 1757 * <script type="text/javascript"> 1758 * var alex2_board = JXG.JSXGraph.initBoard('JXG4a203a84-bde5-4371-ad56-44619690bb50', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 1759 * var s = alex2_board.create('slider',[[0,4],[3,4],[-2,4,5]]); 1760 * var graph = alex2_board.create('functiongraph', [function(x){ return 0.5*x*x-2*x;}, -2, function(){return s.Value();}]); 1761 * </script><pre> 1762 */ 1763 JXG.createFunctiongraph = function (board, parents, attributes) { 1764 var attr, 1765 par = ["x", "x"].concat(parents); // variable name and identity function for x-coordinate 1766 // par = ["x", function(x) { return x; }].concat(parents); 1767 1768 attr = Type.copyAttributes(attributes, board.options, "functiongraph"); 1769 attr = Type.copyAttributes(attr, board.options, "curve"); 1770 attr.curvetype = "functiongraph"; 1771 return new JXG.Curve(board, par, attr); 1772 }; 1773 1774 JXG.registerElement("functiongraph", JXG.createFunctiongraph); 1775 JXG.registerElement("plot", JXG.createFunctiongraph); 1776 1777 /** 1778 * @class The (natural) cubic spline curves (function graph) interpolating a set of points. 1779 * Create a dynamic spline interpolated curve given by sample points p_1 to p_n. 1780 * @pseudo 1781 * @name Spline 1782 * @augments JXG.Curve 1783 * @constructor 1784 * @type JXG.Curve 1785 * @param {JXG.Board} board Reference to the board the spline is drawn on. 1786 * @param {Array} parents Array of points the spline interpolates. This can be 1787 * <ul> 1788 * <li> an array of JSXGraph points</li> 1789 * <li> an array of coordinate pairs</li> 1790 * <li> an array of functions returning coordinate pairs</li> 1791 * <li> an array consisting of an array with x-coordinates and an array of y-coordinates</li> 1792 * </ul> 1793 * All individual entries of coordinates arrays may be numbers or functions returning numbers. 1794 * @param {Object} attributes Define color, width, ... of the spline 1795 * @returns {JXG.Curve} Returns reference to an object of type JXG.Curve. 1796 * @see JXG.Curve 1797 * @example 1798 * 1799 * var p = []; 1800 * p[0] = board.create('point', [-2,2], {size: 4, face: 'o'}); 1801 * p[1] = board.create('point', [0,-1], {size: 4, face: 'o'}); 1802 * p[2] = board.create('point', [2,0], {size: 4, face: 'o'}); 1803 * p[3] = board.create('point', [4,1], {size: 4, face: 'o'}); 1804 * 1805 * var c = board.create('spline', p, {strokeWidth:3}); 1806 * </pre><div id="JXG6c197afc-e482-11e5-b1bf-901b0e1b8723" style="width: 300px; height: 300px;"></div> 1807 * <script type="text/javascript"> 1808 * (function() { 1809 * var board = JXG.JSXGraph.initBoard('JXG6c197afc-e482-11e5-b1bf-901b0e1b8723', 1810 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1811 * 1812 * var p = []; 1813 * p[0] = board.create('point', [-2,2], {size: 4, face: 'o'}); 1814 * p[1] = board.create('point', [0,-1], {size: 4, face: 'o'}); 1815 * p[2] = board.create('point', [2,0], {size: 4, face: 'o'}); 1816 * p[3] = board.create('point', [4,1], {size: 4, face: 'o'}); 1817 * 1818 * var c = board.create('spline', p, {strokeWidth:3}); 1819 * })(); 1820 * 1821 * </script><pre> 1822 * 1823 */ 1824 JXG.createSpline = function (board, parents, attributes) { 1825 var el, funcs, ret; 1826 1827 funcs = function () { 1828 var D, 1829 x = [], 1830 y = []; 1831 1832 return [ 1833 function (t, suspended) { 1834 // Function term 1835 var i, j, c; 1836 1837 if (!suspended) { 1838 x = []; 1839 y = []; 1840 1841 // given as [x[], y[]] 1842 if ( 1843 parents.length === 2 && 1844 Type.isArray(parents[0]) && 1845 Type.isArray(parents[1]) && 1846 parents[0].length === parents[1].length 1847 ) { 1848 for (i = 0; i < parents[0].length; i++) { 1849 if (Type.isFunction(parents[0][i])) { 1850 x.push(parents[0][i]()); 1851 } else { 1852 x.push(parents[0][i]); 1853 } 1854 1855 if (Type.isFunction(parents[1][i])) { 1856 y.push(parents[1][i]()); 1857 } else { 1858 y.push(parents[1][i]); 1859 } 1860 } 1861 } else { 1862 for (i = 0; i < parents.length; i++) { 1863 if (Type.isPoint(parents[i])) { 1864 x.push(parents[i].X()); 1865 y.push(parents[i].Y()); 1866 // given as [[x1,y1], [x2, y2], ...] 1867 } else if (Type.isArray(parents[i]) && parents[i].length === 2) { 1868 for (j = 0; j < parents.length; j++) { 1869 if (Type.isFunction(parents[j][0])) { 1870 x.push(parents[j][0]()); 1871 } else { 1872 x.push(parents[j][0]); 1873 } 1874 1875 if (Type.isFunction(parents[j][1])) { 1876 y.push(parents[j][1]()); 1877 } else { 1878 y.push(parents[j][1]); 1879 } 1880 } 1881 } else if ( 1882 Type.isFunction(parents[i]) && 1883 parents[i]().length === 2 1884 ) { 1885 c = parents[i](); 1886 x.push(c[0]); 1887 y.push(c[1]); 1888 } 1889 } 1890 } 1891 1892 // The array D has only to be calculated when the position of one or more sample points 1893 // changes. Otherwise D is always the same for all points on the spline. 1894 D = Numerics.splineDef(x, y); 1895 } 1896 1897 return Numerics.splineEval(t, x, y, D); 1898 }, 1899 // minX() 1900 function () { 1901 return x[0]; 1902 }, 1903 //maxX() 1904 function () { 1905 return x[x.length - 1]; 1906 } 1907 ]; 1908 }; 1909 1910 attributes = Type.copyAttributes(attributes, board.options, "curve"); 1911 attributes.curvetype = "functiongraph"; 1912 ret = funcs(); 1913 el = new JXG.Curve(board, ["x", "x", ret[0], ret[1], ret[2]], attributes); 1914 el.setParents(parents); 1915 el.elType = "spline"; 1916 1917 return el; 1918 }; 1919 1920 /** 1921 * Register the element type spline at JSXGraph 1922 * @private 1923 */ 1924 JXG.registerElement("spline", JXG.createSpline); 1925 1926 /** 1927 * @class Cardinal spline curve through a given data set. 1928 * Create a dynamic cardinal spline interpolated curve given by sample points p_1 to p_n. 1929 * @pseudo 1930 * @name Cardinalspline 1931 * @augments JXG.Curve 1932 * @constructor 1933 * @type JXG.Curve 1934 * @param {Array} points Points array defining the cardinal spline. This can be 1935 * <ul> 1936 * <li> an array of JSXGraph points</li> 1937 * <li> an array of coordinate pairs</li> 1938 * <li> an array of functions returning coordinate pairs</li> 1939 * <li> an array consisting of an array with x-coordinates and an array of y-coordinates</li> 1940 * </ul> 1941 * All individual entries of coordinates arrays may be numbers or functions returning numbers. 1942 * @param {function,Number} tau Tension parameter 1943 * @param {String} [type='uniform'] Type of the cardinal spline, may be 'uniform' (default) or 'centripetal' 1944 * @see JXG.Curve 1945 * @example 1946 * //Create a cardinal spline out of an array of JXG points with adjustable tension 1947 * 1948 * //Create array of points 1949 * var p = []; 1950 * p.push(board.create('point',[0,0])); 1951 * p.push(board.create('point',[1,4])); 1952 * p.push(board.create('point',[4,5])); 1953 * p.push(board.create('point',[2,3])); 1954 * p.push(board.create('point',[3,0])); 1955 * 1956 * // tension 1957 * var tau = board.create('slider', [[-4,-5],[2,-5],[0.001,0.5,1]], {name:'tau'}); 1958 * var c = board.create('cardinalspline', [p, function(){ return tau.Value();}], {strokeWidth:3}); 1959 * 1960 * </pre><div id="JXG1537cb69-4d45-43aa-8fc3-c6d4f98b4cdd" class="jxgbox" style="width: 300px; height: 300px;"></div> 1961 * <script type="text/javascript"> 1962 * (function() { 1963 * var board = JXG.JSXGraph.initBoard('JXG1537cb69-4d45-43aa-8fc3-c6d4f98b4cdd', 1964 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1965 * //Create a cardinal spline out of an array of JXG points with adjustable tension 1966 * 1967 * //Create array of points 1968 * var p = []; 1969 * p.push(board.create('point',[0,0])); 1970 * p.push(board.create('point',[1,4])); 1971 * p.push(board.create('point',[4,5])); 1972 * p.push(board.create('point',[2,3])); 1973 * p.push(board.create('point',[3,0])); 1974 * 1975 * // tension 1976 * var tau = board.create('slider', [[-4,-5],[2,-5],[0.001,0.5,1]], {name:'tau'}); 1977 * var c = board.create('cardinalspline', [p, function(){ return tau.Value();}], {strokeWidth:3}); 1978 * 1979 * })(); 1980 * 1981 * </script><pre> 1982 * 1983 */ 1984 JXG.createCardinalSpline = function (board, parents, attributes) { 1985 var el, 1986 getPointLike, 1987 points, 1988 tau, 1989 type, 1990 p, 1991 q, 1992 i, 1993 le, 1994 splineArr, 1995 errStr = "\nPossible parent types: [points:array, tau:number|function, type:string]"; 1996 1997 if (!Type.exists(parents[0]) || !Type.isArray(parents[0])) { 1998 throw new Error( 1999 "JSXGraph: JXG.createCardinalSpline: argument 1 'points' has to be array of points or coordinate pairs" + 2000 errStr 2001 ); 2002 } 2003 if ( 2004 !Type.exists(parents[1]) || 2005 (!Type.isNumber(parents[1]) && !Type.isFunction(parents[1])) 2006 ) { 2007 throw new Error( 2008 "JSXGraph: JXG.createCardinalSpline: argument 2 'tau' has to be number between [0,1] or function'" + 2009 errStr 2010 ); 2011 } 2012 if (!Type.exists(parents[2]) || !Type.isString(parents[2])) { 2013 type = 'uniform'; 2014 // throw new Error( 2015 // "JSXGraph: JXG.createCardinalSpline: argument 3 'type' has to be string 'uniform' or 'centripetal'" + 2016 // errStr 2017 // ); 2018 } else { 2019 type = parents[2]; 2020 } 2021 2022 attributes = Type.copyAttributes(attributes, board.options, "curve"); 2023 attributes = Type.copyAttributes(attributes, board.options, "cardinalspline"); 2024 attributes.curvetype = "parameter"; 2025 2026 p = parents[0]; 2027 q = []; 2028 2029 // Given as [x[], y[]] 2030 if ( 2031 !attributes.isarrayofcoordinates && 2032 p.length === 2 && 2033 Type.isArray(p[0]) && 2034 Type.isArray(p[1]) && 2035 p[0].length === p[1].length 2036 ) { 2037 for (i = 0; i < p[0].length; i++) { 2038 q[i] = []; 2039 if (Type.isFunction(p[0][i])) { 2040 q[i].push(p[0][i]()); 2041 } else { 2042 q[i].push(p[0][i]); 2043 } 2044 2045 if (Type.isFunction(p[1][i])) { 2046 q[i].push(p[1][i]()); 2047 } else { 2048 q[i].push(p[1][i]); 2049 } 2050 } 2051 } else { 2052 // given as [[x0, y0], [x1, y1], point, ...] 2053 for (i = 0; i < p.length; i++) { 2054 if (Type.isString(p[i])) { 2055 q.push(board.select(p[i])); 2056 } else if (Type.isPoint(p[i])) { 2057 q.push(p[i]); 2058 // given as [[x0,y0], [x1, y2], ...] 2059 } else if (Type.isArray(p[i]) && p[i].length === 2) { 2060 q[i] = []; 2061 if (Type.isFunction(p[i][0])) { 2062 q[i].push(p[i][0]()); 2063 } else { 2064 q[i].push(p[i][0]); 2065 } 2066 2067 if (Type.isFunction(p[i][1])) { 2068 q[i].push(p[i][1]()); 2069 } else { 2070 q[i].push(p[i][1]); 2071 } 2072 } else if (Type.isFunction(p[i]) && p[i]().length === 2) { 2073 q.push(parents[i]()); 2074 } 2075 } 2076 } 2077 2078 if (attributes.createpoints === true) { 2079 points = Type.providePoints(board, q, attributes, "cardinalspline", ["points"]); 2080 } else { 2081 points = []; 2082 2083 /** 2084 * @ignore 2085 */ 2086 getPointLike = function (ii) { 2087 return { 2088 X: function () { 2089 return q[ii][0]; 2090 }, 2091 Y: function () { 2092 return q[ii][1]; 2093 }, 2094 Dist: function (p) { 2095 var dx = this.X() - p.X(), 2096 dy = this.Y() - p.Y(); 2097 2098 return Mat.hypot(dx, dy); 2099 } 2100 }; 2101 }; 2102 2103 for (i = 0; i < q.length; i++) { 2104 if (Type.isPoint(q[i])) { 2105 points.push(q[i]); 2106 } else { 2107 points.push(getPointLike(i)); 2108 } 2109 } 2110 } 2111 2112 tau = parents[1]; 2113 // type = parents[2]; 2114 2115 splineArr = ["x"].concat(Numerics.CardinalSpline(points, tau, type)); 2116 2117 el = new JXG.Curve(board, splineArr, attributes); 2118 le = points.length; 2119 el.setParents(points); 2120 for (i = 0; i < le; i++) { 2121 p = points[i]; 2122 if (Type.isPoint(p)) { 2123 if (Type.exists(p._is_new)) { 2124 el.addChild(p); 2125 delete p._is_new; 2126 } else { 2127 p.addChild(el); 2128 } 2129 } 2130 } 2131 el.elType = "cardinalspline"; 2132 2133 return el; 2134 }; 2135 2136 /** 2137 * Register the element type cardinalspline at JSXGraph 2138 * @private 2139 */ 2140 JXG.registerElement("cardinalspline", JXG.createCardinalSpline); 2141 2142 /** 2143 * @class Interpolate data points by the spline curve from Metapost (by Donald Knuth and John Hobby). 2144 * Create a dynamic metapost spline interpolated curve given by sample points p_1 to p_n. 2145 * @pseudo 2146 * @name Metapostspline 2147 * @augments JXG.Curve 2148 * @constructor 2149 * @type JXG.Curve 2150 * @param {JXG.Board} board Reference to the board the metapost spline is drawn on. 2151 * @param {Array} parents Array with two entries. 2152 * <p> 2153 * First entry: Array of points the spline interpolates. This can be 2154 * <ul> 2155 * <li> an array of JSXGraph points</li> 2156 * <li> an object of coordinate pairs</li> 2157 * <li> an array of functions returning coordinate pairs</li> 2158 * <li> an array consisting of an array with x-coordinates and an array of y-coordinates</li> 2159 * </ul> 2160 * All individual entries of coordinates arrays may be numbers or functions returning numbers. 2161 * <p> 2162 * Second entry: JavaScript object containing the control values like tension, direction, curl. 2163 * @param {Object} attributes Define color, width, ... of the metapost spline 2164 * @returns {JXG.Curve} Returns reference to an object of type JXG.Curve. 2165 * @see JXG.Curve 2166 * @example 2167 * var po = [], 2168 * attr = { 2169 * size: 5, 2170 * color: 'red' 2171 * }, 2172 * controls; 2173 * 2174 * var tension = board.create('slider', [[-3, 6], [3, 6], [0, 1, 20]], {name: 'tension'}); 2175 * var curl = board.create('slider', [[-3, 5], [3, 5], [0, 1, 30]], {name: 'curl A, D'}); 2176 * var dir = board.create('slider', [[-3, 4], [3, 4], [-180, 0, 180]], {name: 'direction B'}); 2177 * 2178 * po.push(board.create('point', [-3, -3])); 2179 * po.push(board.create('point', [0, -3])); 2180 * po.push(board.create('point', [4, -5])); 2181 * po.push(board.create('point', [6, -2])); 2182 * 2183 * var controls = { 2184 * tension: function() {return tension.Value(); }, 2185 * direction: { 1: function() {return dir.Value(); } }, 2186 * curl: { 0: function() {return curl.Value(); }, 2187 * 3: function() {return curl.Value(); } 2188 * }, 2189 * isClosed: false 2190 * }; 2191 * 2192 * // Plot a metapost curve 2193 * var cu = board.create('metapostspline', [po, controls], {strokeColor: 'blue', strokeWidth: 2}); 2194 * 2195 * 2196 * </pre><div id="JXGb8c6ffed-7419-41a3-9e55-3754b2327ae9" class="jxgbox" style="width: 300px; height: 300px;"></div> 2197 * <script type="text/javascript"> 2198 * (function() { 2199 * var board = JXG.JSXGraph.initBoard('JXGb8c6ffed-7419-41a3-9e55-3754b2327ae9', 2200 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2201 * var po = [], 2202 * attr = { 2203 * size: 5, 2204 * color: 'red' 2205 * }, 2206 * controls; 2207 * 2208 * var tension = board.create('slider', [[-3, 6], [3, 6], [0, 1, 20]], {name: 'tension'}); 2209 * var curl = board.create('slider', [[-3, 5], [3, 5], [0, 1, 30]], {name: 'curl A, D'}); 2210 * var dir = board.create('slider', [[-3, 4], [3, 4], [-180, 0, 180]], {name: 'direction B'}); 2211 * 2212 * po.push(board.create('point', [-3, -3])); 2213 * po.push(board.create('point', [0, -3])); 2214 * po.push(board.create('point', [4, -5])); 2215 * po.push(board.create('point', [6, -2])); 2216 * 2217 * var controls = { 2218 * tension: function() {return tension.Value(); }, 2219 * direction: { 1: function() {return dir.Value(); } }, 2220 * curl: { 0: function() {return curl.Value(); }, 2221 * 3: function() {return curl.Value(); } 2222 * }, 2223 * isClosed: false 2224 * }; 2225 * 2226 * // Plot a metapost curve 2227 * var cu = board.create('metapostspline', [po, controls], {strokeColor: 'blue', strokeWidth: 2}); 2228 * 2229 * 2230 * })(); 2231 * 2232 * </script><pre> 2233 * 2234 */ 2235 JXG.createMetapostSpline = function (board, parents, attributes) { 2236 var el, 2237 getPointLike, 2238 points, 2239 controls, 2240 p, 2241 q, 2242 i, 2243 le, 2244 errStr = "\nPossible parent types: [points:array, controls:object"; 2245 2246 if (!Type.exists(parents[0]) || !Type.isArray(parents[0])) { 2247 throw new Error( 2248 "JSXGraph: JXG.createMetapostSpline: argument 1 'points' has to be array of points or coordinate pairs" + 2249 errStr 2250 ); 2251 } 2252 if (!Type.exists(parents[1]) || !Type.isObject(parents[1])) { 2253 throw new Error( 2254 "JSXGraph: JXG.createMetapostSpline: argument 2 'controls' has to be a JavaScript object'" + 2255 errStr 2256 ); 2257 } 2258 2259 attributes = Type.copyAttributes(attributes, board.options, "curve"); 2260 attributes = Type.copyAttributes(attributes, board.options, "metapostspline"); 2261 attributes.curvetype = "parameter"; 2262 2263 p = parents[0]; 2264 q = []; 2265 2266 // given as [x[], y[]] 2267 if ( 2268 !attributes.isarrayofcoordinates && 2269 p.length === 2 && 2270 Type.isArray(p[0]) && 2271 Type.isArray(p[1]) && 2272 p[0].length === p[1].length 2273 ) { 2274 for (i = 0; i < p[0].length; i++) { 2275 q[i] = []; 2276 if (Type.isFunction(p[0][i])) { 2277 q[i].push(p[0][i]()); 2278 } else { 2279 q[i].push(p[0][i]); 2280 } 2281 2282 if (Type.isFunction(p[1][i])) { 2283 q[i].push(p[1][i]()); 2284 } else { 2285 q[i].push(p[1][i]); 2286 } 2287 } 2288 } else { 2289 // given as [[x0, y0], [x1, y1], point, ...] 2290 for (i = 0; i < p.length; i++) { 2291 if (Type.isString(p[i])) { 2292 q.push(board.select(p[i])); 2293 } else if (Type.isPoint(p[i])) { 2294 q.push(p[i]); 2295 // given as [[x0,y0], [x1, y2], ...] 2296 } else if (Type.isArray(p[i]) && p[i].length === 2) { 2297 q[i] = []; 2298 if (Type.isFunction(p[i][0])) { 2299 q[i].push(p[i][0]()); 2300 } else { 2301 q[i].push(p[i][0]); 2302 } 2303 2304 if (Type.isFunction(p[i][1])) { 2305 q[i].push(p[i][1]()); 2306 } else { 2307 q[i].push(p[i][1]); 2308 } 2309 } else if (Type.isFunction(p[i]) && p[i]().length === 2) { 2310 q.push(parents[i]()); 2311 } 2312 } 2313 } 2314 2315 if (attributes.createpoints === true) { 2316 points = Type.providePoints(board, q, attributes, 'metapostspline', ['points']); 2317 } else { 2318 points = []; 2319 2320 /** 2321 * @ignore 2322 */ 2323 getPointLike = function (ii) { 2324 return { 2325 X: function () { 2326 return q[ii][0]; 2327 }, 2328 Y: function () { 2329 return q[ii][1]; 2330 } 2331 }; 2332 }; 2333 2334 for (i = 0; i < q.length; i++) { 2335 if (Type.isPoint(q[i])) { 2336 points.push(q[i]); 2337 } else { 2338 points.push(getPointLike); 2339 } 2340 } 2341 } 2342 2343 controls = parents[1]; 2344 2345 el = new JXG.Curve(board, ["t", [], [], 0, p.length - 1], attributes); 2346 /** 2347 * @class 2348 * @ignore 2349 */ 2350 el.updateDataArray = function () { 2351 var res, 2352 i, 2353 len = points.length, 2354 p = []; 2355 2356 for (i = 0; i < len; i++) { 2357 p.push([points[i].X(), points[i].Y()]); 2358 } 2359 2360 res = Metapost.curve(p, controls); 2361 this.dataX = res[0]; 2362 this.dataY = res[1]; 2363 }; 2364 el.bezierDegree = 3; 2365 2366 le = points.length; 2367 el.setParents(points); 2368 for (i = 0; i < le; i++) { 2369 if (Type.isPoint(points[i])) { 2370 points[i].addChild(el); 2371 } 2372 } 2373 el.elType = "metapostspline"; 2374 2375 return el; 2376 }; 2377 2378 JXG.registerElement("metapostspline", JXG.createMetapostSpline); 2379 2380 /** 2381 * @class Visualize the Riemann sum which is an approximation of an integral by a finite sum. 2382 * It is realized as a special curve. 2383 * The returned element has the method Value() which returns the sum of the areas of the bars. 2384 * <p> 2385 * In case of type "simpson" and "trapezoidal", the horizontal line approximating the function value 2386 * is replaced by a parabola or a secant. IN case of "simpson", 2387 * the parabola is approximated visually by a polygonal chain of fixed step width. 2388 * 2389 * @pseudo 2390 * @name Riemannsum 2391 * @augments JXG.Curve 2392 * @constructor 2393 * @type Curve 2394 * @param {function,array_number,function_string,function_function,number_function,number} f,n,type_,a_,b_ Parent elements of Riemannsum are a 2395 * Either a function term f(x) describing the function graph which is filled by the Riemann bars, or 2396 * an array consisting of two functions and the area between is filled by the Riemann bars. 2397 * <p> 2398 * n determines the number of bars, it is either a fixed number or a function. 2399 * <p> 2400 * type is a string or function returning one of the values: 'left', 'right', 'middle', 'lower', 'upper', 'random', 'simpson', or 'trapezoidal'. 2401 * Default value is 'left'. "simpson" is Simpson's 1/3 rule. 2402 * <p> 2403 * Further parameters are an optional number or function for the left interval border a, 2404 * and an optional number or function for the right interval border b. 2405 * <p> 2406 * Default values are a=-10 and b=10. 2407 * @see JXG.Curve 2408 * @example 2409 * // Create Riemann sums for f(x) = 0.5*x*x-2*x. 2410 * var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1}); 2411 * var f = function(x) { return 0.5*x*x-2*x; }; 2412 * var r = board.create('riemannsum', 2413 * [f, function(){return s.Value();}, 'upper', -2, 5], 2414 * {fillOpacity:0.4} 2415 * ); 2416 * var g = board.create('functiongraph',[f, -2, 5]); 2417 * var t = board.create('text',[-2,-2, function(){ return 'Sum=' + JXG.toFixed(r.Value(), 4); }]); 2418 * </pre><div class="jxgbox" id="JXG940f40cc-2015-420d-9191-c5d83de988cf" style="width: 300px; height: 300px;"></div> 2419 * <script type="text/javascript"> 2420 * (function(){ 2421 * var board = JXG.JSXGraph.initBoard('JXG940f40cc-2015-420d-9191-c5d83de988cf', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 2422 * var f = function(x) { return 0.5*x*x-2*x; }; 2423 * var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1}); 2424 * var r = board.create('riemannsum', [f, function(){return s.Value();}, 'upper', -2, 5], {fillOpacity:0.4}); 2425 * var g = board.create('functiongraph', [f, -2, 5]); 2426 * var t = board.create('text',[-2,-2, function(){ return 'Sum=' + JXG.toFixed(r.Value(), 4); }]); 2427 * })(); 2428 * </script><pre> 2429 * 2430 * @example 2431 * // Riemann sum between two functions 2432 * var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1}); 2433 * var g = function(x) { return 0.5*x*x-2*x; }; 2434 * var f = function(x) { return -x*(x-4); }; 2435 * var r = board.create('riemannsum', 2436 * [[g,f], function(){return s.Value();}, 'lower', 0, 4], 2437 * {fillOpacity:0.4} 2438 * ); 2439 * var f = board.create('functiongraph',[f, -2, 5]); 2440 * var g = board.create('functiongraph',[g, -2, 5]); 2441 * var t = board.create('text',[-2,-2, function(){ return 'Sum=' + JXG.toFixed(r.Value(), 4); }]); 2442 * </pre><div class="jxgbox" id="JXGf9a7ba38-b50f-4a32-a873-2f3bf9caee79" style="width: 300px; height: 300px;"></div> 2443 * <script type="text/javascript"> 2444 * (function(){ 2445 * var board = JXG.JSXGraph.initBoard('JXGf9a7ba38-b50f-4a32-a873-2f3bf9caee79', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 2446 * var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1}); 2447 * var g = function(x) { return 0.5*x*x-2*x; }; 2448 * var f = function(x) { return -x*(x-4); }; 2449 * var r = board.create('riemannsum', 2450 * [[g,f], function(){return s.Value();}, 'lower', 0, 4], 2451 * {fillOpacity:0.4} 2452 * ); 2453 * var f = board.create('functiongraph',[f, -2, 5]); 2454 * var g = board.create('functiongraph',[g, -2, 5]); 2455 * var t = board.create('text',[-2,-2, function(){ return 'Sum=' + JXG.toFixed(r.Value(), 4); }]); 2456 * })(); 2457 * </script><pre> 2458 */ 2459 JXG.createRiemannsum = function (board, parents, attributes) { 2460 var n, type, f, par, c, attr; 2461 2462 attr = Type.copyAttributes(attributes, board.options, "riemannsum"); 2463 attr.curvetype = "plot"; 2464 2465 f = parents[0]; 2466 n = Type.createFunction(parents[1], board, ""); 2467 2468 if (!Type.exists(n)) { 2469 throw new Error( 2470 "JSXGraph: JXG.createRiemannsum: argument '2' n has to be number or function." + 2471 "\nPossible parent types: [function,n:number|function,type,start:number|function,end:number|function]" 2472 ); 2473 } 2474 2475 if (typeof parents[2] === 'string') { 2476 parents[2] = '\'' + parents[2] + '\''; 2477 } 2478 2479 type = Type.createFunction(parents[2], board, ""); 2480 if (!Type.exists(type)) { 2481 throw new Error( 2482 "JSXGraph: JXG.createRiemannsum: argument 3 'type' has to be string or function." + 2483 "\nPossible parent types: [function,n:number|function,type,start:number|function,end:number|function]" 2484 ); 2485 } 2486 2487 par = [[0], [0]].concat(parents.slice(3)); 2488 2489 c = board.create("curve", par, attr); 2490 2491 c.sum = 0.0; 2492 /** 2493 * Returns the value of the Riemann sum, i.e. the sum of the (signed) areas of the rectangles. 2494 * @name Value 2495 * @memberOf Riemannsum.prototype 2496 * @function 2497 * @returns {Number} value of Riemann sum. 2498 */ 2499 c.Value = function () { 2500 return this.sum; 2501 }; 2502 2503 /** 2504 * @class 2505 * @ignore 2506 */ 2507 c.updateDataArray = function () { 2508 var u = Numerics.riemann(f, n(), type(), this.minX(), this.maxX()); 2509 this.dataX = u[0]; 2510 this.dataY = u[1]; 2511 2512 // Update "Riemann sum" 2513 this.sum = u[2]; 2514 }; 2515 2516 c.addParentsFromJCFunctions([n, type]); 2517 2518 return c; 2519 }; 2520 2521 JXG.registerElement("riemannsum", JXG.createRiemannsum); 2522 2523 /** 2524 * @class A trace curve is simple locus curve showing the orbit of a point that depends on a glider point. 2525 * @pseudo 2526 * @name Tracecurve 2527 * @augments JXG.Curve 2528 * @constructor 2529 * @type Object 2530 * @descript JXG.Curve 2531 * @param {Point} Parent elements of Tracecurve are a 2532 * glider point and a point whose locus is traced. 2533 * @param {point} 2534 * @see JXG.Curve 2535 * @example 2536 * // Create trace curve. 2537 * var c1 = board.create('circle',[[0, 0], [2, 0]]), 2538 * p1 = board.create('point',[-3, 1]), 2539 * g1 = board.create('glider',[2, 1, c1]), 2540 * s1 = board.create('segment',[g1, p1]), 2541 * p2 = board.create('midpoint',[s1]), 2542 * curve = board.create('tracecurve', [g1, p2]); 2543 * 2544 * </pre><div class="jxgbox" id="JXG5749fb7d-04fc-44d2-973e-45c1951e29ad" style="width: 300px; height: 300px;"></div> 2545 * <script type="text/javascript"> 2546 * var tc1_board = JXG.JSXGraph.initBoard('JXG5749fb7d-04fc-44d2-973e-45c1951e29ad', {boundingbox: [-4, 4, 4, -4], axis: false, showcopyright: false, shownavigation: false}); 2547 * var c1 = tc1_board.create('circle',[[0, 0], [2, 0]]), 2548 * p1 = tc1_board.create('point',[-3, 1]), 2549 * g1 = tc1_board.create('glider',[2, 1, c1]), 2550 * s1 = tc1_board.create('segment',[g1, p1]), 2551 * p2 = tc1_board.create('midpoint',[s1]), 2552 * curve = tc1_board.create('tracecurve', [g1, p2]); 2553 * </script><pre> 2554 */ 2555 JXG.createTracecurve = function (board, parents, attributes) { 2556 var c, glider, tracepoint, attr; 2557 2558 if (parents.length !== 2) { 2559 throw new Error( 2560 "JSXGraph: Can't create trace curve with given parent'" + 2561 "\nPossible parent types: [glider, point]" 2562 ); 2563 } 2564 2565 glider = board.select(parents[0]); 2566 tracepoint = board.select(parents[1]); 2567 2568 if (glider.type !== Const.OBJECT_TYPE_GLIDER || !Type.isPoint(tracepoint)) { 2569 throw new Error( 2570 "JSXGraph: Can't create trace curve with parent types '" + 2571 typeof parents[0] + 2572 "' and '" + 2573 typeof parents[1] + 2574 "'." + 2575 "\nPossible parent types: [glider, point]" 2576 ); 2577 } 2578 2579 attr = Type.copyAttributes(attributes, board.options, "tracecurve"); 2580 attr.curvetype = "plot"; 2581 c = board.create("curve", [[0], [0]], attr); 2582 2583 /** 2584 * @class 2585 * @ignore 2586 */ 2587 c.updateDataArray = function () { 2588 var i, step, t, el, pEl, x, y, from, 2589 savetrace, 2590 le = this.visProp.numberpoints, 2591 savePos = glider.position, 2592 slideObj = glider.slideObject, 2593 mi = slideObj.minX(), 2594 ma = slideObj.maxX(); 2595 2596 // set step width 2597 step = (ma - mi) / le; 2598 this.dataX = []; 2599 this.dataY = []; 2600 2601 /* 2602 * For gliders on circles and lines a closed curve is computed. 2603 * For gliders on curves the curve is not closed. 2604 */ 2605 if (slideObj.elementClass !== Const.OBJECT_CLASS_CURVE) { 2606 le++; 2607 } 2608 2609 // Loop over all steps 2610 for (i = 0; i < le; i++) { 2611 t = mi + i * step; 2612 x = slideObj.X(t) / slideObj.Z(t); 2613 y = slideObj.Y(t) / slideObj.Z(t); 2614 2615 // Position the glider 2616 glider.setPositionDirectly(Const.COORDS_BY_USER, [x, y]); 2617 from = false; 2618 2619 // Update all elements from the glider up to the trace element 2620 for (el in this.board.objects) { 2621 if (this.board.objects.hasOwnProperty(el)) { 2622 pEl = this.board.objects[el]; 2623 2624 if (pEl === glider) { 2625 from = true; 2626 } 2627 2628 if (from && pEl.needsRegularUpdate) { 2629 // Save the trace mode of the element 2630 savetrace = pEl.visProp.trace; 2631 pEl.visProp.trace = false; 2632 pEl.needsUpdate = true; 2633 pEl.update(true); 2634 2635 // Restore the trace mode 2636 pEl.visProp.trace = savetrace; 2637 if (pEl === tracepoint) { 2638 break; 2639 } 2640 } 2641 } 2642 } 2643 2644 // Store the position of the trace point 2645 this.dataX[i] = tracepoint.X(); 2646 this.dataY[i] = tracepoint.Y(); 2647 } 2648 2649 // Restore the original position of the glider 2650 glider.position = savePos; 2651 from = false; 2652 2653 // Update all elements from the glider to the trace point 2654 for (el in this.board.objects) { 2655 if (this.board.objects.hasOwnProperty(el)) { 2656 pEl = this.board.objects[el]; 2657 if (pEl === glider) { 2658 from = true; 2659 } 2660 2661 if (from && pEl.needsRegularUpdate) { 2662 savetrace = pEl.visProp.trace; 2663 pEl.visProp.trace = false; 2664 pEl.needsUpdate = true; 2665 pEl.update(true); 2666 pEl.visProp.trace = savetrace; 2667 2668 if (pEl === tracepoint) { 2669 break; 2670 } 2671 } 2672 } 2673 } 2674 }; 2675 2676 return c; 2677 }; 2678 2679 JXG.registerElement("tracecurve", JXG.createTracecurve); 2680 2681 /** 2682 * @class A step function is a function graph that is piecewise constant. 2683 * 2684 * In case the data points should be updated after creation time, 2685 * they can be accessed by curve.xterm and curve.yterm. 2686 * @pseudo 2687 * @name Stepfunction 2688 * @augments JXG.Curve 2689 * @constructor 2690 * @type Curve 2691 * @description JXG.Curve 2692 * @param {Array|Function} Parent1 elements of Stepfunction are two arrays containing the coordinates. 2693 * @param {Array|Function} Parent2 2694 * @see JXG.Curve 2695 * @example 2696 * // Create step function. 2697 var curve = board.create('stepfunction', [[0,1,2,3,4,5], [1,3,0,2,2,1]]); 2698 2699 * </pre><div class="jxgbox" id="JXG32342ec9-ad17-4339-8a97-ff23dc34f51a" style="width: 300px; height: 300px;"></div> 2700 * <script type="text/javascript"> 2701 * var sf1_board = JXG.JSXGraph.initBoard('JXG32342ec9-ad17-4339-8a97-ff23dc34f51a', {boundingbox: [-1, 5, 6, -2], axis: true, showcopyright: false, shownavigation: false}); 2702 * var curve = sf1_board.create('stepfunction', [[0,1,2,3,4,5], [1,3,0,2,2,1]]); 2703 * </script><pre> 2704 */ 2705 JXG.createStepfunction = function (board, parents, attributes) { 2706 var c, attr; 2707 if (parents.length !== 2) { 2708 throw new Error( 2709 "JSXGraph: Can't create step function with given parent'" + 2710 "\nPossible parent types: [array, array|function]" 2711 ); 2712 } 2713 2714 attr = Type.copyAttributes(attributes, board.options, "stepfunction"); 2715 c = board.create("curve", parents, attr); 2716 /** 2717 * @class 2718 * @ignore 2719 */ 2720 c.updateDataArray = function () { 2721 var i, 2722 j = 0, 2723 len = this.xterm.length; 2724 2725 this.dataX = []; 2726 this.dataY = []; 2727 2728 if (len === 0) { 2729 return; 2730 } 2731 2732 this.dataX[j] = this.xterm[0]; 2733 this.dataY[j] = this.yterm[0]; 2734 ++j; 2735 2736 for (i = 1; i < len; ++i) { 2737 this.dataX[j] = this.xterm[i]; 2738 this.dataY[j] = this.dataY[j - 1]; 2739 ++j; 2740 this.dataX[j] = this.xterm[i]; 2741 this.dataY[j] = this.yterm[i]; 2742 ++j; 2743 } 2744 }; 2745 2746 return c; 2747 }; 2748 2749 JXG.registerElement("stepfunction", JXG.createStepfunction); 2750 2751 /** 2752 * @class A curve visualizing the function graph of the (numerical) derivative of a given curve. 2753 * 2754 * @pseudo 2755 * @name Derivative 2756 * @augments JXG.Curve 2757 * @constructor 2758 * @type JXG.Curve 2759 * @param {JXG.Curve} Parent Curve for which the derivative is generated. 2760 * @see JXG.Curve 2761 * @example 2762 * var cu = board.create('cardinalspline', [[[-3,0], [-1,2], [0,1], [2,0], [3,1]], 0.5, 'centripetal'], {createPoints: false}); 2763 * var d = board.create('derivative', [cu], {dash: 2}); 2764 * 2765 * </pre><div id="JXGb9600738-1656-11e8-8184-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 2766 * <script type="text/javascript"> 2767 * (function() { 2768 * var board = JXG.JSXGraph.initBoard('JXGb9600738-1656-11e8-8184-901b0e1b8723', 2769 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2770 * var cu = board.create('cardinalspline', [[[-3,0], [-1,2], [0,1], [2,0], [3,1]], 0.5, 'centripetal'], {createPoints: false}); 2771 * var d = board.create('derivative', [cu], {dash: 2}); 2772 * 2773 * })(); 2774 * 2775 * </script><pre> 2776 * 2777 */ 2778 JXG.createDerivative = function (board, parents, attributes) { 2779 var c, curve, dx, dy, attr; 2780 2781 if (parents.length !== 1 && parents[0].class !== Const.OBJECT_CLASS_CURVE) { 2782 throw new Error( 2783 "JSXGraph: Can't create derivative curve with given parent'" + 2784 "\nPossible parent types: [curve]" 2785 ); 2786 } 2787 2788 attr = Type.copyAttributes(attributes, board.options, "curve"); 2789 2790 curve = parents[0]; 2791 dx = Numerics.D(curve.X); 2792 dy = Numerics.D(curve.Y); 2793 2794 c = board.create( 2795 "curve", 2796 [ 2797 function (t) { 2798 return curve.X(t); 2799 }, 2800 function (t) { 2801 return dy(t) / dx(t); 2802 }, 2803 curve.minX(), 2804 curve.maxX() 2805 ], 2806 attr 2807 ); 2808 2809 c.setParents(curve); 2810 2811 return c; 2812 }; 2813 2814 JXG.registerElement("derivative", JXG.createDerivative); 2815 2816 /** 2817 * @class The path forming the intersection of two closed path elements. 2818 * The elements may be of type curve, circle, polygon, inequality. 2819 * If one element is a curve, it has to be closed. 2820 * The resulting element is of type curve. 2821 * @pseudo 2822 * @name CurveIntersection 2823 * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve1 First element which is intersected 2824 * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve2 Second element which is intersected 2825 * @augments JXG.Curve 2826 * @constructor 2827 * @type JXG.Curve 2828 * 2829 * @example 2830 * var f = board.create('functiongraph', ['cos(x)']); 2831 * var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1}); 2832 * var circ = board.create('circle', [[0,0], 4]); 2833 * var clip = board.create('curveintersection', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6}); 2834 * 2835 * </pre><div id="JXGe2948257-8835-4276-9164-8acccb48e8d4" class="jxgbox" style="width: 300px; height: 300px;"></div> 2836 * <script type="text/javascript"> 2837 * (function() { 2838 * var board = JXG.JSXGraph.initBoard('JXGe2948257-8835-4276-9164-8acccb48e8d4', 2839 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2840 * var f = board.create('functiongraph', ['cos(x)']); 2841 * var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1}); 2842 * var circ = board.create('circle', [[0,0], 4]); 2843 * var clip = board.create('curveintersection', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6}); 2844 * 2845 * })(); 2846 * 2847 * </script><pre> 2848 * 2849 */ 2850 JXG.createCurveIntersection = function (board, parents, attributes) { 2851 var c; 2852 2853 if (parents.length !== 2) { 2854 throw new Error( 2855 "JSXGraph: Can't create curve intersection with given parent'" + 2856 "\nPossible parent types: [array, array|function]" 2857 ); 2858 } 2859 2860 c = board.create("curve", [[], []], attributes); 2861 /** 2862 * @class 2863 * @ignore 2864 */ 2865 c.updateDataArray = function () { 2866 var a = Clip.intersection(parents[0], parents[1], this.board); 2867 this.dataX = a[0]; 2868 this.dataY = a[1]; 2869 }; 2870 return c; 2871 }; 2872 2873 /** 2874 * @class The path forming the union of two closed path elements. 2875 * The elements may be of type curve, circle, polygon, inequality. 2876 * If one element is a curve, it has to be closed. 2877 * The resulting element is of type curve. 2878 * @pseudo 2879 * @name CurveUnion 2880 * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve1 First element defining the union 2881 * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve2 Second element defining the union 2882 * @augments JXG.Curve 2883 * @constructor 2884 * @type JXG.Curve 2885 * 2886 * @example 2887 * var f = board.create('functiongraph', ['cos(x)']); 2888 * var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1}); 2889 * var circ = board.create('circle', [[0,0], 4]); 2890 * var clip = board.create('curveunion', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6}); 2891 * 2892 * </pre><div id="JXGe2948257-8835-4276-9164-8acccb48e8d4" class="jxgbox" style="width: 300px; height: 300px;"></div> 2893 * <script type="text/javascript"> 2894 * (function() { 2895 * var board = JXG.JSXGraph.initBoard('JXGe2948257-8835-4276-9164-8acccb48e8d4', 2896 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2897 * var f = board.create('functiongraph', ['cos(x)']); 2898 * var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1}); 2899 * var circ = board.create('circle', [[0,0], 4]); 2900 * var clip = board.create('curveunion', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6}); 2901 * 2902 * })(); 2903 * 2904 * </script><pre> 2905 * 2906 */ 2907 JXG.createCurveUnion = function (board, parents, attributes) { 2908 var c; 2909 2910 if (parents.length !== 2) { 2911 throw new Error( 2912 "JSXGraph: Can't create curve union with given parent'" + 2913 "\nPossible parent types: [array, array|function]" 2914 ); 2915 } 2916 2917 c = board.create("curve", [[], []], attributes); 2918 /** 2919 * @class 2920 * @ignore 2921 */ 2922 c.updateDataArray = function () { 2923 var a = Clip.union(parents[0], parents[1], this.board); 2924 this.dataX = a[0]; 2925 this.dataY = a[1]; 2926 }; 2927 return c; 2928 }; 2929 2930 /** 2931 * @class The path forming the difference of two closed path elements. 2932 * The elements may be of type curve, circle, polygon, inequality. 2933 * If one element is a curve, it has to be closed. 2934 * The resulting element is of type curve. 2935 * @pseudo 2936 * @name CurveDifference 2937 * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve1 First element from which the second element is "subtracted" 2938 * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve2 Second element which is subtracted from the first element 2939 * @augments JXG.Curve 2940 * @constructor 2941 * @type JXG.Curve 2942 * 2943 * @example 2944 * var f = board.create('functiongraph', ['cos(x)']); 2945 * var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1}); 2946 * var circ = board.create('circle', [[0,0], 4]); 2947 * var clip = board.create('curvedifference', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6}); 2948 * 2949 * </pre><div id="JXGe2948257-8835-4276-9164-8acccb48e8d4" class="jxgbox" style="width: 300px; height: 300px;"></div> 2950 * <script type="text/javascript"> 2951 * (function() { 2952 * var board = JXG.JSXGraph.initBoard('JXGe2948257-8835-4276-9164-8acccb48e8d4', 2953 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2954 * var f = board.create('functiongraph', ['cos(x)']); 2955 * var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1}); 2956 * var circ = board.create('circle', [[0,0], 4]); 2957 * var clip = board.create('curvedifference', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6}); 2958 * 2959 * })(); 2960 * 2961 * </script><pre> 2962 * 2963 */ 2964 JXG.createCurveDifference = function (board, parents, attributes) { 2965 var c; 2966 2967 if (parents.length !== 2) { 2968 throw new Error( 2969 "JSXGraph: Can't create curve difference with given parent'" + 2970 "\nPossible parent types: [array, array|function]" 2971 ); 2972 } 2973 2974 c = board.create("curve", [[], []], attributes); 2975 /** 2976 * @class 2977 * @ignore 2978 */ 2979 c.updateDataArray = function () { 2980 var a = Clip.difference(parents[0], parents[1], this.board); 2981 this.dataX = a[0]; 2982 this.dataY = a[1]; 2983 }; 2984 return c; 2985 }; 2986 2987 JXG.registerElement("curvedifference", JXG.createCurveDifference); 2988 JXG.registerElement("curveintersection", JXG.createCurveIntersection); 2989 JXG.registerElement("curveunion", JXG.createCurveUnion); 2990 2991 // /** 2992 // * @class Concat of two path elements, in general neither is a closed path. The parent elements have to be curves, too. 2993 // * The resulting element is of type curve. The curve points are simply concatenated. 2994 // * @pseudo 2995 // * @name CurveConcat 2996 // * @param {JXG.Curve} curve1 First curve element. 2997 // * @param {JXG.Curve} curve2 Second curve element. 2998 // * @augments JXG.Curve 2999 // * @constructor 3000 // * @type JXG.Curve 3001 // */ 3002 // JXG.createCurveConcat = function (board, parents, attributes) { 3003 // var c; 3004 3005 // if (parents.length !== 2) { 3006 // throw new Error( 3007 // "JSXGraph: Can't create curve difference with given parent'" + 3008 // "\nPossible parent types: [array, array|function]" 3009 // ); 3010 // } 3011 3012 // c = board.create("curve", [[], []], attributes); 3013 // /** 3014 // * @class 3015 // * @ignore 3016 // */ 3017 // c.updateCurve = function () { 3018 // this.points = parents[0].points.concat( 3019 // [new JXG.Coords(Const.COORDS_BY_USER, [NaN, NaN], this.board)] 3020 // ).concat(parents[1].points); 3021 // this.numberPoints = this.points.length; 3022 // return this; 3023 // }; 3024 3025 // return c; 3026 // }; 3027 3028 // JXG.registerElement("curveconcat", JXG.createCurveConcat); 3029 3030 /** 3031 * @class Vertical or horizontal box plot curve to present numerical data through their quartiles. 3032 * The direction of the box plot is controlled by the attribute "dir". 3033 * @pseudo 3034 * @name Boxplot 3035 * @param {Array} quantiles Array containing at least five quantiles. The elements can be of type number, function or string. 3036 * @param {Number|Function} axis Axis position of the box plot 3037 * @param {Number|Function} width Width of the rectangle part of the box plot. The width of the first and 4th quantile 3038 * is relative to this width and can be controlled by the attribute "smallWidth". 3039 * @augments JXG.Curve 3040 * @constructor 3041 * @type JXG.Curve 3042 * 3043 * @example 3044 * var Q = [ -1, 2, 3, 3.5, 5 ]; 3045 * 3046 * var b = board.create('boxplot', [Q, 2, 4], {strokeWidth: 3}); 3047 * 3048 * </pre><div id="JXG13eb23a1-a641-41a2-be11-8e03e400a947" class="jxgbox" style="width: 300px; height: 300px;"></div> 3049 * <script type="text/javascript"> 3050 * (function() { 3051 * var board = JXG.JSXGraph.initBoard('JXG13eb23a1-a641-41a2-be11-8e03e400a947', 3052 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 3053 * var Q = [ -1, 2, 3, 3.5, 5 ]; 3054 * var b = board.create('boxplot', [Q, 2, 4], {strokeWidth: 3}); 3055 * 3056 * })(); 3057 * 3058 * </script><pre> 3059 * 3060 * @example 3061 * var Q = [ -1, 2, 3, 3.5, 5 ]; 3062 * var b = board.create('boxplot', [Q, 3, 4], {dir: 'horizontal', smallWidth: 0.25, color:'red'}); 3063 * 3064 * </pre><div id="JXG0deb9cb2-84bc-470d-a6db-8be9a5694813" class="jxgbox" style="width: 300px; height: 300px;"></div> 3065 * <script type="text/javascript"> 3066 * (function() { 3067 * var board = JXG.JSXGraph.initBoard('JXG0deb9cb2-84bc-470d-a6db-8be9a5694813', 3068 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 3069 * var Q = [ -1, 2, 3, 3.5, 5 ]; 3070 * var b = board.create('boxplot', [Q, 3, 4], {dir: 'horizontal', smallWidth: 0.25, color:'red'}); 3071 * 3072 * })(); 3073 * 3074 * </script><pre> 3075 * 3076 * @example 3077 * var data = [57, 57, 57, 58, 63, 66, 66, 67, 67, 68, 69, 70, 70, 70, 70, 72, 73, 75, 75, 76, 76, 78, 79, 81]; 3078 * var Q = []; 3079 * 3080 * Q[0] = JXG.Math.Statistics.min(data); 3081 * Q = Q.concat(JXG.Math.Statistics.percentile(data, [25, 50, 75])); 3082 * Q[4] = JXG.Math.Statistics.max(data); 3083 * 3084 * var b = board.create('boxplot', [Q, 0, 3]); 3085 * 3086 * </pre><div id="JXGef079e76-ae99-41e4-af29-1d07d83bf85a" class="jxgbox" style="width: 300px; height: 300px;"></div> 3087 * <script type="text/javascript"> 3088 * (function() { 3089 * var board = JXG.JSXGraph.initBoard('JXGef079e76-ae99-41e4-af29-1d07d83bf85a', 3090 * {boundingbox: [-5,90,5,30], axis: true, showcopyright: false, shownavigation: false}); 3091 * var data = [57, 57, 57, 58, 63, 66, 66, 67, 67, 68, 69, 70, 70, 70, 70, 72, 73, 75, 75, 76, 76, 78, 79, 81]; 3092 * var Q = []; 3093 * 3094 * Q[0] = JXG.Math.Statistics.min(data); 3095 * Q = Q.concat(JXG.Math.Statistics.percentile(data, [25, 50, 75])); 3096 * Q[4] = JXG.Math.Statistics.max(data); 3097 * 3098 * var b = board.create('boxplot', [Q, 0, 3]); 3099 * 3100 * })(); 3101 * 3102 * </script><pre> 3103 * 3104 * @example 3105 * var mi = board.create('glider', [0, -1, board.defaultAxes.y]); 3106 * var ma = board.create('glider', [0, 5, board.defaultAxes.y]); 3107 * var Q = [function() { return mi.Y(); }, 2, 3, 3.5, function() { return ma.Y(); }]; 3108 * 3109 * var b = board.create('boxplot', [Q, 0, 2]); 3110 * 3111 * </pre><div id="JXG3b3225da-52f0-42fe-8396-be9016bf289b" class="jxgbox" style="width: 300px; height: 300px;"></div> 3112 * <script type="text/javascript"> 3113 * (function() { 3114 * var board = JXG.JSXGraph.initBoard('JXG3b3225da-52f0-42fe-8396-be9016bf289b', 3115 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 3116 * var mi = board.create('glider', [0, -1, board.defaultAxes.y]); 3117 * var ma = board.create('glider', [0, 5, board.defaultAxes.y]); 3118 * var Q = [function() { return mi.Y(); }, 2, 3, 3.5, function() { return ma.Y(); }]; 3119 * 3120 * var b = board.create('boxplot', [Q, 0, 2]); 3121 * 3122 * })(); 3123 * 3124 * </script><pre> 3125 * 3126 */ 3127 JXG.createBoxPlot = function (board, parents, attributes) { 3128 var box, i, len, 3129 attr = Type.copyAttributes(attributes, board.options, "boxplot"); 3130 3131 if (parents.length !== 3) { 3132 throw new Error( 3133 "JSXGraph: Can't create box plot with given parent'" + 3134 "\nPossible parent types: [array, number|function, number|function] containing quantiles, axis, width" 3135 ); 3136 } 3137 if (parents[0].length < 5) { 3138 throw new Error( 3139 "JSXGraph: Can't create box plot with given parent[0]'" + 3140 "\nparent[0] has to contain at least 5 quantiles." 3141 ); 3142 } 3143 box = board.create("curve", [[], []], attr); 3144 3145 len = parents[0].length; // Quantiles 3146 box.Q = []; 3147 for (i = 0; i < len; i++) { 3148 box.Q[i] = Type.createFunction(parents[0][i], board); 3149 } 3150 box.x = Type.createFunction(parents[1], board); 3151 box.w = Type.createFunction(parents[2], board); 3152 3153 /** 3154 * @class 3155 * @ignore 3156 */ 3157 box.updateDataArray = function () { 3158 var v1, v2, l1, l2, r1, r2, w2, dir, x; 3159 3160 w2 = this.evalVisProp('smallwidth'); 3161 dir = this.evalVisProp('dir'); 3162 x = this.x(); 3163 l1 = x - this.w() * 0.5; 3164 l2 = x - this.w() * 0.5 * w2; 3165 r1 = x + this.w() * 0.5; 3166 r2 = x + this.w() * 0.5 * w2; 3167 v1 = [x, l2, r2, x, x, l1, l1, r1, r1, x, NaN, l1, r1, NaN, x, x, l2, r2, x]; 3168 v2 = [ 3169 this.Q[0](), 3170 this.Q[0](), 3171 this.Q[0](), 3172 this.Q[0](), 3173 this.Q[1](), 3174 this.Q[1](), 3175 this.Q[3](), 3176 this.Q[3](), 3177 this.Q[1](), 3178 this.Q[1](), 3179 NaN, 3180 this.Q[2](), 3181 this.Q[2](), 3182 NaN, 3183 this.Q[3](), 3184 this.Q[4](), 3185 this.Q[4](), 3186 this.Q[4](), 3187 this.Q[4]() 3188 ]; 3189 if (dir === "vertical") { 3190 this.dataX = v1; 3191 this.dataY = v2; 3192 } else { 3193 this.dataX = v2; 3194 this.dataY = v1; 3195 } 3196 }; 3197 3198 box.addParentsFromJCFunctions([box.Q, box.x, box.w]); 3199 3200 return box; 3201 }; 3202 3203 JXG.registerElement("boxplot", JXG.createBoxPlot); 3204 3205 /** 3206 * @class An implicit curve is a plane curve defined by an implicit equation 3207 * relating two coordinate variables, commonly <i>x</i> and <i>y</i>. 3208 * For example, the unit circle is defined by the implicit equation 3209 * x<sup>2</sup> + y<sup>2</sup> = 1. 3210 * In general, every implicit curve is defined by an equation of the form 3211 * <i>f(x, y) = 0</i> 3212 * for some function <i>f</i> of two variables. (<a href="https://en.wikipedia.org/wiki/Implicit_curve">Wikipedia</a>) 3213 * <p> 3214 * The partial derivatives for <i>f</i> are optional. If not given, numerical 3215 * derivatives are used instead. This is good enough for most practical use cases. 3216 * But if supplied, both partial derivatives must be supplied. 3217 * <p> 3218 * The most effective attributes to tinker with if the implicit curve algorithm fails are 3219 * {@link ImplicitCurve#resolution_outer}, 3220 * {@link ImplicitCurve#resolution_inner}, 3221 * {@link ImplicitCurve#alpha_0}, 3222 * {@link ImplicitCurve#h_initial}, 3223 * {@link ImplicitCurve#h_max}, and 3224 * {@link ImplicitCurve#qdt_box}. 3225 * 3226 * @pseudo 3227 * @name ImplicitCurve 3228 * @param {Function|String} f Function of two variables for the left side of the equation <i>f(x,y)=0</i>. 3229 * If f is supplied as string, it has to use the variables 'x' and 'y'. 3230 * @param {Function|String} [dfx=null] Optional partial derivative in respect to the first variable 3231 * If dfx is supplied as string, it has to use the variables 'x' and 'y'. 3232 * @param {Function|String} [dfy=null] Optional partial derivative in respect to the second variable 3233 * If dfy is supplied as string, it has to use the variables 'x' and 'y'. 3234 * @param {Array|Function} [rangex=boundingbox] Optional array of length 2 3235 * of the form [x_min, x_max] setting the domain of the x coordinate of the implicit curve. 3236 * If not supplied, the board's boundingbox (+ the attribute "margin") is taken. 3237 * @param {Array|Function} [rangey=boundingbox] Optional array of length 2 3238 * of the form [y_min, y_max] setting the domain of the y coordinate of the implicit curve. 3239 * If not supplied, the board's boundingbox (+ the attribute "margin") is taken. 3240 * @augments JXG.Curve 3241 * @constructor 3242 * @type JXG.Curve 3243 * 3244 * @example 3245 * var f, c; 3246 * f = (x, y) => 1 / 16 * x ** 2 + y ** 2 - 1; 3247 * c = board.create('implicitcurve', [f], { 3248 * strokeWidth: 3, 3249 * strokeColor: JXG.palette.red, 3250 * strokeOpacity: 0.8 3251 * }); 3252 * 3253 * </pre><div id="JXGa6e86701-1a82-48d0-b007-3a3d32075076" class="jxgbox" style="width: 300px; height: 300px;"></div> 3254 * <script type="text/javascript"> 3255 * (function() { 3256 * var board = JXG.JSXGraph.initBoard('JXGa6e86701-1a82-48d0-b007-3a3d32075076', 3257 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 3258 * var f, c; 3259 * f = (x, y) => 1 / 16 * x ** 2 + y ** 2 - 1; 3260 * c = board.create('implicitcurve', [f], { 3261 * strokeWidth: 3, 3262 * strokeColor: JXG.palette.red, 3263 * strokeOpacity: 0.8 3264 * }); 3265 * 3266 * })(); 3267 * 3268 * </script><pre> 3269 * 3270 * @example 3271 * var a, c, f; 3272 * a = board.create('slider', [[-3, 6], [3, 6], [-3, 1, 3]], { 3273 * name: 'a', stepWidth: 0.1 3274 * }); 3275 * f = (x, y) => x ** 2 - 2 * x * y - 2 * x + (a.Value() + 1) * y ** 2 + (4 * a.Value() + 2) * y + 4 * a.Value() - 3; 3276 * c = board.create('implicitcurve', [f], { 3277 * strokeWidth: 3, 3278 * strokeColor: JXG.palette.red, 3279 * strokeOpacity: 0.8, 3280 * resolution_outer: 20, 3281 * resolution_inner: 20 3282 * }); 3283 * 3284 * </pre><div id="JXG0b133a54-9509-4a65-9722-9c5145e23b40" class="jxgbox" style="width: 300px; height: 300px;"></div> 3285 * <script type="text/javascript"> 3286 * (function() { 3287 * var board = JXG.JSXGraph.initBoard('JXG0b133a54-9509-4a65-9722-9c5145e23b40', 3288 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 3289 * var a, c, f; 3290 * a = board.create('slider', [[-3, 6], [3, 6], [-3, 1, 3]], { 3291 * name: 'a', stepWidth: 0.1 3292 * }); 3293 * f = (x, y) => x ** 2 - 2 * x * y - 2 * x + (a.Value() + 1) * y ** 2 + (4 * a.Value() + 2) * y + 4 * a.Value() - 3; 3294 * c = board.create('implicitcurve', [f], { 3295 * strokeWidth: 3, 3296 * strokeColor: JXG.palette.red, 3297 * strokeOpacity: 0.8, 3298 * resolution_outer: 20, 3299 * resolution_inner: 20 3300 * }); 3301 * 3302 * })(); 3303 * 3304 * </script><pre> 3305 * 3306 * @example 3307 * var c = board.create('implicitcurve', ['abs(x * y) - 3'], { 3308 * strokeWidth: 3, 3309 * strokeColor: JXG.palette.red, 3310 * strokeOpacity: 0.8 3311 * }); 3312 * 3313 * </pre><div id="JXG02802981-0abb-446b-86ea-ee588f02ed1a" class="jxgbox" style="width: 300px; height: 300px;"></div> 3314 * <script type="text/javascript"> 3315 * (function() { 3316 * var board = JXG.JSXGraph.initBoard('JXG02802981-0abb-446b-86ea-ee588f02ed1a', 3317 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 3318 * var c = board.create('implicitcurve', ['abs(x * y) - 3'], { 3319 * strokeWidth: 3, 3320 * strokeColor: JXG.palette.red, 3321 * strokeOpacity: 0.8 3322 * }); 3323 * 3324 * })(); 3325 * 3326 * </script><pre> 3327 * 3328 * @example 3329 * var niveauline = []; 3330 * niveauline = [0.5, 1, 1.5, 2]; 3331 * for (let i = 0; i < niveauline.length; i++) { 3332 * board.create("implicitcurve", [ 3333 * (x, y) => x ** .5 * y ** .5 - niveauline[i], 3334 [0.25, 3], [0.5, 4] // Domain 3335 * ], { 3336 * strokeWidth: 2, 3337 * strokeColor: JXG.palette.red, 3338 * strokeOpacity: (1 + i) / niveauline.length, 3339 * needsRegularUpdate: false 3340 * }); 3341 * } 3342 * 3343 * </pre><div id="JXGccee9aab-6dd9-4a79-827d-3164f70cc6a1" class="jxgbox" style="width: 300px; height: 300px;"></div> 3344 * <script type="text/javascript"> 3345 * (function() { 3346 * var board = JXG.JSXGraph.initBoard('JXGccee9aab-6dd9-4a79-827d-3164f70cc6a1', 3347 * {boundingbox: [-1, 5, 5,-1], axis: true, showcopyright: false, shownavigation: false}); 3348 * var niveauline = []; 3349 * niveauline = [0.5, 1, 1.5, 2]; 3350 * for (let i = 0; i < niveauline.length; i++) { 3351 * board.create("implicitcurve", [ 3352 * (x, y) => x ** .5 * y ** .5 - niveauline[i], 3353 * [0.25, 3], [0.5, 4] 3354 * ], { 3355 * strokeWidth: 2, 3356 * strokeColor: JXG.palette.red, 3357 * strokeOpacity: (1 + i) / niveauline.length, 3358 * needsRegularUpdate: false 3359 * }); 3360 * } 3361 * 3362 * })(); 3363 * 3364 * </script><pre> 3365 * 3366 */ 3367 JXG.createImplicitCurve = function (board, parents, attributes) { 3368 var c, attr; 3369 3370 if ([1, 3, 5].indexOf(parents.length) < 0) { 3371 throw new Error( 3372 "JSXGraph: Can't create curve implicitCurve with given parent'" + 3373 "\nPossible parent types: [f], [f, rangex, rangey], [f, dfx, dfy] or [f, dfx, dfy, rangex, rangey]" + 3374 "\nwith functions f, dfx, dfy and arrays of length 2 rangex, rangey." 3375 ); 3376 } 3377 3378 // if (parents.length === 3) { 3379 // if (!Type.isArray(parents[1]) && !Type.isArray(parents[2])) { 3380 // throw new Error( 3381 // "JSXGraph: Can't create curve implicitCurve with given parent'" + 3382 // "\nPossible parent types: [f], [f, rangex, rangey], [f, dfx, dfy] or [f, dfx, dfy, rangex, rangey]" + 3383 // "\nwith functions f, dfx, dfy and arrays of length 2 rangex, rangey." 3384 // ); 3385 // } 3386 // } 3387 // if (parents.length === 5) { 3388 // if (!Type.isArray(parents[3]) && !Type.isArray(parents[4])) { 3389 // throw new Error( 3390 // "JSXGraph: Can't create curve implicitCurve with given parent'" + 3391 // "\nPossible parent types: [f], [f, rangex, rangey], [f, dfx, dfy] or [f, dfx, dfy, rangex, rangey]" + 3392 // "\nwith functions f, dfx, dfy and arrays of length 2 rangex, rangey." 3393 // ); 3394 // } 3395 // } 3396 3397 attr = Type.copyAttributes(attributes, board.options, "implicitcurve"); 3398 c = board.create("curve", [[], []], attr); 3399 3400 /** 3401 * Function of two variables for the left side of the equation <i>f(x,y)=0</i>. 3402 * 3403 * @name f 3404 * @memberOf ImplicitCurve.prototype 3405 * @function 3406 * @returns {Number} 3407 */ 3408 c.f = Type.createFunction(parents[0], board, 'x, y'); 3409 3410 /** 3411 * Partial derivative in the first variable of 3412 * the left side of the equation <i>f(x,y)=0</i>. 3413 * If null, then numerical derivative is used. 3414 * 3415 * @name dfx 3416 * @memberOf ImplicitCurve.prototype 3417 * @function 3418 * @returns {Number} 3419 */ 3420 if (parents.length === 5 || Type.isString(parents[1]) || Type.isFunction(parents[1])) { 3421 c.dfx = Type.createFunction(parents[1], board, 'x, y'); 3422 } else { 3423 c.dfx = null; 3424 } 3425 3426 /** 3427 * Partial derivative in the second variable of 3428 * the left side of the equation <i>f(x,y)=0</i>. 3429 * If null, then numerical derivative is used. 3430 * 3431 * @name dfy 3432 * @memberOf ImplicitCurve.prototype 3433 * @function 3434 * @returns {Number} 3435 */ 3436 if (parents.length === 5 || Type.isString(parents[2]) || Type.isFunction(parents[2])) { 3437 c.dfy = Type.createFunction(parents[2], board, 'x, y'); 3438 } else { 3439 c.dfy = null; 3440 } 3441 3442 /** 3443 * Defines a domain for searching f(x,y)=0. Default is null, meaning 3444 * the bounding box of the board is used. 3445 * Using domain, visProp.margin is ignored. 3446 * @name domain 3447 * @memberOf ImplicitCurve.prototype 3448 * @param {Array} of length 4 defining the domain used to compute the implict curve. 3449 * Syntax: [x_min, y_max, x_max, y_min] 3450 */ 3451 // c.domain = board.getBoundingBox(); 3452 c.domain = null; 3453 if (parents.length === 5) { 3454 c.domain = [parents[3], parents[4]]; 3455 // [Math.min(parents[3][0], parents[3][1]), Math.max(parents[3][0], parents[3][1])], 3456 // [Math.min(parents[4][0], parents[4][1]), Math.max(parents[4][0], parents[4][1])] 3457 // ]; 3458 } else if (parents.length === 3) { 3459 c.domain = [parents[1], parents[2]]; 3460 // [Math.min(parents[1][0], parents[1][1]), Math.max(parents[1][0], parents[1][1])], 3461 // [Math.min(parents[2][0], parents[2][1]), Math.max(parents[2][0], parents[2][1])] 3462 // ]; 3463 } 3464 3465 /** 3466 * @class 3467 * @ignore 3468 */ 3469 c.updateDataArray = function () { 3470 var bbox, rx, ry, 3471 ip, cfg, 3472 ret = [], 3473 mgn; 3474 3475 if (this.domain === null) { 3476 mgn = this.evalVisProp('margin'); 3477 bbox = this.board.getBoundingBox(); 3478 bbox[0] -= mgn; 3479 bbox[1] += mgn; 3480 bbox[2] += mgn; 3481 bbox[3] -= mgn; 3482 } else { 3483 rx = Type.evaluate(this.domain[0]); 3484 ry = Type.evaluate(this.domain[1]); 3485 bbox = [ 3486 Math.min(rx[0], rx[1]), 3487 Math.max(ry[0], ry[1]), 3488 Math.max(rx[0], rx[1]), 3489 Math.min(ry[0], ry[1]) 3490 // rx[0], ry[1], rx[1], ry[0] 3491 ]; 3492 } 3493 3494 cfg = { 3495 resolution_out: Math.max(0.01, this.evalVisProp('resolution_outer')), 3496 resolution_in: Math.max(0.01, this.evalVisProp('resolution_inner')), 3497 max_steps: this.evalVisProp('max_steps'), 3498 alpha_0: this.evalVisProp('alpha_0'), 3499 tol_u0: this.evalVisProp('tol_u0'), 3500 tol_newton: this.evalVisProp('tol_newton'), 3501 tol_cusp: this.evalVisProp('tol_cusp'), 3502 tol_progress: this.evalVisProp('tol_progress'), 3503 qdt_box: this.evalVisProp('qdt_box'), 3504 kappa_0: this.evalVisProp('kappa_0'), 3505 delta_0: this.evalVisProp('delta_0'), 3506 h_initial: this.evalVisProp('h_initial'), 3507 h_critical: this.evalVisProp('h_critical'), 3508 h_max: this.evalVisProp('h_max'), 3509 loop_dist: this.evalVisProp('loop_dist'), 3510 loop_dir: this.evalVisProp('loop_dir'), 3511 loop_detection: this.evalVisProp('loop_detection'), 3512 unitX: this.board.unitX, 3513 unitY: this.board.unitY 3514 }; 3515 this.dataX = []; 3516 this.dataY = []; 3517 3518 // console.time("implicit plot"); 3519 ip = new ImplicitPlot(bbox, cfg, this.f, this.dfx, this.dfy); 3520 this.qdt = ip.qdt; 3521 3522 ret = ip.plot(); 3523 // console.timeEnd("implicit plot"); 3524 3525 this.dataX = ret[0]; 3526 this.dataY = ret[1]; 3527 }; 3528 3529 c.elType = 'implicitcurve'; 3530 3531 return c; 3532 }; 3533 3534 JXG.registerElement("implicitcurve", JXG.createImplicitCurve); 3535 3536 3537 export default JXG.Curve; 3538 3539 // export default { 3540 // Curve: JXG.Curve, 3541 // createCardinalSpline: JXG.createCardinalSpline, 3542 // createCurve: JXG.createCurve, 3543 // createCurveDifference: JXG.createCurveDifference, 3544 // createCurveIntersection: JXG.createCurveIntersection, 3545 // createCurveUnion: JXG.createCurveUnion, 3546 // createDerivative: JXG.createDerivative, 3547 // createFunctiongraph: JXG.createFunctiongraph, 3548 // createMetapostSpline: JXG.createMetapostSpline, 3549 // createPlot: JXG.createFunctiongraph, 3550 // createSpline: JXG.createSpline, 3551 // createRiemannsum: JXG.createRiemannsum, 3552 // createStepfunction: JXG.createStepfunction, 3553 // createTracecurve: JXG.createTracecurve 3554 // }; 3555 3556 // const Curve = JXG.Curve; 3557 // export { Curve as default, Curve}; 3558