1 /* 2 Copyright 2008-2023 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"; 40 import Const from "./constants"; 41 import Coords from "./coords"; 42 import GeometryElement from "./element"; 43 import Mat from "../math/math"; 44 import Numerics from "../math/numerics"; 45 import Plot from "../math/plot"; 46 import Geometry from "../math/geometry"; 47 import GeonextParser from "../parser/geonext"; 48 import Type from "../utils/type"; 49 import QDT from "../math/qdt"; 50 51 /** 52 * Curves are the common object for function graphs, parametric curves, polar curves, and data plots. 53 * @class Creates a new curve object. Do not use this constructor to create a curve. Use {@link JXG.Board#create} with 54 * type {@link Curve}, or {@link Functiongraph} instead. 55 * @augments JXG.GeometryElement 56 * @param {String|JXG.Board} board The board the new curve is drawn on. 57 * @param {Array} parents defining terms An array with the functon terms or the data points of the curve. 58 * @param {Object} attributes Defines the visual appearance of the curve. 59 * @see JXG.Board#generateName 60 * @see JXG.Board#addCurve 61 */ 62 JXG.Curve = function (board, parents, attributes) { 63 this.constructor(board, attributes, Const.OBJECT_TYPE_CURVE, Const.OBJECT_CLASS_CURVE); 64 65 this.points = []; 66 /** 67 * Number of points on curves. This value changes 68 * between numberPointsLow and numberPointsHigh. 69 * It is set in {@link JXG.Curve#updateCurve}. 70 */ 71 this.numberPoints = Type.evaluate(this.visProp.numberpointshigh); 72 73 this.bezierDegree = 1; 74 75 /** 76 * Array holding the x-coordinates of a data plot. 77 * This array can be updated during run time by overwriting 78 * the method {@link JXG.Curve#updateDataArray}. 79 * @type array 80 */ 81 this.dataX = null; 82 83 /** 84 * Array holding the y-coordinates of a data plot. 85 * This array can be updated during run time by overwriting 86 * the method {@link JXG.Curve#updateDataArray}. 87 * @type array 88 */ 89 this.dataY = null; 90 91 /** 92 * Array of ticks storing all the ticks on this curve. Do not set this field directly and use 93 * {@link JXG.Curve#addTicks} and {@link JXG.Curve#removeTicks} to add and remove ticks to and 94 * from the curve. 95 * @type Array 96 * @see JXG.Ticks 97 */ 98 this.ticks = []; 99 100 /** 101 * Stores a quad tree if it is required. The quad tree is generated in the curve 102 * updates and can be used to speed up the hasPoint method. 103 * @type JXG.Math.Quadtree 104 */ 105 this.qdt = null; 106 107 if (Type.exists(parents[0])) { 108 this.varname = parents[0]; 109 } else { 110 this.varname = "x"; 111 } 112 113 // function graphs: "x" 114 this.xterm = parents[1]; 115 // function graphs: e.g. "x^2" 116 this.yterm = parents[2]; 117 118 // Converts GEONExT syntax into JavaScript syntax 119 this.generateTerm(this.varname, this.xterm, this.yterm, parents[3], parents[4]); 120 // First evaluation of the curve 121 this.updateCurve(); 122 123 this.id = this.board.setId(this, "G"); 124 this.board.renderer.drawCurve(this); 125 126 this.board.finalizeAdding(this); 127 128 this.createGradient(); 129 this.elType = "curve"; 130 this.createLabel(); 131 132 if (Type.isString(this.xterm)) { 133 this.notifyParents(this.xterm); 134 } 135 if (Type.isString(this.yterm)) { 136 this.notifyParents(this.yterm); 137 } 138 139 this.methodMap = Type.deepCopy(this.methodMap, { 140 generateTerm: "generateTerm", 141 setTerm: "generateTerm", 142 move: "moveTo", 143 moveTo: "moveTo" 144 }); 145 }; 146 147 JXG.Curve.prototype = new GeometryElement(); 148 149 JXG.extend( 150 JXG.Curve.prototype, 151 /** @lends JXG.Curve.prototype */ { 152 /** 153 * Gives the default value of the left bound for the curve. 154 * May be overwritten in {@link JXG.Curve#generateTerm}. 155 * @returns {Number} Left bound for the curve. 156 */ 157 minX: function () { 158 var leftCoords; 159 160 if (Type.evaluate(this.visProp.curvetype) === "polar") { 161 return 0; 162 } 163 164 leftCoords = new Coords( 165 Const.COORDS_BY_SCREEN, 166 [-this.board.canvasWidth * 0.1, 0], 167 this.board, 168 false 169 ); 170 return leftCoords.usrCoords[1]; 171 }, 172 173 /** 174 * Gives the default value of the right bound for the curve. 175 * May be overwritten in {@link JXG.Curve#generateTerm}. 176 * @returns {Number} Right bound for the curve. 177 */ 178 maxX: function () { 179 var rightCoords; 180 181 if (Type.evaluate(this.visProp.curvetype) === "polar") { 182 return 2 * Math.PI; 183 } 184 rightCoords = new Coords( 185 Const.COORDS_BY_SCREEN, 186 [this.board.canvasWidth * 1.1, 0], 187 this.board, 188 false 189 ); 190 191 return rightCoords.usrCoords[1]; 192 }, 193 194 /** 195 * The parametric function which defines the x-coordinate of the curve. 196 * @param {Number} t A number between {@link JXG.Curve#minX} and {@link JXG.Curve#maxX}. 197 * @param {Boolean} suspendUpdate A boolean flag which is false for the 198 * first call of the function during a fresh plot of the curve and true 199 * for all subsequent calls of the function. This may be used to speed up the 200 * plotting of the curve, if the e.g. the curve depends on some input elements. 201 * @returns {Number} x-coordinate of the curve at t. 202 */ 203 X: function (t) { 204 return NaN; 205 }, 206 207 /** 208 * The parametric function which defines the y-coordinate of the curve. 209 * @param {Number} t A number between {@link JXG.Curve#minX} and {@link JXG.Curve#maxX}. 210 * @param {Boolean} suspendUpdate A boolean flag which is false for the 211 * first call of the function during a fresh plot of the curve and true 212 * for all subsequent calls of the function. This may be used to speed up the 213 * plotting of the curve, if the e.g. the curve depends on some input elements. 214 * @returns {Number} y-coordinate of the curve at t. 215 */ 216 Y: function (t) { 217 return NaN; 218 }, 219 220 /** 221 * Treat the curve as curve with homogeneous coordinates. 222 * @param {Number} t A number between {@link JXG.Curve#minX} and {@link JXG.Curve#maxX}. 223 * @returns {Number} Always 1.0 224 */ 225 Z: function (t) { 226 return 1; 227 }, 228 229 /** 230 * Checks whether (x,y) is near the curve. 231 * @param {Number} x Coordinate in x direction, screen coordinates. 232 * @param {Number} y Coordinate in y direction, screen coordinates. 233 * @param {Number} start Optional start index for search on data plots. 234 * @returns {Boolean} True if (x,y) is near the curve, False otherwise. 235 */ 236 hasPoint: function (x, y, start) { 237 var t, 238 checkPoint, 239 len, 240 invMat, 241 c, 242 i, 243 tX, 244 tY, 245 isIn, 246 res = [], 247 points, 248 qdt, 249 steps = Type.evaluate(this.visProp.numberpointslow), 250 d = (this.maxX() - this.minX()) / steps, 251 prec, 252 type, 253 dist = Infinity, 254 ux2, 255 uy2, 256 ev_ct, 257 mi, 258 ma, 259 suspendUpdate = true; 260 261 if (Type.isObject(Type.evaluate(this.visProp.precision))) { 262 type = this.board._inputDevice; 263 prec = Type.evaluate(this.visProp.precision[type]); 264 } else { 265 // 'inherit' 266 prec = this.board.options.precision.hasPoint; 267 } 268 269 // From now on, x,y are usrCoords 270 checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board, false); 271 x = checkPoint.usrCoords[1]; 272 y = checkPoint.usrCoords[2]; 273 274 // Handle inner points of the curve 275 if (this.bezierDegree === 1 && Type.evaluate(this.visProp.hasinnerpoints)) { 276 isIn = Geometry.windingNumber([1, x, y], this.points, true); 277 if (isIn !== 0) { 278 return true; 279 } 280 } 281 282 // We use usrCoords. Only in the final distance calculation 283 // screen coords are used 284 prec += Type.evaluate(this.visProp.strokewidth) * 0.5; 285 prec *= prec; // We do not want to take sqrt 286 ux2 = this.board.unitX * this.board.unitX; 287 uy2 = this.board.unitY * this.board.unitY; 288 289 mi = this.minX(); 290 ma = this.maxX(); 291 if (Type.exists(this._visibleArea)) { 292 mi = this._visibleArea[0]; 293 ma = this._visibleArea[1]; 294 d = (ma - mi) / steps; 295 } 296 297 ev_ct = Type.evaluate(this.visProp.curvetype); 298 if (ev_ct === "parameter" || ev_ct === "polar") { 299 // Transform the mouse/touch coordinates 300 // back to the original position of the curve. 301 // This is needed, because we work with the function terms, not the points. 302 if (this.transformations.length > 0) { 303 this.updateTransformMatrix(); 304 invMat = Mat.inverse(this.transformMat); 305 c = Mat.matVecMult(invMat, [1, x, y]); 306 x = c[1]; 307 y = c[2]; 308 } 309 310 // Brute force search for a point on the curve close to the mouse pointer 311 for (i = 0, t = mi; i < steps; i++) { 312 tX = this.X(t, suspendUpdate); 313 tY = this.Y(t, suspendUpdate); 314 315 dist = (x - tX) * (x - tX) * ux2 + (y - tY) * (y - tY) * uy2; 316 317 if (dist <= prec) { 318 return true; 319 } 320 321 t += d; 322 } 323 } else if (ev_ct === "plot" || ev_ct === "functiongraph") { 324 // Here, we can ignore transformations of the curve, 325 // since we are working directly with the points. 326 327 if (!Type.exists(start) || start < 0) { 328 start = 0; 329 } 330 331 if ( 332 Type.exists(this.qdt) && 333 Type.evaluate(this.visProp.useqdt) && 334 this.bezierDegree !== 3 335 ) { 336 qdt = this.qdt.query(new Coords(Const.COORDS_BY_USER, [x, y], this.board)); 337 points = qdt.points; 338 len = points.length; 339 } else { 340 points = this.points; 341 len = this.numberPoints - 1; 342 } 343 344 for (i = start; i < len; i++) { 345 if (this.bezierDegree === 3) { 346 res.push(Geometry.projectCoordsToBeziersegment([1, x, y], this, i)); 347 } else { 348 if (qdt) { 349 if (points[i].prev) { 350 res = Geometry.projectCoordsToSegment( 351 [1, x, y], 352 points[i].prev.usrCoords, 353 points[i].usrCoords 354 ); 355 } 356 357 // If the next point in the array is the same as the current points 358 // next neighbor we don't have to project it onto that segment because 359 // that will already be done in the next iteration of this loop. 360 if (points[i].next && points[i + 1] !== points[i].next) { 361 res = Geometry.projectCoordsToSegment( 362 [1, x, y], 363 points[i].usrCoords, 364 points[i].next.usrCoords 365 ); 366 } 367 } else { 368 res = Geometry.projectCoordsToSegment( 369 [1, x, y], 370 points[i].usrCoords, 371 points[i + 1].usrCoords 372 ); 373 } 374 } 375 376 if ( 377 res[1] >= 0 && 378 res[1] <= 1 && 379 (x - res[0][1]) * (x - res[0][1]) * ux2 + 380 (y - res[0][2]) * (y - res[0][2]) * uy2 <= 381 prec 382 ) { 383 return true; 384 } 385 } 386 return false; 387 } 388 return dist < prec; 389 }, 390 391 /** 392 * Allocate points in the Coords array this.points 393 */ 394 allocatePoints: function () { 395 var i, len; 396 397 len = this.numberPoints; 398 399 if (this.points.length < this.numberPoints) { 400 for (i = this.points.length; i < len; i++) { 401 this.points[i] = new Coords( 402 Const.COORDS_BY_USER, 403 [0, 0], 404 this.board, 405 false 406 ); 407 } 408 } 409 }, 410 411 /** 412 * Computes for equidistant points on the x-axis the values of the function 413 * @returns {JXG.Curve} Reference to the curve object. 414 * @see JXG.Curve#updateCurve 415 */ 416 update: function () { 417 if (this.needsUpdate) { 418 if (Type.evaluate(this.visProp.trace)) { 419 this.cloneToBackground(true); 420 } 421 this.updateCurve(); 422 } 423 424 return this; 425 }, 426 427 /** 428 * Updates the visual contents of the curve. 429 * @returns {JXG.Curve} Reference to the curve object. 430 */ 431 updateRenderer: function () { 432 //var wasReal; 433 434 if (!this.needsUpdate) { 435 return this; 436 } 437 438 if (this.visPropCalc.visible) { 439 // wasReal = this.isReal; 440 441 this.isReal = Plot.checkReal(this.points); 442 443 if ( 444 //wasReal && 445 !this.isReal 446 ) { 447 this.updateVisibility(false); 448 } 449 } 450 451 if (this.visPropCalc.visible) { 452 this.board.renderer.updateCurve(this); 453 } 454 455 /* Update the label if visible. */ 456 if ( 457 this.hasLabel && 458 this.visPropCalc.visible && 459 this.label && 460 this.label.visPropCalc.visible && 461 this.isReal 462 ) { 463 this.label.update(); 464 this.board.renderer.updateText(this.label); 465 } 466 467 // Update rendNode display 468 this.setDisplayRendNode(); 469 // if (this.visPropCalc.visible !== this.visPropOld.visible) { 470 // this.board.renderer.display(this, this.visPropCalc.visible); 471 // this.visPropOld.visible = this.visPropCalc.visible; 472 // 473 // if (this.hasLabel) { 474 // this.board.renderer.display(this.label, this.label.visPropCalc.visible); 475 // } 476 // } 477 478 this.needsUpdate = false; 479 return this; 480 }, 481 482 /** 483 * For dynamic dataplots updateCurve can be used to compute new entries 484 * for the arrays {@link JXG.Curve#dataX} and {@link JXG.Curve#dataY}. It 485 * is used in {@link JXG.Curve#updateCurve}. Default is an empty method, can 486 * be overwritten by the user. 487 * 488 * 489 * @example 490 * // This example overwrites the updateDataArray method. 491 * // There, new values for the arrays JXG.Curve.dataX and JXG.Curve.dataY 492 * // are computed from the value of the slider N 493 * 494 * var N = board.create('slider', [[0,1.5],[3,1.5],[1,3,40]], {name:'n',snapWidth:1}); 495 * var circ = board.create('circle',[[4,-1.5],1],{strokeWidth:1, strokecolor:'black', strokeWidth:2, 496 * fillColor:'#0055ff13'}); 497 * 498 * var c = board.create('curve', [[0],[0]],{strokecolor:'red', strokeWidth:2}); 499 * c.updateDataArray = function() { 500 * var r = 1, n = Math.floor(N.Value()), 501 * x = [0], y = [0], 502 * phi = Math.PI/n, 503 * h = r*Math.cos(phi), 504 * s = r*Math.sin(phi), 505 * i, j, 506 * px = 0, py = 0, sgn = 1, 507 * d = 16, 508 * dt = phi/d, 509 * pt; 510 * 511 * for (i = 0; i < n; i++) { 512 * for (j = -d; j <= d; j++) { 513 * pt = dt*j; 514 * x.push(px + r*Math.sin(pt)); 515 * y.push(sgn*r*Math.cos(pt) - (sgn-1)*h*0.5); 516 * } 517 * px += s; 518 * sgn *= (-1); 519 * } 520 * x.push((n - 1)*s); 521 * y.push(h + (sgn - 1)*h*0.5); 522 * this.dataX = x; 523 * this.dataY = y; 524 * } 525 * 526 * var c2 = board.create('curve', [[0],[0]],{strokecolor:'red', strokeWidth:1}); 527 * c2.updateDataArray = function() { 528 * var r = 1, n = Math.floor(N.Value()), 529 * px = circ.midpoint.X(), py = circ.midpoint.Y(), 530 * x = [px], y = [py], 531 * phi = Math.PI/n, 532 * s = r*Math.sin(phi), 533 * i, j, 534 * d = 16, 535 * dt = phi/d, 536 * pt = Math.PI*0.5+phi; 537 * 538 * for (i = 0; i < n; i++) { 539 * for (j= -d; j <= d; j++) { 540 * x.push(px + r*Math.cos(pt)); 541 * y.push(py + r*Math.sin(pt)); 542 * pt -= dt; 543 * } 544 * x.push(px); 545 * y.push(py); 546 * pt += dt; 547 * } 548 * this.dataX = x; 549 * this.dataY = y; 550 * } 551 * board.update(); 552 * 553 * </pre><div id="JXG20bc7802-e69e-11e5-b1bf-901b0e1b8723" class="jxgbox" style="width: 600px; height: 400px;"></div> 554 * <script type="text/javascript"> 555 * (function() { 556 * var board = JXG.JSXGraph.initBoard('JXG20bc7802-e69e-11e5-b1bf-901b0e1b8723', 557 * {boundingbox: [-1.5,2,8,-3], keepaspectratio: true, axis: true, showcopyright: false, shownavigation: false}); 558 * var N = board.create('slider', [[0,1.5],[3,1.5],[1,3,40]], {name:'n',snapWidth:1}); 559 * var circ = board.create('circle',[[4,-1.5],1],{strokeWidth:1, strokecolor:'black', 560 * strokeWidth:2, fillColor:'#0055ff13'}); 561 * 562 * var c = board.create('curve', [[0],[0]],{strokecolor:'red', strokeWidth:2}); 563 * c.updateDataArray = function() { 564 * var r = 1, n = Math.floor(N.Value()), 565 * x = [0], y = [0], 566 * phi = Math.PI/n, 567 * h = r*Math.cos(phi), 568 * s = r*Math.sin(phi), 569 * i, j, 570 * px = 0, py = 0, sgn = 1, 571 * d = 16, 572 * dt = phi/d, 573 * pt; 574 * 575 * for (i=0;i<n;i++) { 576 * for (j=-d;j<=d;j++) { 577 * pt = dt*j; 578 * x.push(px+r*Math.sin(pt)); 579 * y.push(sgn*r*Math.cos(pt)-(sgn-1)*h*0.5); 580 * } 581 * px += s; 582 * sgn *= (-1); 583 * } 584 * x.push((n-1)*s); 585 * y.push(h+(sgn-1)*h*0.5); 586 * this.dataX = x; 587 * this.dataY = y; 588 * } 589 * 590 * var c2 = board.create('curve', [[0],[0]],{strokecolor:'red', strokeWidth:1}); 591 * c2.updateDataArray = function() { 592 * var r = 1, n = Math.floor(N.Value()), 593 * px = circ.midpoint.X(), py = circ.midpoint.Y(), 594 * x = [px], y = [py], 595 * phi = Math.PI/n, 596 * s = r*Math.sin(phi), 597 * i, j, 598 * d = 16, 599 * dt = phi/d, 600 * pt = Math.PI*0.5+phi; 601 * 602 * for (i=0;i<n;i++) { 603 * for (j=-d;j<=d;j++) { 604 * x.push(px+r*Math.cos(pt)); 605 * y.push(py+r*Math.sin(pt)); 606 * pt -= dt; 607 * } 608 * x.push(px); 609 * y.push(py); 610 * pt += dt; 611 * } 612 * this.dataX = x; 613 * this.dataY = y; 614 * } 615 * board.update(); 616 * 617 * })(); 618 * 619 * </script><pre> 620 * 621 * @example 622 * // This is an example which overwrites updateDataArray and produces 623 * // a Bezier curve of degree three. 624 * var A = board.create('point', [-3,3]); 625 * var B = board.create('point', [3,-2]); 626 * var line = board.create('segment', [A,B]); 627 * 628 * var height = 0.5; // height of the curly brace 629 * 630 * // Curly brace 631 * var crl = board.create('curve', [[0],[0]], {strokeWidth:1, strokeColor:'black'}); 632 * crl.bezierDegree = 3; 633 * crl.updateDataArray = function() { 634 * var d = [B.X()-A.X(), B.Y()-A.Y()], 635 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 636 * mid = [(A.X()+B.X())*0.5, (A.Y()+B.Y())*0.5]; 637 * 638 * d[0] *= height/dl; 639 * d[1] *= height/dl; 640 * 641 * this.dataX = [ A.X(), A.X()-d[1], mid[0], mid[0]-d[1], mid[0], B.X()-d[1], B.X() ]; 642 * this.dataY = [ A.Y(), A.Y()+d[0], mid[1], mid[1]+d[0], mid[1], B.Y()+d[0], B.Y() ]; 643 * }; 644 * 645 * // Text 646 * var txt = board.create('text', [ 647 * function() { 648 * var d = [B.X()-A.X(), B.Y()-A.Y()], 649 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 650 * mid = (A.X()+B.X())*0.5; 651 * 652 * d[1] *= height/dl; 653 * return mid-d[1]+0.1; 654 * }, 655 * function() { 656 * var d = [B.X()-A.X(), B.Y()-A.Y()], 657 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 658 * mid = (A.Y()+B.Y())*0.5; 659 * 660 * d[0] *= height/dl; 661 * return mid+d[0]+0.1; 662 * }, 663 * function() { return "length=" + JXG.toFixed(B.Dist(A), 2); } 664 * ]); 665 * 666 * 667 * board.update(); // This update is necessary to call updateDataArray the first time. 668 * 669 * </pre><div id="JXGa61a4d66-e69f-11e5-b1bf-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 670 * <script type="text/javascript"> 671 * (function() { 672 * var board = JXG.JSXGraph.initBoard('JXGa61a4d66-e69f-11e5-b1bf-901b0e1b8723', 673 * {boundingbox: [-4, 4, 4,-4], axis: true, showcopyright: false, shownavigation: false}); 674 * var A = board.create('point', [-3,3]); 675 * var B = board.create('point', [3,-2]); 676 * var line = board.create('segment', [A,B]); 677 * 678 * var height = 0.5; // height of the curly brace 679 * 680 * // Curly brace 681 * var crl = board.create('curve', [[0],[0]], {strokeWidth:1, strokeColor:'black'}); 682 * crl.bezierDegree = 3; 683 * crl.updateDataArray = function() { 684 * var d = [B.X()-A.X(), B.Y()-A.Y()], 685 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 686 * mid = [(A.X()+B.X())*0.5, (A.Y()+B.Y())*0.5]; 687 * 688 * d[0] *= height/dl; 689 * d[1] *= height/dl; 690 * 691 * this.dataX = [ A.X(), A.X()-d[1], mid[0], mid[0]-d[1], mid[0], B.X()-d[1], B.X() ]; 692 * this.dataY = [ A.Y(), A.Y()+d[0], mid[1], mid[1]+d[0], mid[1], B.Y()+d[0], B.Y() ]; 693 * }; 694 * 695 * // Text 696 * var txt = board.create('text', [ 697 * function() { 698 * var d = [B.X()-A.X(), B.Y()-A.Y()], 699 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 700 * mid = (A.X()+B.X())*0.5; 701 * 702 * d[1] *= height/dl; 703 * return mid-d[1]+0.1; 704 * }, 705 * function() { 706 * var d = [B.X()-A.X(), B.Y()-A.Y()], 707 * dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]), 708 * mid = (A.Y()+B.Y())*0.5; 709 * 710 * d[0] *= height/dl; 711 * return mid+d[0]+0.1; 712 * }, 713 * function() { return "length="+JXG.toFixed(B.Dist(A), 2); } 714 * ]); 715 * 716 * 717 * board.update(); // This update is necessary to call updateDataArray the first time. 718 * 719 * })(); 720 * 721 * </script><pre> 722 * 723 * 724 */ 725 updateDataArray: function () { 726 // this used to return this, but we shouldn't rely on the user to implement it. 727 }, 728 729 /** 730 * Computes the curve path 731 * @see JXG.Curve#update 732 * @returns {JXG.Curve} Reference to the curve object. 733 */ 734 updateCurve: function () { 735 var len, 736 mi, 737 ma, 738 x, 739 y, 740 i, 741 version = this.visProp.plotversion, 742 //t1, t2, l1, 743 suspendUpdate = false; 744 745 this.updateTransformMatrix(); 746 this.updateDataArray(); 747 mi = this.minX(); 748 ma = this.maxX(); 749 750 // Discrete data points 751 // x-coordinates are in an array 752 if (Type.exists(this.dataX)) { 753 this.numberPoints = this.dataX.length; 754 len = this.numberPoints; 755 756 // It is possible, that the array length has increased. 757 this.allocatePoints(); 758 759 for (i = 0; i < len; i++) { 760 x = i; 761 762 // y-coordinates are in an array 763 if (Type.exists(this.dataY)) { 764 y = i; 765 // The last parameter prevents rounding in usr2screen(). 766 this.points[i].setCoordinates( 767 Const.COORDS_BY_USER, 768 [this.dataX[i], this.dataY[i]], 769 false 770 ); 771 } else { 772 // discrete x data, continuous y data 773 y = this.X(x); 774 // The last parameter prevents rounding in usr2screen(). 775 this.points[i].setCoordinates( 776 Const.COORDS_BY_USER, 777 [this.dataX[i], this.Y(y, suspendUpdate)], 778 false 779 ); 780 } 781 this.points[i]._t = i; 782 783 // this.updateTransform(this.points[i]); 784 suspendUpdate = true; 785 } 786 // continuous x data 787 } else { 788 if (Type.evaluate(this.visProp.doadvancedplot)) { 789 // console.time("plot"); 790 791 if (version === 1 || Type.evaluate(this.visProp.doadvancedplotold)) { 792 Plot.updateParametricCurveOld(this, mi, ma); 793 } else if (version === 2) { 794 Plot.updateParametricCurve_v2(this, mi, ma); 795 } else if (version === 3) { 796 Plot.updateParametricCurve_v3(this, mi, ma); 797 } else if (version === 4) { 798 Plot.updateParametricCurve_v4(this, mi, ma); 799 } else { 800 Plot.updateParametricCurve_v2(this, mi, ma); 801 } 802 // console.timeEnd("plot"); 803 } else { 804 if (this.board.updateQuality === this.board.BOARD_QUALITY_HIGH) { 805 this.numberPoints = Type.evaluate(this.visProp.numberpointshigh); 806 } else { 807 this.numberPoints = Type.evaluate(this.visProp.numberpointslow); 808 } 809 810 // It is possible, that the array length has increased. 811 this.allocatePoints(); 812 Plot.updateParametricCurveNaive(this, mi, ma, this.numberPoints); 813 } 814 len = this.numberPoints; 815 816 if ( 817 Type.evaluate(this.visProp.useqdt) && 818 this.board.updateQuality === this.board.BOARD_QUALITY_HIGH 819 ) { 820 this.qdt = new QDT(this.board.getBoundingBox()); 821 for (i = 0; i < this.points.length; i++) { 822 this.qdt.insert(this.points[i]); 823 824 if (i > 0) { 825 this.points[i].prev = this.points[i - 1]; 826 } 827 828 if (i < len - 1) { 829 this.points[i].next = this.points[i + 1]; 830 } 831 } 832 } 833 834 // for (i = 0; i < len; i++) { 835 // this.updateTransform(this.points[i]); 836 // } 837 } 838 839 if ( 840 Type.evaluate(this.visProp.curvetype) !== "plot" && 841 Type.evaluate(this.visProp.rdpsmoothing) 842 ) { 843 // console.time("rdp"); 844 this.points = Numerics.RamerDouglasPeucker(this.points, 0.2); 845 this.numberPoints = this.points.length; 846 // console.timeEnd("rdp"); 847 // console.log(this.numberPoints); 848 } 849 850 len = this.numberPoints; 851 for (i = 0; i < len; i++) { 852 this.updateTransform(this.points[i]); 853 } 854 855 return this; 856 }, 857 858 updateTransformMatrix: function () { 859 var t, 860 i, 861 len = this.transformations.length; 862 863 this.transformMat = [ 864 [1, 0, 0], 865 [0, 1, 0], 866 [0, 0, 1] 867 ]; 868 869 for (i = 0; i < len; i++) { 870 t = this.transformations[i]; 871 t.update(); 872 this.transformMat = Mat.matMatMult(t.matrix, this.transformMat); 873 } 874 875 return this; 876 }, 877 878 /** 879 * Applies the transformations of the curve to the given point <tt>p</tt>. 880 * Before using it, {@link JXG.Curve#updateTransformMatrix} has to be called. 881 * @param {JXG.Point} p 882 * @returns {JXG.Point} The given point. 883 */ 884 updateTransform: function (p) { 885 var c, 886 len = this.transformations.length; 887 888 if (len > 0) { 889 c = Mat.matVecMult(this.transformMat, p.usrCoords); 890 p.setCoordinates(Const.COORDS_BY_USER, c, false, true); 891 } 892 893 return p; 894 }, 895 896 /** 897 * Add transformations to this curve. 898 * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of {@link JXG.Transformation}s. 899 * @returns {JXG.Curve} Reference to the curve object. 900 */ 901 addTransform: function (transform) { 902 var i, 903 list = Type.isArray(transform) ? transform : [transform], 904 len = list.length; 905 906 for (i = 0; i < len; i++) { 907 this.transformations.push(list[i]); 908 } 909 910 return this; 911 }, 912 913 /** 914 * Generate the method curve.X() in case curve.dataX is an array 915 * and generate the method curve.Y() in case curve.dataY is an array. 916 * @private 917 * @param {String} which Either 'X' or 'Y' 918 * @returns {function} 919 **/ 920 interpolationFunctionFromArray: function (which) { 921 var data = "data" + which, 922 that = this; 923 924 return function (t, suspendedUpdate) { 925 var i, 926 j, 927 t0, 928 t1, 929 arr = that[data], 930 len = arr.length, 931 last, 932 f = []; 933 934 if (isNaN(t)) { 935 return NaN; 936 } 937 938 if (t < 0) { 939 if (Type.isFunction(arr[0])) { 940 return arr[0](); 941 } 942 943 return arr[0]; 944 } 945 946 if (that.bezierDegree === 3) { 947 last = (len - 1) / 3; 948 949 if (t >= last) { 950 if (Type.isFunction(arr[arr.length - 1])) { 951 return arr[arr.length - 1](); 952 } 953 954 return arr[arr.length - 1]; 955 } 956 957 i = Math.floor(t) * 3; 958 t0 = t % 1; 959 t1 = 1 - t0; 960 961 for (j = 0; j < 4; j++) { 962 if (Type.isFunction(arr[i + j])) { 963 f[j] = arr[i + j](); 964 } else { 965 f[j] = arr[i + j]; 966 } 967 } 968 969 return ( 970 t1 * t1 * (t1 * f[0] + 3 * t0 * f[1]) + 971 (3 * t1 * f[2] + t0 * f[3]) * t0 * t0 972 ); 973 } 974 975 if (t > len - 2) { 976 i = len - 2; 977 } else { 978 i = parseInt(Math.floor(t), 10); 979 } 980 981 if (i === t) { 982 if (Type.isFunction(arr[i])) { 983 return arr[i](); 984 } 985 return arr[i]; 986 } 987 988 for (j = 0; j < 2; j++) { 989 if (Type.isFunction(arr[i + j])) { 990 f[j] = arr[i + j](); 991 } else { 992 f[j] = arr[i + j]; 993 } 994 } 995 return f[0] + (f[1] - f[0]) * (t - i); 996 }; 997 }, 998 999 /** 1000 * Converts the JavaScript/JessieCode/GEONExT syntax of the defining function term into JavaScript. 1001 * New methods X() and Y() for the Curve object are generated, further 1002 * new methods for minX() and maxX(). 1003 * @see JXG.GeonextParser.geonext2JS. 1004 */ 1005 generateTerm: function (varname, xterm, yterm, mi, ma) { 1006 var fx, fy; 1007 1008 // Generate the methods X() and Y() 1009 if (Type.isArray(xterm)) { 1010 // Discrete data 1011 this.dataX = xterm; 1012 1013 this.numberPoints = this.dataX.length; 1014 this.X = this.interpolationFunctionFromArray.apply(this, ["X"]); 1015 this.visProp.curvetype = "plot"; 1016 this.isDraggable = true; 1017 } else { 1018 // Continuous data 1019 this.X = Type.createFunction(xterm, this.board, varname); 1020 if (Type.isString(xterm)) { 1021 this.visProp.curvetype = "functiongraph"; 1022 } else if (Type.isFunction(xterm) || Type.isNumber(xterm)) { 1023 this.visProp.curvetype = "parameter"; 1024 } 1025 1026 this.isDraggable = true; 1027 } 1028 1029 if (Type.isArray(yterm)) { 1030 this.dataY = yterm; 1031 this.Y = this.interpolationFunctionFromArray.apply(this, ["Y"]); 1032 } else { 1033 this.Y = Type.createFunction(yterm, this.board, varname); 1034 } 1035 1036 /** 1037 * Polar form 1038 * Input data is function xterm() and offset coordinates yterm 1039 */ 1040 if (Type.isFunction(xterm) && Type.isArray(yterm)) { 1041 // Xoffset, Yoffset 1042 fx = Type.createFunction(yterm[0], this.board, ""); 1043 fy = Type.createFunction(yterm[1], this.board, ""); 1044 1045 this.X = function (phi) { 1046 return xterm(phi) * Math.cos(phi) + fx(); 1047 }; 1048 this.X.deps = fx.deps; 1049 1050 this.Y = function (phi) { 1051 return xterm(phi) * Math.sin(phi) + fy(); 1052 }; 1053 this.Y.deps = fy.deps; 1054 1055 this.visProp.curvetype = "polar"; 1056 } 1057 1058 // Set the bounds lower bound 1059 if (Type.exists(mi)) { 1060 this.minX = Type.createFunction(mi, this.board, ""); 1061 } 1062 if (Type.exists(ma)) { 1063 this.maxX = Type.createFunction(ma, this.board, ""); 1064 } 1065 1066 this.addParentsFromJCFunctions([this.X, this.Y, this.minX, this.maxX]); 1067 }, 1068 1069 /** 1070 * Finds dependencies in a given term and notifies the parents by adding the 1071 * dependent object to the found objects child elements. 1072 * @param {String} contentStr String containing dependencies for the given object. 1073 */ 1074 notifyParents: function (contentStr) { 1075 var fstr, 1076 dep, 1077 isJessieCode = false, 1078 obj; 1079 1080 // Read dependencies found by the JessieCode parser 1081 obj = { xterm: 1, yterm: 1 }; 1082 for (fstr in obj) { 1083 if ( 1084 obj.hasOwnProperty(fstr) && 1085 this.hasOwnProperty(fstr) && 1086 this[fstr].origin 1087 ) { 1088 isJessieCode = true; 1089 for (dep in this[fstr].origin.deps) { 1090 if (this[fstr].origin.deps.hasOwnProperty(dep)) { 1091 this[fstr].origin.deps[dep].addChild(this); 1092 } 1093 } 1094 } 1095 } 1096 1097 if (!isJessieCode) { 1098 GeonextParser.findDependencies(this, contentStr, this.board); 1099 } 1100 }, 1101 1102 // documented in geometry element 1103 getLabelAnchor: function () { 1104 var c, 1105 x, 1106 y, 1107 ax = 0.05 * this.board.canvasWidth, 1108 ay = 0.05 * this.board.canvasHeight, 1109 bx = 0.95 * this.board.canvasWidth, 1110 by = 0.95 * this.board.canvasHeight; 1111 1112 switch (Type.evaluate(this.visProp.label.position)) { 1113 case "ulft": 1114 x = ax; 1115 y = ay; 1116 break; 1117 case "llft": 1118 x = ax; 1119 y = by; 1120 break; 1121 case "rt": 1122 x = bx; 1123 y = 0.5 * by; 1124 break; 1125 case "lrt": 1126 x = bx; 1127 y = by; 1128 break; 1129 case "urt": 1130 x = bx; 1131 y = ay; 1132 break; 1133 case "top": 1134 x = 0.5 * bx; 1135 y = ay; 1136 break; 1137 case "bot": 1138 x = 0.5 * bx; 1139 y = by; 1140 break; 1141 default: 1142 // includes case 'lft' 1143 x = ax; 1144 y = 0.5 * by; 1145 } 1146 1147 c = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board, false); 1148 return Geometry.projectCoordsToCurve( 1149 c.usrCoords[1], 1150 c.usrCoords[2], 1151 0, 1152 this, 1153 this.board 1154 )[0]; 1155 }, 1156 1157 // documented in geometry element 1158 cloneToBackground: function () { 1159 var er, 1160 copy = { 1161 id: this.id + "T" + this.numTraces, 1162 elementClass: Const.OBJECT_CLASS_CURVE, 1163 1164 points: this.points.slice(0), 1165 bezierDegree: this.bezierDegree, 1166 numberPoints: this.numberPoints, 1167 board: this.board, 1168 visProp: Type.deepCopy(this.visProp, this.visProp.traceattributes, true) 1169 }; 1170 1171 copy.visProp.layer = this.board.options.layer.trace; 1172 copy.visProp.curvetype = this.visProp.curvetype; 1173 this.numTraces++; 1174 1175 Type.clearVisPropOld(copy); 1176 copy.visPropCalc = { 1177 visible: Type.evaluate(copy.visProp.visible) 1178 }; 1179 er = this.board.renderer.enhancedRendering; 1180 this.board.renderer.enhancedRendering = true; 1181 this.board.renderer.drawCurve(copy); 1182 this.board.renderer.enhancedRendering = er; 1183 this.traces[copy.id] = copy.rendNode; 1184 1185 return this; 1186 }, 1187 1188 // Already documented in GeometryElement 1189 bounds: function () { 1190 var minX = Infinity, 1191 maxX = -Infinity, 1192 minY = Infinity, 1193 maxY = -Infinity, 1194 l = this.points.length, 1195 i, 1196 bezier, 1197 up; 1198 1199 if (this.bezierDegree === 3) { 1200 // Add methods X(), Y() 1201 for (i = 0; i < l; i++) { 1202 this.points[i].X = Type.bind(function () { 1203 return this.usrCoords[1]; 1204 }, this.points[i]); 1205 this.points[i].Y = Type.bind(function () { 1206 return this.usrCoords[2]; 1207 }, this.points[i]); 1208 } 1209 bezier = Numerics.bezier(this.points); 1210 up = bezier[3](); 1211 minX = Numerics.fminbr( 1212 function (t) { 1213 return bezier[0](t); 1214 }, 1215 [0, up] 1216 ); 1217 maxX = Numerics.fminbr( 1218 function (t) { 1219 return -bezier[0](t); 1220 }, 1221 [0, up] 1222 ); 1223 minY = Numerics.fminbr( 1224 function (t) { 1225 return bezier[1](t); 1226 }, 1227 [0, up] 1228 ); 1229 maxY = Numerics.fminbr( 1230 function (t) { 1231 return -bezier[1](t); 1232 }, 1233 [0, up] 1234 ); 1235 1236 minX = bezier[0](minX); 1237 maxX = bezier[0](maxX); 1238 minY = bezier[1](minY); 1239 maxY = bezier[1](maxY); 1240 return [minX, maxY, maxX, minY]; 1241 } 1242 1243 // Linear segments 1244 for (i = 0; i < l; i++) { 1245 if (minX > this.points[i].usrCoords[1]) { 1246 minX = this.points[i].usrCoords[1]; 1247 } 1248 1249 if (maxX < this.points[i].usrCoords[1]) { 1250 maxX = this.points[i].usrCoords[1]; 1251 } 1252 1253 if (minY > this.points[i].usrCoords[2]) { 1254 minY = this.points[i].usrCoords[2]; 1255 } 1256 1257 if (maxY < this.points[i].usrCoords[2]) { 1258 maxY = this.points[i].usrCoords[2]; 1259 } 1260 } 1261 1262 return [minX, maxY, maxX, minY]; 1263 }, 1264 1265 // documented in element.js 1266 getParents: function () { 1267 var p = [this.xterm, this.yterm, this.minX(), this.maxX()]; 1268 1269 if (this.parents.length !== 0) { 1270 p = this.parents; 1271 } 1272 1273 return p; 1274 }, 1275 1276 /** 1277 * Shift the curve by the vector 'where'. 1278 * 1279 * @param {Array} where Array containing the x and y coordinate of the target location. 1280 * @returns {JXG.Curve} Reference to itself. 1281 */ 1282 moveTo: function (where) { 1283 // TODO add animation 1284 var delta = [], 1285 p; 1286 if (this.points.length > 0 && !Type.evaluate(this.visProp.fixed)) { 1287 p = this.points[0]; 1288 if (where.length === 3) { 1289 delta = [ 1290 where[0] - p.usrCoords[0], 1291 where[1] - p.usrCoords[1], 1292 where[2] - p.usrCoords[2] 1293 ]; 1294 } else { 1295 delta = [where[0] - p.usrCoords[1], where[1] - p.usrCoords[2]]; 1296 } 1297 this.setPosition(Const.COORDS_BY_USER, delta); 1298 } 1299 return this; 1300 }, 1301 1302 /** 1303 * If the curve is the result of a transformation applied 1304 * to a continuous curve, the glider projection has to be done 1305 * on the original curve. Otherwise there will be problems 1306 * when changing between high and low precision plotting, 1307 * since there number of points changes. 1308 * 1309 * @private 1310 * @returns {Array} [Boolean, curve]: Array contining 'true' if curve is result of a transformation, 1311 * and the source curve of the transformation. 1312 */ 1313 getTransformationSource: function () { 1314 var isTransformed, curve_org; 1315 if (Type.exists(this._transformationSource)) { 1316 curve_org = this._transformationSource; 1317 if ( 1318 curve_org.elementClass === Const.OBJECT_CLASS_CURVE //&& 1319 //Type.evaluate(curve_org.visProp.curvetype) !== 'plot' 1320 ) { 1321 isTransformed = true; 1322 } 1323 } 1324 return [isTransformed, curve_org]; 1325 }, 1326 1327 pnpoly: function (x_in, y_in, coord_type) { 1328 var i, 1329 j, 1330 len, 1331 x, 1332 y, 1333 crds, 1334 v = this.points, 1335 isIn = false; 1336 1337 if (coord_type === Const.COORDS_BY_USER) { 1338 crds = new Coords(Const.COORDS_BY_USER, [x_in, y_in], this.board); 1339 x = crds.scrCoords[1]; 1340 y = crds.scrCoords[2]; 1341 } else { 1342 x = x_in; 1343 y = y_in; 1344 } 1345 1346 len = this.points.length; 1347 for (i = 0, j = len - 2; i < len - 1; j = i++) { 1348 if ( 1349 v[i].scrCoords[2] > y !== v[j].scrCoords[2] > y && 1350 x < 1351 ((v[j].scrCoords[1] - v[i].scrCoords[1]) * (y - v[i].scrCoords[2])) / 1352 (v[j].scrCoords[2] - v[i].scrCoords[2]) + 1353 v[i].scrCoords[1] 1354 ) { 1355 isIn = !isIn; 1356 } 1357 } 1358 1359 return isIn; 1360 } 1361 } 1362 ); 1363 1364 /** 1365 * @class This element is used to provide a constructor for curve, which is just a wrapper for element {@link Curve}. 1366 * A curve is a mapping from R to R^2. t mapsto (x(t),y(t)). The graph is drawn for t in the interval [a,b]. 1367 * <p> 1368 * The following types of curves can be plotted: 1369 * <ul> 1370 * <li> parametric curves: t mapsto (x(t),y(t)), where x() and y() are univariate functions. 1371 * <li> polar curves: curves commonly written with polar equations like spirals and cardioids. 1372 * <li> data plots: plot line segments through a given list of coordinates. 1373 * </ul> 1374 * @pseudo 1375 * @description 1376 * @name Curve 1377 * @augments JXG.Curve 1378 * @constructor 1379 * @type JXG.Curve 1380 * 1381 * @param {function,number_function,number_function,number_function,number} x,y,a_,b_ Parent elements for Parametric Curves. 1382 * <p> 1383 * x describes the x-coordinate of the curve. It may be a function term in one variable, e.g. x(t). 1384 * In case of x being of type number, x(t) is set to a constant function. 1385 * this function at the values of the array. 1386 * </p> 1387 * <p> 1388 * y describes the y-coordinate of the curve. In case of a number, y(t) is set to the constant function 1389 * returning this number. 1390 * </p> 1391 * <p> 1392 * Further parameters are an optional number or function for the left interval border a, 1393 * and an optional number or function for the right interval border b. 1394 * </p> 1395 * <p> 1396 * Default values are a=-10 and b=10. 1397 * </p> 1398 * @param {array_array,function,number} x,y Parent elements for Data Plots. 1399 * <p> 1400 * x and y are arrays contining the x and y coordinates of the data points which are connected by 1401 * line segments. The individual entries of x and y may also be functions. 1402 * In case of x being an array the curve type is data plot, regardless of the second parameter and 1403 * if additionally the second parameter y is a function term the data plot evaluates. 1404 * </p> 1405 * @param {function_array,function,number_function,number_function,number} r,offset_,a_,b_ Parent elements for Polar Curves. 1406 * <p> 1407 * The first parameter is a function term r(phi) describing the polar curve. 1408 * </p> 1409 * <p> 1410 * The second parameter is the offset of the curve. It has to be 1411 * an array containing numbers or functions describing the offset. Default value is the origin [0,0]. 1412 * </p> 1413 * <p> 1414 * Further parameters are an optional number or function for the left interval border a, 1415 * and an optional number or function for the right interval border b. 1416 * </p> 1417 * <p> 1418 * Default values are a=-10 and b=10. 1419 * </p> 1420 * <p> 1421 * Additionally, a curve can be created by providing a curve and a transformation (or an array of transformations). 1422 * The result is a curve which is the transformation of the supplied curve. 1423 * 1424 * @see JXG.Curve 1425 * @example 1426 * // Parametric curve 1427 * // Create a curve of the form (t-sin(t), 1-cos(t), i.e. 1428 * // the cycloid curve. 1429 * var graph = board.create('curve', 1430 * [function(t){ return t-Math.sin(t);}, 1431 * function(t){ return 1-Math.cos(t);}, 1432 * 0, 2*Math.PI] 1433 * ); 1434 * </pre><div class="jxgbox" id="JXGaf9f818b-f3b6-4c4d-8c4c-e4a4078b726d" style="width: 300px; height: 300px;"></div> 1435 * <script type="text/javascript"> 1436 * var c1_board = JXG.JSXGraph.initBoard('JXGaf9f818b-f3b6-4c4d-8c4c-e4a4078b726d', {boundingbox: [-1, 5, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1437 * var graph1 = c1_board.create('curve', [function(t){ return t-Math.sin(t);},function(t){ return 1-Math.cos(t);},0, 2*Math.PI]); 1438 * </script><pre> 1439 * @example 1440 * // Data plots 1441 * // Connect a set of points given by coordinates with dashed line segments. 1442 * // The x- and y-coordinates of the points are given in two separate 1443 * // arrays. 1444 * var x = [0,1,2,3,4,5,6,7,8,9]; 1445 * var y = [9.2,1.3,7.2,-1.2,4.0,5.3,0.2,6.5,1.1,0.0]; 1446 * var graph = board.create('curve', [x,y], {dash:2}); 1447 * </pre><div class="jxgbox" id="JXG7dcbb00e-b6ff-481d-b4a8-887f5d8c6a83" style="width: 300px; height: 300px;"></div> 1448 * <script type="text/javascript"> 1449 * var c3_board = JXG.JSXGraph.initBoard('JXG7dcbb00e-b6ff-481d-b4a8-887f5d8c6a83', {boundingbox: [-1,10,10,-1], axis: true, showcopyright: false, shownavigation: false}); 1450 * var x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 1451 * var y = [9.2, 1.3, 7.2, -1.2, 4.0, 5.3, 0.2, 6.5, 1.1, 0.0]; 1452 * var graph3 = c3_board.create('curve', [x,y], {dash:2}); 1453 * </script><pre> 1454 * @example 1455 * // Polar plot 1456 * // Create a curve with the equation r(phi)= a*(1+phi), i.e. 1457 * // a cardioid. 1458 * var a = board.create('slider',[[0,2],[2,2],[0,1,2]]); 1459 * var graph = board.create('curve', 1460 * [function(phi){ return a.Value()*(1-Math.cos(phi));}, 1461 * [1,0], 1462 * 0, 2*Math.PI], 1463 * {curveType: 'polar'} 1464 * ); 1465 * </pre><div class="jxgbox" id="JXGd0bc7a2a-8124-45ca-a6e7-142321a8f8c2" style="width: 300px; height: 300px;"></div> 1466 * <script type="text/javascript"> 1467 * var c2_board = JXG.JSXGraph.initBoard('JXGd0bc7a2a-8124-45ca-a6e7-142321a8f8c2', {boundingbox: [-3,3,3,-3], axis: true, showcopyright: false, shownavigation: false}); 1468 * var a = c2_board.create('slider',[[0,2],[2,2],[0,1,2]]); 1469 * var graph2 = c2_board.create('curve', [function(phi){ return a.Value()*(1-Math.cos(phi));}, [1,0], 0, 2*Math.PI], {curveType: 'polar'}); 1470 * </script><pre> 1471 * 1472 * @example 1473 * // Draggable Bezier curve 1474 * var col, p, c; 1475 * col = 'blue'; 1476 * p = []; 1477 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 1478 * p.push(board.create('point',[1, 2.5 ], {size: 5, strokeColor:col, fillColor:col})); 1479 * p.push(board.create('point',[-1, -2.5 ], {size: 5, strokeColor:col, fillColor:col})); 1480 * p.push(board.create('point',[2, -2], {size: 5, strokeColor:col, fillColor:col})); 1481 * 1482 * c = board.create('curve', JXG.Math.Numerics.bezier(p), 1483 * {strokeColor:'red', name:"curve", strokeWidth:5, fixed: false}); // Draggable curve 1484 * c.addParents(p); 1485 * </pre><div class="jxgbox" id="JXG7bcc6280-f6eb-433e-8281-c837c3387849" style="width: 300px; height: 300px;"></div> 1486 * <script type="text/javascript"> 1487 * (function(){ 1488 * var board, col, p, c; 1489 * board = JXG.JSXGraph.initBoard('JXG7bcc6280-f6eb-433e-8281-c837c3387849', {boundingbox: [-3,3,3,-3], axis: true, showcopyright: false, shownavigation: false}); 1490 * col = 'blue'; 1491 * p = []; 1492 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 1493 * p.push(board.create('point',[1, 2.5 ], {size: 5, strokeColor:col, fillColor:col})); 1494 * p.push(board.create('point',[-1, -2.5 ], {size: 5, strokeColor:col, fillColor:col})); 1495 * p.push(board.create('point',[2, -2], {size: 5, strokeColor:col, fillColor:col})); 1496 * 1497 * c = board.create('curve', JXG.Math.Numerics.bezier(p), 1498 * {strokeColor:'red', name:"curve", strokeWidth:5, fixed: false}); // Draggable curve 1499 * c.addParents(p); 1500 * })(); 1501 * </script><pre> 1502 * 1503 * @example 1504 * // The curve cu2 is the reflection of cu1 against line li 1505 * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'}); 1506 * var reflect = board.create('transform', [li], {type: 'reflect'}); 1507 * var cu1 = board.create('curve', [[-1, -1, -0.5, -1, -1, -0.5], [-3, -2, -2, -2, -2.5, -2.5]]); 1508 * var cu2 = board.create('curve', [cu1, reflect], {strokeColor: 'red'}); 1509 * 1510 * </pre><div id="JXG866dc7a2-d448-11e7-93b3-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 1511 * <script type="text/javascript"> 1512 * (function() { 1513 * var board = JXG.JSXGraph.initBoard('JXG866dc7a2-d448-11e7-93b3-901b0e1b8723', 1514 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1515 * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'}); 1516 * var reflect = board.create('transform', [li], {type: 'reflect'}); 1517 * var cu1 = board.create('curve', [[-1, -1, -0.5, -1, -1, -0.5], [-3, -2, -2, -2, -2.5, -2.5]]); 1518 * var cu2 = board.create('curve', [cu1, reflect], {strokeColor: 'red'}); 1519 * 1520 * })(); 1521 * 1522 * </script><pre> 1523 */ 1524 JXG.createCurve = function (board, parents, attributes) { 1525 var obj, 1526 cu, 1527 attr = Type.copyAttributes(attributes, board.options, "curve"); 1528 1529 obj = board.select(parents[0], true); 1530 if ( 1531 Type.isTransformationOrArray(parents[1]) && 1532 Type.isObject(obj) && 1533 (obj.type === Const.OBJECT_TYPE_CURVE || 1534 obj.type === Const.OBJECT_TYPE_ANGLE || 1535 obj.type === Const.OBJECT_TYPE_ARC || 1536 obj.type === Const.OBJECT_TYPE_CONIC || 1537 obj.type === Const.OBJECT_TYPE_SECTOR) 1538 ) { 1539 if (obj.type === Const.OBJECT_TYPE_SECTOR) { 1540 attr = Type.copyAttributes(attributes, board.options, "sector"); 1541 } else if (obj.type === Const.OBJECT_TYPE_ARC) { 1542 attr = Type.copyAttributes(attributes, board.options, "arc"); 1543 } else if (obj.type === Const.OBJECT_TYPE_ANGLE) { 1544 if (!Type.exists(attributes.withLabel)) { 1545 attributes.withLabel = false; 1546 } 1547 attr = Type.copyAttributes(attributes, board.options, "angle"); 1548 } else { 1549 attr = Type.copyAttributes(attributes, board.options, "curve"); 1550 } 1551 attr = Type.copyAttributes(attr, board.options, "curve"); 1552 1553 cu = new JXG.Curve(board, ["x", [], []], attr); 1554 cu.updateDataArray = function () { 1555 var i, 1556 le = obj.numberPoints; 1557 this.bezierDegree = obj.bezierDegree; 1558 this.dataX = []; 1559 this.dataY = []; 1560 for (i = 0; i < le; i++) { 1561 this.dataX.push(obj.points[i].usrCoords[1]); 1562 this.dataY.push(obj.points[i].usrCoords[2]); 1563 } 1564 return this; 1565 }; 1566 cu.addTransform(parents[1]); 1567 obj.addChild(cu); 1568 cu.setParents([obj]); 1569 cu._transformationSource = obj; 1570 1571 return cu; 1572 } 1573 attr = Type.copyAttributes(attributes, board.options, "curve"); 1574 return new JXG.Curve(board, ["x"].concat(parents), attr); 1575 }; 1576 1577 JXG.registerElement("curve", JXG.createCurve); 1578 1579 /** 1580 * @class This element is used to provide a constructor for functiongraph, 1581 * which is just a wrapper for element {@link Curve} with {@link JXG.Curve#X}() 1582 * set to x. The graph is drawn for x in the interval [a,b]. 1583 * @pseudo 1584 * @description 1585 * @name Functiongraph 1586 * @augments JXG.Curve 1587 * @constructor 1588 * @type JXG.Curve 1589 * @param {function_number,function_number,function} f,a_,b_ Parent elements are a function term f(x) describing the function graph. 1590 * <p> 1591 * Further, an optional number or function for the left interval border a, 1592 * and an optional number or function for the right interval border b. 1593 * <p> 1594 * Default values are a=-10 and b=10. 1595 * @see JXG.Curve 1596 * @example 1597 * // Create a function graph for f(x) = 0.5*x*x-2*x 1598 * var graph = board.create('functiongraph', 1599 * [function(x){ return 0.5*x*x-2*x;}, -2, 4] 1600 * ); 1601 * </pre><div class="jxgbox" id="JXGefd432b5-23a3-4846-ac5b-b471e668b437" style="width: 300px; height: 300px;"></div> 1602 * <script type="text/javascript"> 1603 * var alex1_board = JXG.JSXGraph.initBoard('JXGefd432b5-23a3-4846-ac5b-b471e668b437', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 1604 * var graph = alex1_board.create('functiongraph', [function(x){ return 0.5*x*x-2*x;}, -2, 4]); 1605 * </script><pre> 1606 * @example 1607 * // Create a function graph for f(x) = 0.5*x*x-2*x with variable interval 1608 * var s = board.create('slider',[[0,4],[3,4],[-2,4,5]]); 1609 * var graph = board.create('functiongraph', 1610 * [function(x){ return 0.5*x*x-2*x;}, 1611 * -2, 1612 * function(){return s.Value();}] 1613 * ); 1614 * </pre><div class="jxgbox" id="JXG4a203a84-bde5-4371-ad56-44619690bb50" style="width: 300px; height: 300px;"></div> 1615 * <script type="text/javascript"> 1616 * var alex2_board = JXG.JSXGraph.initBoard('JXG4a203a84-bde5-4371-ad56-44619690bb50', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 1617 * var s = alex2_board.create('slider',[[0,4],[3,4],[-2,4,5]]); 1618 * var graph = alex2_board.create('functiongraph', [function(x){ return 0.5*x*x-2*x;}, -2, function(){return s.Value();}]); 1619 * </script><pre> 1620 */ 1621 JXG.createFunctiongraph = function (board, parents, attributes) { 1622 var attr, 1623 par = ["x", "x"].concat(parents); 1624 1625 attr = Type.copyAttributes(attributes, board.options, "curve"); 1626 attr.curvetype = "functiongraph"; 1627 return new JXG.Curve(board, par, attr); 1628 }; 1629 1630 JXG.registerElement("functiongraph", JXG.createFunctiongraph); 1631 JXG.registerElement("plot", JXG.createFunctiongraph); 1632 1633 /** 1634 * @class This element is used to provide a constructor for (natural) cubic spline curves. 1635 * Create a dynamic spline interpolated curve given by sample points p_1 to p_n. 1636 * @pseudo 1637 * @description 1638 * @name Spline 1639 * @augments JXG.Curve 1640 * @constructor 1641 * @type JXG.Curve 1642 * @param {JXG.Board} board Reference to the board the spline is drawn on. 1643 * @param {Array} parents Array of points the spline interpolates. This can be 1644 * <ul> 1645 * <li> an array of JXGGraph points</li> 1646 * <li> an array of coordinate pairs</li> 1647 * <li> an array of functions returning coordinate pairs</li> 1648 * <li> an array consisting of an array with x-coordinates and an array of y-coordinates</li> 1649 * </ul> 1650 * All individual entries of coordinates arrays may be numbers or functions returning numbers. 1651 * @param {Object} attributes Define color, width, ... of the spline 1652 * @returns {JXG.Curve} Returns reference to an object of type JXG.Curve. 1653 * @see JXG.Curve 1654 * @example 1655 * 1656 * var p = []; 1657 * p[0] = board.create('point', [-2,2], {size: 4, face: 'o'}); 1658 * p[1] = board.create('point', [0,-1], {size: 4, face: 'o'}); 1659 * p[2] = board.create('point', [2,0], {size: 4, face: 'o'}); 1660 * p[3] = board.create('point', [4,1], {size: 4, face: 'o'}); 1661 * 1662 * var c = board.create('spline', p, {strokeWidth:3}); 1663 * </pre><div id="JXG6c197afc-e482-11e5-b1bf-901b0e1b8723" style="width: 300px; height: 300px;"></div> 1664 * <script type="text/javascript"> 1665 * (function() { 1666 * var board = JXG.JSXGraph.initBoard('JXG6c197afc-e482-11e5-b1bf-901b0e1b8723', 1667 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1668 * 1669 * var p = []; 1670 * p[0] = board.create('point', [-2,2], {size: 4, face: 'o'}); 1671 * p[1] = board.create('point', [0,-1], {size: 4, face: 'o'}); 1672 * p[2] = board.create('point', [2,0], {size: 4, face: 'o'}); 1673 * p[3] = board.create('point', [4,1], {size: 4, face: 'o'}); 1674 * 1675 * var c = board.create('spline', p, {strokeWidth:3}); 1676 * })(); 1677 * 1678 * </script><pre> 1679 * 1680 */ 1681 JXG.createSpline = function (board, parents, attributes) { 1682 var el, funcs, ret; 1683 1684 funcs = function () { 1685 var D, 1686 x = [], 1687 y = []; 1688 1689 return [ 1690 function (t, suspended) { 1691 // Function term 1692 var i, j, c; 1693 1694 if (!suspended) { 1695 x = []; 1696 y = []; 1697 1698 // given as [x[], y[]] 1699 if ( 1700 parents.length === 2 && 1701 Type.isArray(parents[0]) && 1702 Type.isArray(parents[1]) && 1703 parents[0].length === parents[1].length 1704 ) { 1705 for (i = 0; i < parents[0].length; i++) { 1706 if (Type.isFunction(parents[0][i])) { 1707 x.push(parents[0][i]()); 1708 } else { 1709 x.push(parents[0][i]); 1710 } 1711 1712 if (Type.isFunction(parents[1][i])) { 1713 y.push(parents[1][i]()); 1714 } else { 1715 y.push(parents[1][i]); 1716 } 1717 } 1718 } else { 1719 for (i = 0; i < parents.length; i++) { 1720 if (Type.isPoint(parents[i])) { 1721 x.push(parents[i].X()); 1722 y.push(parents[i].Y()); 1723 // given as [[x1,y1], [x2, y2], ...] 1724 } else if (Type.isArray(parents[i]) && parents[i].length === 2) { 1725 for (j = 0; j < parents.length; j++) { 1726 if (Type.isFunction(parents[j][0])) { 1727 x.push(parents[j][0]()); 1728 } else { 1729 x.push(parents[j][0]); 1730 } 1731 1732 if (Type.isFunction(parents[j][1])) { 1733 y.push(parents[j][1]()); 1734 } else { 1735 y.push(parents[j][1]); 1736 } 1737 } 1738 } else if ( 1739 Type.isFunction(parents[i]) && 1740 parents[i]().length === 2 1741 ) { 1742 c = parents[i](); 1743 x.push(c[0]); 1744 y.push(c[1]); 1745 } 1746 } 1747 } 1748 1749 // The array D has only to be calculated when the position of one or more sample points 1750 // changes. Otherwise D is always the same for all points on the spline. 1751 D = Numerics.splineDef(x, y); 1752 } 1753 1754 return Numerics.splineEval(t, x, y, D); 1755 }, 1756 // minX() 1757 function () { 1758 return x[0]; 1759 }, 1760 //maxX() 1761 function () { 1762 return x[x.length - 1]; 1763 } 1764 ]; 1765 }; 1766 1767 attributes = Type.copyAttributes(attributes, board.options, "curve"); 1768 attributes.curvetype = "functiongraph"; 1769 ret = funcs(); 1770 el = new JXG.Curve(board, ["x", "x", ret[0], ret[1], ret[2]], attributes); 1771 el.setParents(parents); 1772 el.elType = "spline"; 1773 1774 return el; 1775 }; 1776 1777 /** 1778 * Register the element type spline at JSXGraph 1779 * @private 1780 */ 1781 JXG.registerElement("spline", JXG.createSpline); 1782 1783 /** 1784 * @class This element is used to provide a constructor for cardinal spline curves. 1785 * Create a dynamic cardinal spline interpolated curve given by sample points p_1 to p_n. 1786 * @pseudo 1787 * @description 1788 * @name Cardinalspline 1789 * @augments JXG.Curve 1790 * @constructor 1791 * @type JXG.Curve 1792 * @param {JXG.Board} board Reference to the board the cardinal spline is drawn on. 1793 * @param {Array} parents Array with three entries. 1794 * <p> 1795 * First entry: Array of points the spline interpolates. This can be 1796 * <ul> 1797 * <li> an array of JXGGraph points</li> 1798 * <li> an array of coordinate pairs</li> 1799 * <li> an array of functions returning coordinate pairs</li> 1800 * <li> an array consisting of an array with x-coordinates and an array of y-coordinates</li> 1801 * </ul> 1802 * All individual entries of coordinates arrays may be numbers or functions returning numbers. 1803 * <p> 1804 * Second entry: tau number or function 1805 * <p> 1806 * Third entry: type string containing 'uniform' (default) or 'centripetal'. 1807 * @param {Object} attributes Define color, width, ... of the cardinal spline 1808 * @returns {JXG.Curve} Returns reference to an object of type JXG.Curve. 1809 * @see JXG.Curve 1810 * @example 1811 * //create a cardinal spline out of an array of JXG points with adjustable tension 1812 * //create array of points 1813 * var p1 = board.create('point',[0,0]) 1814 * var p2 = board.create('point',[1,4]) 1815 * var p3 = board.create('point',[4,5]) 1816 * var p4 = board.create('point',[2,3]) 1817 * var p5 = board.create('point',[3,0]) 1818 * var p = [p1,p2,p3,p4,p5] 1819 * 1820 * // tension 1821 * tau = board.create('slider', [[4,3],[9,3],[0.001,0.5,1]], {name:'tau'}); 1822 * c = board.create('curve', JXG.Math.Numerics.CardinalSpline(p, function(){ return tau.Value();}), {strokeWidth:3}); 1823 * </pre><div id="JXG6c197afc-e482-11e5-b2af-901b0e1b8723" style="width: 300px; height: 300px;"></div> 1824 * <script type="text/javascript"> 1825 * (function() { 1826 * var board = JXG.JSXGraph.initBoard('JXG6c197afc-e482-11e5-b2af-901b0e1b8723', 1827 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1828 * 1829 * var p = []; 1830 * p[0] = board.create('point', [-2,2], {size: 4, face: 'o'}); 1831 * p[1] = board.create('point', [0,-1], {size: 4, face: 'o'}); 1832 * p[2] = board.create('point', [2,0], {size: 4, face: 'o'}); 1833 * p[3] = board.create('point', [4,1], {size: 4, face: 'o'}); 1834 * 1835 * var c = board.create('spline', p, {strokeWidth:3}); 1836 * })(); 1837 * 1838 * </script><pre> 1839 */ 1840 JXG.createCardinalSpline = function (board, parents, attributes) { 1841 var el, 1842 getPointLike, 1843 points, 1844 tau, 1845 type, 1846 p, 1847 q, 1848 i, 1849 le, 1850 splineArr, 1851 errStr = "\nPossible parent types: [points:array, tau:number|function, type:string]"; 1852 1853 if (!Type.exists(parents[0]) || !Type.isArray(parents[0])) { 1854 throw new Error( 1855 "JSXGraph: JXG.createCardinalSpline: argument 1 'points' has to be array of points or coordinate pairs" + 1856 errStr 1857 ); 1858 } 1859 if ( 1860 !Type.exists(parents[1]) || 1861 (!Type.isNumber(parents[1]) && !Type.isFunction(parents[1])) 1862 ) { 1863 throw new Error( 1864 "JSXGraph: JXG.createCardinalSpline: argument 2 'tau' has to be number between [0,1] or function'" + 1865 errStr 1866 ); 1867 } 1868 if (!Type.exists(parents[2]) || !Type.isString(parents[2])) { 1869 throw new Error( 1870 "JSXGraph: JXG.createCardinalSpline: argument 3 'type' has to be string 'uniform' or 'centripetal'" + 1871 errStr 1872 ); 1873 } 1874 1875 attributes = Type.copyAttributes(attributes, board.options, "curve"); 1876 attributes = Type.copyAttributes(attributes, board.options, "cardinalspline"); 1877 attributes.curvetype = "parameter"; 1878 1879 p = parents[0]; 1880 q = []; 1881 1882 // given as [x[], y[]] 1883 if ( 1884 !attributes.isarrayofcoordinates && 1885 p.length === 2 && 1886 Type.isArray(p[0]) && 1887 Type.isArray(p[1]) && 1888 p[0].length === p[1].length 1889 ) { 1890 for (i = 0; i < p[0].length; i++) { 1891 q[i] = []; 1892 if (Type.isFunction(p[0][i])) { 1893 q[i].push(p[0][i]()); 1894 } else { 1895 q[i].push(p[0][i]); 1896 } 1897 1898 if (Type.isFunction(p[1][i])) { 1899 q[i].push(p[1][i]()); 1900 } else { 1901 q[i].push(p[1][i]); 1902 } 1903 } 1904 } else { 1905 // given as [[x0, y0], [x1, y1], point, ...] 1906 for (i = 0; i < p.length; i++) { 1907 if (Type.isString(p[i])) { 1908 q.push(board.select(p[i])); 1909 } else if (Type.isPoint(p[i])) { 1910 q.push(p[i]); 1911 // given as [[x0,y0], [x1, y2], ...] 1912 } else if (Type.isArray(p[i]) && p[i].length === 2) { 1913 q[i] = []; 1914 if (Type.isFunction(p[i][0])) { 1915 q[i].push(p[i][0]()); 1916 } else { 1917 q[i].push(p[i][0]); 1918 } 1919 1920 if (Type.isFunction(p[i][1])) { 1921 q[i].push(p[i][1]()); 1922 } else { 1923 q[i].push(p[i][1]); 1924 } 1925 } else if (Type.isFunction(p[i]) && p[i]().length === 2) { 1926 q.push(parents[i]()); 1927 } 1928 } 1929 } 1930 1931 if (attributes.createpoints === true) { 1932 points = Type.providePoints(board, q, attributes, "cardinalspline", ["points"]); 1933 } else { 1934 points = []; 1935 1936 /** 1937 * @ignore 1938 */ 1939 getPointLike = function (ii) { 1940 return { 1941 X: function () { 1942 return q[ii][0]; 1943 }, 1944 Y: function () { 1945 return q[ii][1]; 1946 }, 1947 Dist: function (p) { 1948 var dx = this.X() - p.X(), 1949 dy = this.Y() - p.Y(); 1950 return Math.sqrt(dx * dx + dy * dy); 1951 } 1952 }; 1953 }; 1954 1955 for (i = 0; i < q.length; i++) { 1956 if (Type.isPoint(q[i])) { 1957 points.push(q[i]); 1958 } else { 1959 points.push(getPointLike(i)); 1960 } 1961 } 1962 } 1963 1964 tau = parents[1]; 1965 type = parents[2]; 1966 1967 splineArr = ["x"].concat(Numerics.CardinalSpline(points, tau, type)); 1968 1969 el = new JXG.Curve(board, splineArr, attributes); 1970 le = points.length; 1971 el.setParents(points); 1972 for (i = 0; i < le; i++) { 1973 p = points[i]; 1974 if (Type.isPoint(p)) { 1975 if (Type.exists(p._is_new)) { 1976 el.addChild(p); 1977 delete p._is_new; 1978 } else { 1979 p.addChild(el); 1980 } 1981 } 1982 } 1983 el.elType = "cardinalspline"; 1984 1985 return el; 1986 }; 1987 1988 /** 1989 * Register the element type cardinalspline at JSXGraph 1990 * @private 1991 */ 1992 JXG.registerElement("cardinalspline", JXG.createCardinalSpline); 1993 1994 /** 1995 * @class This element is used to provide a constructor for metapost spline curves. 1996 * Create a dynamic metapost spline interpolated curve given by sample points p_1 to p_n. 1997 * @pseudo 1998 * @description 1999 * @name Metapostspline 2000 * @augments JXG.Curve 2001 * @constructor 2002 * @type JXG.Curve 2003 * @param {JXG.Board} board Reference to the board the metapost spline is drawn on. 2004 * @param {Array} parents Array with two entries. 2005 * <p> 2006 * First entry: Array of points the spline interpolates. This can be 2007 * <ul> 2008 * <li> an array of JXGGraph points</li> 2009 * <li> an object of coordinate pairs</li> 2010 * <li> an array of functions returning coordinate pairs</li> 2011 * <li> an array consisting of an array with x-coordinates and an array of y-coordinates</li> 2012 * </ul> 2013 * All individual entries of coordinates arrays may be numbers or functions returning numbers. 2014 * <p> 2015 * Second entry: JavaScript object containing the control values like tension, direction, curl. 2016 * @param {Object} attributes Define color, width, ... of the metapost spline 2017 * @returns {JXG.Curve} Returns reference to an object of type JXG.Curve. 2018 * @see JXG.Curve 2019 * @example 2020 * var po = [], 2021 * attr = { 2022 * size: 5, 2023 * color: 'red' 2024 * }, 2025 * controls; 2026 * 2027 * var tension = board.create('slider', [[-3, 6], [3, 6], [0, 1, 20]], {name: 'tension'}); 2028 * var curl = board.create('slider', [[-3, 5], [3, 5], [0, 1, 30]], {name: 'curl A, D'}); 2029 * var dir = board.create('slider', [[-3, 4], [3, 4], [-180, 0, 180]], {name: 'direction B'}); 2030 * 2031 * po.push(board.create('point', [-3, -3])); 2032 * po.push(board.create('point', [0, -3])); 2033 * po.push(board.create('point', [4, -5])); 2034 * po.push(board.create('point', [6, -2])); 2035 * 2036 * var controls = { 2037 * tension: function() {return tension.Value(); }, 2038 * direction: { 1: function() {return dir.Value(); } }, 2039 * curl: { 0: function() {return curl.Value(); }, 2040 * 3: function() {return curl.Value(); } 2041 * }, 2042 * isClosed: false 2043 * }; 2044 * 2045 * // Plot a metapost curve 2046 * var cu = board.create('metapostspline', [po, controls], {strokeColor: 'blue', strokeWidth: 2}); 2047 * 2048 * 2049 * </pre><div id="JXGb8c6ffed-7419-41a3-9e55-3754b2327ae9" class="jxgbox" style="width: 300px; height: 300px;"></div> 2050 * <script type="text/javascript"> 2051 * (function() { 2052 * var board = JXG.JSXGraph.initBoard('JXGb8c6ffed-7419-41a3-9e55-3754b2327ae9', 2053 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2054 * var po = [], 2055 * attr = { 2056 * size: 5, 2057 * color: 'red' 2058 * }, 2059 * controls; 2060 * 2061 * var tension = board.create('slider', [[-3, 6], [3, 6], [0, 1, 20]], {name: 'tension'}); 2062 * var curl = board.create('slider', [[-3, 5], [3, 5], [0, 1, 30]], {name: 'curl A, D'}); 2063 * var dir = board.create('slider', [[-3, 4], [3, 4], [-180, 0, 180]], {name: 'direction B'}); 2064 * 2065 * po.push(board.create('point', [-3, -3])); 2066 * po.push(board.create('point', [0, -3])); 2067 * po.push(board.create('point', [4, -5])); 2068 * po.push(board.create('point', [6, -2])); 2069 * 2070 * var controls = { 2071 * tension: function() {return tension.Value(); }, 2072 * direction: { 1: function() {return dir.Value(); } }, 2073 * curl: { 0: function() {return curl.Value(); }, 2074 * 3: function() {return curl.Value(); } 2075 * }, 2076 * isClosed: false 2077 * }; 2078 * 2079 * // Plot a metapost curve 2080 * var cu = board.create('metapostspline', [po, controls], {strokeColor: 'blue', strokeWidth: 2}); 2081 * 2082 * 2083 * })(); 2084 * 2085 * </script><pre> 2086 * 2087 */ 2088 JXG.createMetapostSpline = function (board, parents, attributes) { 2089 var el, 2090 getPointLike, 2091 points, 2092 controls, 2093 p, 2094 q, 2095 i, 2096 le, 2097 errStr = "\nPossible parent types: [points:array, controls:object"; 2098 2099 if (!Type.exists(parents[0]) || !Type.isArray(parents[0])) { 2100 throw new Error( 2101 "JSXGraph: JXG.createMetapostSpline: argument 1 'points' has to be array of points or coordinate pairs" + 2102 errStr 2103 ); 2104 } 2105 if (!Type.exists(parents[1]) || !Type.isObject(parents[1])) { 2106 throw new Error( 2107 "JSXGraph: JXG.createMetapostSpline: argument 2 'controls' has to be a JavaScript object'" + 2108 errStr 2109 ); 2110 } 2111 2112 attributes = Type.copyAttributes(attributes, board.options, "curve"); 2113 attributes = Type.copyAttributes(attributes, board.options, "metapostspline"); 2114 attributes.curvetype = "parameter"; 2115 2116 p = parents[0]; 2117 q = []; 2118 2119 // given as [x[], y[]] 2120 if ( 2121 !attributes.isarrayofcoordinates && 2122 p.length === 2 && 2123 Type.isArray(p[0]) && 2124 Type.isArray(p[1]) && 2125 p[0].length === p[1].length 2126 ) { 2127 for (i = 0; i < p[0].length; i++) { 2128 q[i] = []; 2129 if (Type.isFunction(p[0][i])) { 2130 q[i].push(p[0][i]()); 2131 } else { 2132 q[i].push(p[0][i]); 2133 } 2134 2135 if (Type.isFunction(p[1][i])) { 2136 q[i].push(p[1][i]()); 2137 } else { 2138 q[i].push(p[1][i]); 2139 } 2140 } 2141 } else { 2142 // given as [[x0, y0], [x1, y1], point, ...] 2143 for (i = 0; i < p.length; i++) { 2144 if (Type.isString(p[i])) { 2145 q.push(board.select(p[i])); 2146 } else if (Type.isPoint(p[i])) { 2147 q.push(p[i]); 2148 // given as [[x0,y0], [x1, y2], ...] 2149 } else if (Type.isArray(p[i]) && p[i].length === 2) { 2150 q[i] = []; 2151 if (Type.isFunction(p[i][0])) { 2152 q[i].push(p[i][0]()); 2153 } else { 2154 q[i].push(p[i][0]); 2155 } 2156 2157 if (Type.isFunction(p[i][1])) { 2158 q[i].push(p[i][1]()); 2159 } else { 2160 q[i].push(p[i][1]); 2161 } 2162 } else if (Type.isFunction(p[i]) && p[i]().length === 2) { 2163 q.push(parents[i]()); 2164 } 2165 } 2166 } 2167 2168 if (attributes.createpoints === true) { 2169 points = Type.providePoints(board, q, attributes, "metapostspline", ["points"]); 2170 } else { 2171 points = []; 2172 2173 /** 2174 * @ignore 2175 */ 2176 getPointLike = function (ii) { 2177 return { 2178 X: function () { 2179 return q[ii][0]; 2180 }, 2181 Y: function () { 2182 return q[ii][1]; 2183 } 2184 }; 2185 }; 2186 2187 for (i = 0; i < q.length; i++) { 2188 if (Type.isPoint(q[i])) { 2189 points.push(q[i]); 2190 } else { 2191 points.push(getPointLike); 2192 } 2193 } 2194 } 2195 2196 controls = parents[1]; 2197 2198 el = new JXG.Curve(board, ["t", [], [], 0, p.length - 1], attributes); 2199 el.updateDataArray = function () { 2200 var res, 2201 i, 2202 len = points.length, 2203 p = []; 2204 2205 for (i = 0; i < len; i++) { 2206 p.push([points[i].X(), points[i].Y()]); 2207 } 2208 2209 res = JXG.Math.Metapost.curve(p, controls); 2210 this.dataX = res[0]; 2211 this.dataY = res[1]; 2212 }; 2213 el.bezierDegree = 3; 2214 2215 le = points.length; 2216 el.setParents(points); 2217 for (i = 0; i < le; i++) { 2218 if (Type.isPoint(points[i])) { 2219 points[i].addChild(el); 2220 } 2221 } 2222 el.elType = "metapostspline"; 2223 2224 return el; 2225 }; 2226 2227 JXG.registerElement("metapostspline", JXG.createMetapostSpline); 2228 2229 /** 2230 * @class This element is used to provide a constructor for Riemann sums, which is realized as a special curve. 2231 * The returned element has the method Value() which returns the sum of the areas of the bars. 2232 * @pseudo 2233 * @description 2234 * @name Riemannsum 2235 * @augments JXG.Curve 2236 * @constructor 2237 * @type JXG.Curve 2238 * @param {function,array_number,function_string,function_function,number_function,number} f,n,type_,a_,b_ Parent elements of Riemannsum are a 2239 * Either a function term f(x) describing the function graph which is filled by the Riemann bars, or 2240 * an array consisting of two functions and the area between is filled by the Riemann bars. 2241 * <p> 2242 * n determines the number of bars, it is either a fixed number or a function. 2243 * <p> 2244 * type is a string or function returning one of the values: 'left', 'right', 'middle', 'lower', 'upper', 'random', 'simpson', or 'trapezoidal'. 2245 * Default value is 'left'. 2246 * <p> 2247 * Further parameters are an optional number or function for the left interval border a, 2248 * and an optional number or function for the right interval border b. 2249 * <p> 2250 * Default values are a=-10 and b=10. 2251 * @see JXG.Curve 2252 * @example 2253 * // Create Riemann sums for f(x) = 0.5*x*x-2*x. 2254 * var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1}); 2255 * var f = function(x) { return 0.5*x*x-2*x; }; 2256 * var r = board.create('riemannsum', 2257 * [f, function(){return s.Value();}, 'upper', -2, 5], 2258 * {fillOpacity:0.4} 2259 * ); 2260 * var g = board.create('functiongraph',[f, -2, 5]); 2261 * var t = board.create('text',[-2,-2, function(){ return 'Sum=' + JXG.toFixed(r.Value(), 4); }]); 2262 * </pre><div class="jxgbox" id="JXG940f40cc-2015-420d-9191-c5d83de988cf" style="width: 300px; height: 300px;"></div> 2263 * <script type="text/javascript"> 2264 * (function(){ 2265 * var board = JXG.JSXGraph.initBoard('JXG940f40cc-2015-420d-9191-c5d83de988cf', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 2266 * var f = function(x) { return 0.5*x*x-2*x; }; 2267 * var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1}); 2268 * var r = board.create('riemannsum', [f, function(){return s.Value();}, 'upper', -2, 5], {fillOpacity:0.4}); 2269 * var g = board.create('functiongraph', [f, -2, 5]); 2270 * var t = board.create('text',[-2,-2, function(){ return 'Sum=' + JXG.toFixed(r.Value(), 4); }]); 2271 * })(); 2272 * </script><pre> 2273 * 2274 * @example 2275 * // Riemann sum between two functions 2276 * var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1}); 2277 * var g = function(x) { return 0.5*x*x-2*x; }; 2278 * var f = function(x) { return -x*(x-4); }; 2279 * var r = board.create('riemannsum', 2280 * [[g,f], function(){return s.Value();}, 'lower', 0, 4], 2281 * {fillOpacity:0.4} 2282 * ); 2283 * var f = board.create('functiongraph',[f, -2, 5]); 2284 * var g = board.create('functiongraph',[g, -2, 5]); 2285 * var t = board.create('text',[-2,-2, function(){ return 'Sum=' + JXG.toFixed(r.Value(), 4); }]); 2286 * </pre><div class="jxgbox" id="JXGf9a7ba38-b50f-4a32-a873-2f3bf9caee79" style="width: 300px; height: 300px;"></div> 2287 * <script type="text/javascript"> 2288 * (function(){ 2289 * var board = JXG.JSXGraph.initBoard('JXGf9a7ba38-b50f-4a32-a873-2f3bf9caee79', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 2290 * var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1}); 2291 * var g = function(x) { return 0.5*x*x-2*x; }; 2292 * var f = function(x) { return -x*(x-4); }; 2293 * var r = board.create('riemannsum', 2294 * [[g,f], function(){return s.Value();}, 'lower', 0, 4], 2295 * {fillOpacity:0.4} 2296 * ); 2297 * var f = board.create('functiongraph',[f, -2, 5]); 2298 * var g = board.create('functiongraph',[g, -2, 5]); 2299 * var t = board.create('text',[-2,-2, function(){ return 'Sum=' + JXG.toFixed(r.Value(), 4); }]); 2300 * })(); 2301 * </script><pre> 2302 */ 2303 JXG.createRiemannsum = function (board, parents, attributes) { 2304 var n, type, f, par, c, attr; 2305 2306 attr = Type.copyAttributes(attributes, board.options, "riemannsum"); 2307 attr.curvetype = "plot"; 2308 2309 f = parents[0]; 2310 n = Type.createFunction(parents[1], board, ""); 2311 2312 if (!Type.exists(n)) { 2313 throw new Error( 2314 "JSXGraph: JXG.createRiemannsum: argument '2' n has to be number or function." + 2315 "\nPossible parent types: [function,n:number|function,type,start:number|function,end:number|function]" 2316 ); 2317 } 2318 2319 type = Type.createFunction(parents[2], board, "", false); 2320 if (!Type.exists(type)) { 2321 throw new Error( 2322 "JSXGraph: JXG.createRiemannsum: argument 3 'type' has to be string or function." + 2323 "\nPossible parent types: [function,n:number|function,type,start:number|function,end:number|function]" 2324 ); 2325 } 2326 2327 par = [[0], [0]].concat(parents.slice(3)); 2328 2329 c = board.create("curve", par, attr); 2330 2331 c.sum = 0.0; 2332 /** 2333 * Returns the value of the Riemann sum, i.e. the sum of the (signed) areas of the rectangles. 2334 * @name Value 2335 * @memberOf Riemann.prototype 2336 * @function 2337 * @returns {Number} value of Riemann sum. 2338 */ 2339 c.Value = function () { 2340 return this.sum; 2341 }; 2342 2343 /** 2344 * @ignore 2345 */ 2346 c.updateDataArray = function () { 2347 var u = Numerics.riemann(f, n(), type(), this.minX(), this.maxX()); 2348 this.dataX = u[0]; 2349 this.dataY = u[1]; 2350 2351 // Update "Riemann sum" 2352 this.sum = u[2]; 2353 }; 2354 2355 c.addParentsFromJCFunctions([n, type]); 2356 2357 return c; 2358 }; 2359 2360 JXG.registerElement("riemannsum", JXG.createRiemannsum); 2361 2362 /** 2363 * @class This element is used to provide a constructor for trace curve (simple locus curve), which is realized as a special curve. 2364 * @pseudo 2365 * @description 2366 * @name Tracecurve 2367 * @augments JXG.Curve 2368 * @constructor 2369 * @type JXG.Curve 2370 * @param {Point,Point} Parent elements of Tracecurve are a 2371 * glider point and a point whose locus is traced. 2372 * @see JXG.Curve 2373 * @example 2374 * // Create trace curve. 2375 * var c1 = board.create('circle',[[0, 0], [2, 0]]), 2376 * p1 = board.create('point',[-3, 1]), 2377 * g1 = board.create('glider',[2, 1, c1]), 2378 * s1 = board.create('segment',[g1, p1]), 2379 * p2 = board.create('midpoint',[s1]), 2380 * curve = board.create('tracecurve', [g1, p2]); 2381 * 2382 * </pre><div class="jxgbox" id="JXG5749fb7d-04fc-44d2-973e-45c1951e29ad" style="width: 300px; height: 300px;"></div> 2383 * <script type="text/javascript"> 2384 * var tc1_board = JXG.JSXGraph.initBoard('JXG5749fb7d-04fc-44d2-973e-45c1951e29ad', {boundingbox: [-4, 4, 4, -4], axis: false, showcopyright: false, shownavigation: false}); 2385 * var c1 = tc1_board.create('circle',[[0, 0], [2, 0]]), 2386 * p1 = tc1_board.create('point',[-3, 1]), 2387 * g1 = tc1_board.create('glider',[2, 1, c1]), 2388 * s1 = tc1_board.create('segment',[g1, p1]), 2389 * p2 = tc1_board.create('midpoint',[s1]), 2390 * curve = tc1_board.create('tracecurve', [g1, p2]); 2391 * </script><pre> 2392 */ 2393 JXG.createTracecurve = function (board, parents, attributes) { 2394 var c, glider, tracepoint, attr; 2395 2396 if (parents.length !== 2) { 2397 throw new Error( 2398 "JSXGraph: Can't create trace curve with given parent'" + 2399 "\nPossible parent types: [glider, point]" 2400 ); 2401 } 2402 2403 glider = board.select(parents[0]); 2404 tracepoint = board.select(parents[1]); 2405 2406 if (glider.type !== Const.OBJECT_TYPE_GLIDER || !Type.isPoint(tracepoint)) { 2407 throw new Error( 2408 "JSXGraph: Can't create trace curve with parent types '" + 2409 typeof parents[0] + 2410 "' and '" + 2411 typeof parents[1] + 2412 "'." + 2413 "\nPossible parent types: [glider, point]" 2414 ); 2415 } 2416 2417 attr = Type.copyAttributes(attributes, board.options, "tracecurve"); 2418 attr.curvetype = "plot"; 2419 c = board.create("curve", [[0], [0]], attr); 2420 2421 /** 2422 * @ignore 2423 */ 2424 c.updateDataArray = function () { 2425 var i, 2426 step, 2427 t, 2428 el, 2429 pEl, 2430 x, 2431 y, 2432 from, 2433 savetrace, 2434 le = attr.numberpoints, 2435 savePos = glider.position, 2436 slideObj = glider.slideObject, 2437 mi = slideObj.minX(), 2438 ma = slideObj.maxX(); 2439 2440 // set step width 2441 step = (ma - mi) / le; 2442 this.dataX = []; 2443 this.dataY = []; 2444 2445 /* 2446 * For gliders on circles and lines a closed curve is computed. 2447 * For gliders on curves the curve is not closed. 2448 */ 2449 if (slideObj.elementClass !== Const.OBJECT_CLASS_CURVE) { 2450 le++; 2451 } 2452 2453 // Loop over all steps 2454 for (i = 0; i < le; i++) { 2455 t = mi + i * step; 2456 x = slideObj.X(t) / slideObj.Z(t); 2457 y = slideObj.Y(t) / slideObj.Z(t); 2458 2459 // Position the glider 2460 glider.setPositionDirectly(Const.COORDS_BY_USER, [x, y]); 2461 from = false; 2462 2463 // Update all elements from the glider up to the trace element 2464 for (el in this.board.objects) { 2465 if (this.board.objects.hasOwnProperty(el)) { 2466 pEl = this.board.objects[el]; 2467 2468 if (pEl === glider) { 2469 from = true; 2470 } 2471 2472 if (from && pEl.needsRegularUpdate) { 2473 // Save the trace mode of the element 2474 savetrace = pEl.visProp.trace; 2475 pEl.visProp.trace = false; 2476 pEl.needsUpdate = true; 2477 pEl.update(true); 2478 2479 // Restore the trace mode 2480 pEl.visProp.trace = savetrace; 2481 if (pEl === tracepoint) { 2482 break; 2483 } 2484 } 2485 } 2486 } 2487 2488 // Store the position of the trace point 2489 this.dataX[i] = tracepoint.X(); 2490 this.dataY[i] = tracepoint.Y(); 2491 } 2492 2493 // Restore the original position of the glider 2494 glider.position = savePos; 2495 from = false; 2496 2497 // Update all elements from the glider to the trace point 2498 for (el in this.board.objects) { 2499 if (this.board.objects.hasOwnProperty(el)) { 2500 pEl = this.board.objects[el]; 2501 if (pEl === glider) { 2502 from = true; 2503 } 2504 2505 if (from && pEl.needsRegularUpdate) { 2506 savetrace = pEl.visProp.trace; 2507 pEl.visProp.trace = false; 2508 pEl.needsUpdate = true; 2509 pEl.update(true); 2510 pEl.visProp.trace = savetrace; 2511 2512 if (pEl === tracepoint) { 2513 break; 2514 } 2515 } 2516 } 2517 } 2518 }; 2519 2520 return c; 2521 }; 2522 2523 JXG.registerElement("tracecurve", JXG.createTracecurve); 2524 2525 /** 2526 * @class This element is used to provide a constructor for step function, which is realized as a special curve. 2527 * 2528 * In case the data points should be updated after creation time, they can be accessed by curve.xterm and curve.yterm. 2529 * @pseudo 2530 * @description 2531 * @name Stepfunction 2532 * @augments JXG.Curve 2533 * @constructor 2534 * @type JXG.Curve 2535 * @param {Array,Array|Function} Parent elements of Stepfunction are two arrays containing the coordinates. 2536 * @see JXG.Curve 2537 * @example 2538 * // Create step function. 2539 var curve = board.create('stepfunction', [[0,1,2,3,4,5], [1,3,0,2,2,1]]); 2540 2541 * </pre><div class="jxgbox" id="JXG32342ec9-ad17-4339-8a97-ff23dc34f51a" style="width: 300px; height: 300px;"></div> 2542 * <script type="text/javascript"> 2543 * var sf1_board = JXG.JSXGraph.initBoard('JXG32342ec9-ad17-4339-8a97-ff23dc34f51a', {boundingbox: [-1, 5, 6, -2], axis: true, showcopyright: false, shownavigation: false}); 2544 * var curve = sf1_board.create('stepfunction', [[0,1,2,3,4,5], [1,3,0,2,2,1]]); 2545 * </script><pre> 2546 */ 2547 JXG.createStepfunction = function (board, parents, attributes) { 2548 var c, attr; 2549 if (parents.length !== 2) { 2550 throw new Error( 2551 "JSXGraph: Can't create step function with given parent'" + 2552 "\nPossible parent types: [array, array|function]" 2553 ); 2554 } 2555 2556 attr = Type.copyAttributes(attributes, board.options, "stepfunction"); 2557 c = board.create("curve", parents, attr); 2558 /** 2559 * @ignore 2560 */ 2561 c.updateDataArray = function () { 2562 var i, 2563 j = 0, 2564 len = this.xterm.length; 2565 2566 this.dataX = []; 2567 this.dataY = []; 2568 2569 if (len === 0) { 2570 return; 2571 } 2572 2573 this.dataX[j] = this.xterm[0]; 2574 this.dataY[j] = this.yterm[0]; 2575 ++j; 2576 2577 for (i = 1; i < len; ++i) { 2578 this.dataX[j] = this.xterm[i]; 2579 this.dataY[j] = this.dataY[j - 1]; 2580 ++j; 2581 this.dataX[j] = this.xterm[i]; 2582 this.dataY[j] = this.yterm[i]; 2583 ++j; 2584 } 2585 }; 2586 2587 return c; 2588 }; 2589 2590 JXG.registerElement("stepfunction", JXG.createStepfunction); 2591 2592 /** 2593 * @class This element is used to provide a constructor for the graph showing 2594 * the (numerical) derivative of a given curve. 2595 * 2596 * @pseudo 2597 * @description 2598 * @name Derivative 2599 * @augments JXG.Curve 2600 * @constructor 2601 * @type JXG.Curve 2602 * @param {JXG.Curve} Parent Curve for which the derivative is generated. 2603 * @see JXG.Curve 2604 * @example 2605 * var cu = board.create('cardinalspline', [[[-3,0], [-1,2], [0,1], [2,0], [3,1]], 0.5, 'centripetal'], {createPoints: false}); 2606 * var d = board.create('derivative', [cu], {dash: 2}); 2607 * 2608 * </pre><div id="JXGb9600738-1656-11e8-8184-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 2609 * <script type="text/javascript"> 2610 * (function() { 2611 * var board = JXG.JSXGraph.initBoard('JXGb9600738-1656-11e8-8184-901b0e1b8723', 2612 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2613 * var cu = board.create('cardinalspline', [[[-3,0], [-1,2], [0,1], [2,0], [3,1]], 0.5, 'centripetal'], {createPoints: false}); 2614 * var d = board.create('derivative', [cu], {dash: 2}); 2615 * 2616 * })(); 2617 * 2618 * </script><pre> 2619 * 2620 */ 2621 JXG.createDerivative = function (board, parents, attributes) { 2622 var c, curve, dx, dy, attr; 2623 2624 if (parents.length !== 1 && parents[0].class !== Const.OBJECT_CLASS_CURVE) { 2625 throw new Error( 2626 "JSXGraph: Can't create derivative curve with given parent'" + 2627 "\nPossible parent types: [curve]" 2628 ); 2629 } 2630 2631 attr = Type.copyAttributes(attributes, board.options, "curve"); 2632 2633 curve = parents[0]; 2634 dx = Numerics.D(curve.X); 2635 dy = Numerics.D(curve.Y); 2636 2637 c = board.create( 2638 "curve", 2639 [ 2640 function (t) { 2641 return curve.X(t); 2642 }, 2643 function (t) { 2644 return dy(t) / dx(t); 2645 }, 2646 curve.minX(), 2647 curve.maxX() 2648 ], 2649 attr 2650 ); 2651 2652 c.setParents(curve); 2653 2654 return c; 2655 }; 2656 2657 JXG.registerElement("derivative", JXG.createDerivative); 2658 2659 /** 2660 * @class Intersection of two closed path elements. The elements may be of type curve, circle, polygon, inequality. 2661 * If one element is a curve, it has to be closed. 2662 * The resulting element is of type curve. 2663 * @pseudo 2664 * @description 2665 * @name CurveIntersection 2666 * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve1 First element which is intersected 2667 * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve2 Second element which is intersected 2668 * @augments JXG.Curve 2669 * @constructor 2670 * @type JXG.Curve 2671 * 2672 * @example 2673 * var f = board.create('functiongraph', ['cos(x)']); 2674 * var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1}); 2675 * var circ = board.create('circle', [[0,0], 4]); 2676 * var clip = board.create('curveintersection', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6}); 2677 * 2678 * </pre><div id="JXGe2948257-8835-4276-9164-8acccb48e8d4" class="jxgbox" style="width: 300px; height: 300px;"></div> 2679 * <script type="text/javascript"> 2680 * (function() { 2681 * var board = JXG.JSXGraph.initBoard('JXGe2948257-8835-4276-9164-8acccb48e8d4', 2682 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2683 * var f = board.create('functiongraph', ['cos(x)']); 2684 * var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1}); 2685 * var circ = board.create('circle', [[0,0], 4]); 2686 * var clip = board.create('curveintersection', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6}); 2687 * 2688 * })(); 2689 * 2690 * </script><pre> 2691 * 2692 */ 2693 JXG.createCurveIntersection = function (board, parents, attributes) { 2694 var c; 2695 2696 if (parents.length !== 2) { 2697 throw new Error( 2698 "JSXGraph: Can't create curve intersection with given parent'" + 2699 "\nPossible parent types: [array, array|function]" 2700 ); 2701 } 2702 2703 c = board.create("curve", [[], []], attributes); 2704 /** 2705 * @ignore 2706 */ 2707 c.updateDataArray = function () { 2708 var a = JXG.Math.Clip.intersection(parents[0], parents[1], this.board); 2709 this.dataX = a[0]; 2710 this.dataY = a[1]; 2711 }; 2712 return c; 2713 }; 2714 2715 /** 2716 * @class Union of two closed path elements. The elements may be of type curve, circle, polygon, inequality. 2717 * If one element is a curve, it has to be closed. 2718 * The resulting element is of type curve. 2719 * @pseudo 2720 * @description 2721 * @name CurveUnion 2722 * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve1 First element defining the union 2723 * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve2 Second element defining the union 2724 * @augments JXG.Curve 2725 * @constructor 2726 * @type JXG.Curve 2727 * 2728 * @example 2729 * var f = board.create('functiongraph', ['cos(x)']); 2730 * var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1}); 2731 * var circ = board.create('circle', [[0,0], 4]); 2732 * var clip = board.create('curveunion', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6}); 2733 * 2734 * </pre><div id="JXGe2948257-8835-4276-9164-8acccb48e8d4" class="jxgbox" style="width: 300px; height: 300px;"></div> 2735 * <script type="text/javascript"> 2736 * (function() { 2737 * var board = JXG.JSXGraph.initBoard('JXGe2948257-8835-4276-9164-8acccb48e8d4', 2738 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2739 * var f = board.create('functiongraph', ['cos(x)']); 2740 * var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1}); 2741 * var circ = board.create('circle', [[0,0], 4]); 2742 * var clip = board.create('curveunion', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6}); 2743 * 2744 * })(); 2745 * 2746 * </script><pre> 2747 * 2748 */ 2749 JXG.createCurveUnion = function (board, parents, attributes) { 2750 var c; 2751 2752 if (parents.length !== 2) { 2753 throw new Error( 2754 "JSXGraph: Can't create curve union with given parent'" + 2755 "\nPossible parent types: [array, array|function]" 2756 ); 2757 } 2758 2759 c = board.create("curve", [[], []], attributes); 2760 /** 2761 * @ignore 2762 */ 2763 c.updateDataArray = function () { 2764 var a = JXG.Math.Clip.union(parents[0], parents[1], this.board); 2765 this.dataX = a[0]; 2766 this.dataY = a[1]; 2767 }; 2768 return c; 2769 }; 2770 2771 /** 2772 * @class Difference of two closed path elements. The elements may be of type curve, circle, polygon, inequality. 2773 * If one element is a curve, it has to be closed. 2774 * The resulting element is of type curve. 2775 * @pseudo 2776 * @description 2777 * @name CurveDifference 2778 * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve1 First element from which the second element is "subtracted" 2779 * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve2 Second element which is subtracted from the first element 2780 * @augments JXG.Curve 2781 * @constructor 2782 * @type JXG.Curve 2783 * 2784 * @example 2785 * var f = board.create('functiongraph', ['cos(x)']); 2786 * var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1}); 2787 * var circ = board.create('circle', [[0,0], 4]); 2788 * var clip = board.create('curvedifference', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6}); 2789 * 2790 * </pre><div id="JXGe2948257-8835-4276-9164-8acccb48e8d4" class="jxgbox" style="width: 300px; height: 300px;"></div> 2791 * <script type="text/javascript"> 2792 * (function() { 2793 * var board = JXG.JSXGraph.initBoard('JXGe2948257-8835-4276-9164-8acccb48e8d4', 2794 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2795 * var f = board.create('functiongraph', ['cos(x)']); 2796 * var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1}); 2797 * var circ = board.create('circle', [[0,0], 4]); 2798 * var clip = board.create('curvedifference', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6}); 2799 * 2800 * })(); 2801 * 2802 * </script><pre> 2803 * 2804 */ 2805 JXG.createCurveDifference = function (board, parents, attributes) { 2806 var c; 2807 2808 if (parents.length !== 2) { 2809 throw new Error( 2810 "JSXGraph: Can't create curve difference with given parent'" + 2811 "\nPossible parent types: [array, array|function]" 2812 ); 2813 } 2814 2815 c = board.create("curve", [[], []], attributes); 2816 /** 2817 * @ignore 2818 */ 2819 c.updateDataArray = function () { 2820 var a = JXG.Math.Clip.difference(parents[0], parents[1], this.board); 2821 this.dataX = a[0]; 2822 this.dataY = a[1]; 2823 }; 2824 return c; 2825 }; 2826 2827 JXG.registerElement("curvedifference", JXG.createCurveDifference); 2828 JXG.registerElement("curveintersection", JXG.createCurveIntersection); 2829 JXG.registerElement("curveunion", JXG.createCurveUnion); 2830 2831 /** 2832 * @class Box plot curve. The direction of the box plot can be either vertical or horizontal which 2833 * is controlled by the attribute "dir". 2834 * @pseudo 2835 * @description 2836 * @name Boxplot 2837 * @param {Array} quantiles Array conatining at least five quantiles. The elements can be of type number, function or string. 2838 * @param {Number|Function} axis Axis position of the box plot 2839 * @param {Number|Function} width Width of the rectangle part of the box plot. The width of the first and 4th quantile 2840 * is relative to this width and can be controlled by the attribute "smallWidth". 2841 * @augments JXG.Curve 2842 * @constructor 2843 * @type JXG.Curve 2844 * 2845 * @example 2846 * var Q = [ -1, 2, 3, 3.5, 5 ]; 2847 * 2848 * var b = board.create('boxplot', [Q, 2, 4], {strokeWidth: 3}); 2849 * 2850 * </pre><div id="JXG13eb23a1-a641-41a2-be11-8e03e400a947" class="jxgbox" style="width: 300px; height: 300px;"></div> 2851 * <script type="text/javascript"> 2852 * (function() { 2853 * var board = JXG.JSXGraph.initBoard('JXG13eb23a1-a641-41a2-be11-8e03e400a947', 2854 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2855 * var Q = [ -1, 2, 3, 3.5, 5 ]; 2856 * var b = board.create('boxplot', [Q, 2, 4], {strokeWidth: 3}); 2857 * 2858 * })(); 2859 * 2860 * </script><pre> 2861 * 2862 * @example 2863 * var Q = [ -1, 2, 3, 3.5, 5 ]; 2864 * var b = board.create('boxplot', [Q, 3, 4], {dir: 'horizontal', smallWidth: 0.25, color:'red'}); 2865 * 2866 * </pre><div id="JXG0deb9cb2-84bc-470d-a6db-8be9a5694813" class="jxgbox" style="width: 300px; height: 300px;"></div> 2867 * <script type="text/javascript"> 2868 * (function() { 2869 * var board = JXG.JSXGraph.initBoard('JXG0deb9cb2-84bc-470d-a6db-8be9a5694813', 2870 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2871 * var Q = [ -1, 2, 3, 3.5, 5 ]; 2872 * var b = board.create('boxplot', [Q, 3, 4], {dir: 'horizontal', smallWidth: 0.25, color:'red'}); 2873 * 2874 * })(); 2875 * 2876 * </script><pre> 2877 * 2878 * @example 2879 * 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]; 2880 * var Q = []; 2881 * 2882 * Q[0] = JXG.Math.Statistics.min(data); 2883 * Q = Q.concat(JXG.Math.Statistics.percentile(data, [25, 50, 75])); 2884 * Q[4] = JXG.Math.Statistics.max(data); 2885 * 2886 * var b = board.create('boxplot', [Q, 0, 3]); 2887 * 2888 * </pre><div id="JXGef079e76-ae99-41e4-af29-1d07d83bf85a" class="jxgbox" style="width: 300px; height: 300px;"></div> 2889 * <script type="text/javascript"> 2890 * (function() { 2891 * var board = JXG.JSXGraph.initBoard('JXGef079e76-ae99-41e4-af29-1d07d83bf85a', 2892 * {boundingbox: [-5,90,5,30], axis: true, showcopyright: false, shownavigation: false}); 2893 * 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]; 2894 * var Q = []; 2895 * 2896 * Q[0] = JXG.Math.Statistics.min(data); 2897 * Q = Q.concat(JXG.Math.Statistics.percentile(data, [25, 50, 75])); 2898 * Q[4] = JXG.Math.Statistics.max(data); 2899 * 2900 * var b = board.create('boxplot', [Q, 0, 3]); 2901 * 2902 * })(); 2903 * 2904 * </script><pre> 2905 * 2906 * @example 2907 * var mi = board.create('glider', [0, -1, board.defaultAxes.y]); 2908 * var ma = board.create('glider', [0, 5, board.defaultAxes.y]); 2909 * var Q = [function() { return mi.Y(); }, 2, 3, 3.5, function() { return ma.Y(); }]; 2910 * 2911 * var b = board.create('boxplot', [Q, 0, 2]); 2912 * 2913 * </pre><div id="JXG3b3225da-52f0-42fe-8396-be9016bf289b" class="jxgbox" style="width: 300px; height: 300px;"></div> 2914 * <script type="text/javascript"> 2915 * (function() { 2916 * var board = JXG.JSXGraph.initBoard('JXG3b3225da-52f0-42fe-8396-be9016bf289b', 2917 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2918 * var mi = board.create('glider', [0, -1, board.defaultAxes.y]); 2919 * var ma = board.create('glider', [0, 5, board.defaultAxes.y]); 2920 * var Q = [function() { return mi.Y(); }, 2, 3, 3.5, function() { return ma.Y(); }]; 2921 * 2922 * var b = board.create('boxplot', [Q, 0, 2]); 2923 * 2924 * })(); 2925 * 2926 * </script><pre> 2927 * 2928 */ 2929 JXG.createBoxPlot = function (board, parents, attributes) { 2930 var box, i, len, w2, 2931 attr = Type.copyAttributes(attributes, board.options, "boxplot"); 2932 2933 if (parents.length !== 3) { 2934 throw new Error( 2935 "JSXGraph: Can't create box plot with given parent'" + 2936 "\nPossible parent types: [array, number|function, number|function] containing quantiles, axis, width" 2937 ); 2938 } 2939 if (parents[0].length < 5) { 2940 throw new Error( 2941 "JSXGraph: Can't create box plot with given parent[0]'" + 2942 "\nparent[0] has to conatin at least 5 quantiles." 2943 ); 2944 } 2945 box = board.create("curve", [[], []], attr); 2946 2947 len = parents[0].length; // Quantiles 2948 box.Q = []; 2949 for (i = 0; i < len; i++) { 2950 box.Q[i] = Type.createFunction(parents[0][i], board, null, true); 2951 } 2952 box.x = Type.createFunction(parents[1], board, null, true); 2953 box.w = Type.createFunction(parents[2], board, null, true); 2954 2955 box.updateDataArray = function () { 2956 var v1, v2, l1, l2, r1, r2, w2, dir, x; 2957 2958 w2 = Type.evaluate(this.visProp.smallwidth); 2959 dir = Type.evaluate(this.visProp.dir); 2960 x = this.x(); 2961 l1 = x - this.w() * 0.5; 2962 l2 = x - this.w() * 0.5 * w2; 2963 r1 = x + this.w() * 0.5; 2964 r2 = x + this.w() * 0.5 * w2; 2965 v1 = [x, l2, r2, x, x, l1, l1, r1, r1, x, NaN, l1, r1, NaN, x, x, l2, r2, x]; 2966 v2 = [ 2967 this.Q[0](), 2968 this.Q[0](), 2969 this.Q[0](), 2970 this.Q[0](), 2971 this.Q[1](), 2972 this.Q[1](), 2973 this.Q[3](), 2974 this.Q[3](), 2975 this.Q[1](), 2976 this.Q[1](), 2977 NaN, 2978 this.Q[2](), 2979 this.Q[2](), 2980 NaN, 2981 this.Q[3](), 2982 this.Q[4](), 2983 this.Q[4](), 2984 this.Q[4](), 2985 this.Q[4]() 2986 ]; 2987 if (dir === "vertical") { 2988 this.dataX = v1; 2989 this.dataY = v2; 2990 } else { 2991 this.dataX = v2; 2992 this.dataY = v1; 2993 } 2994 }; 2995 2996 box.addParentsFromJCFunctions([box.Q, box.x, box.w]); 2997 2998 return box; 2999 }; 3000 3001 JXG.registerElement("boxplot", JXG.createBoxPlot); 3002 3003 export default JXG.Curve; 3004 3005 // export default { 3006 // Curve: JXG.Curve, 3007 // createCardinalSpline: JXG.createCardinalSpline, 3008 // createCurve: JXG.createCurve, 3009 // createCurveDifference: JXG.createCurveDifference, 3010 // createCurveIntersection: JXG.createCurveIntersection, 3011 // createCurveUnion: JXG.createCurveUnion, 3012 // createDerivative: JXG.createDerivative, 3013 // createFunctiongraph: JXG.createFunctiongraph, 3014 // createMetapostSpline: JXG.createMetapostSpline, 3015 // createPlot: JXG.createFunctiongraph, 3016 // createSpline: JXG.createSpline, 3017 // createRiemannsum: JXG.createRiemannsum, 3018 // createStepfunction: JXG.createStepfunction, 3019 // createTracecurve: JXG.createTracecurve 3020 // }; 3021 3022 // const Curve = JXG.Curve; 3023 // export { Curve as default, Curve}; 3024