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