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