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