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