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