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