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