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