1 /* 2 Copyright 2008-2013 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/statistics', 'math/numerics', 57 'math/geometry', 'parser/geonext', 'utils/type', 'base/transformation' 58 ], function (JXG, Const, Coords, GeometryElement, Mat, Statistics, Numerics, Geometry, GeonextParser, Type, Transform) { 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 = this.visProp.numberpointshigh; 83 84 this.bezierDegree = 1; 85 86 this.dataX = null; 87 this.dataY = null; 88 89 if (Type.exists(parents[0])) { 90 this.varname = parents[0]; 91 } else { 92 this.varname = 'x'; 93 } 94 95 // function graphs: "x" 96 this.xterm = parents[1]; 97 // function graphs: e.g. "x^2" 98 this.yterm = parents[2]; 99 100 // Converts GEONExT syntax into JavaScript syntax 101 this.generateTerm(this.varname, this.xterm, this.yterm, parents[3], parents[4]); 102 // First evaluation of the curve 103 this.updateCurve(); 104 105 this.id = this.board.setId(this, 'G'); 106 this.board.renderer.drawCurve(this); 107 108 this.board.finalizeAdding(this); 109 110 this.createGradient(); 111 this.elType = 'curve'; 112 this.createLabel(); 113 114 if (typeof this.xterm === 'string') { 115 this.notifyParents(this.xterm); 116 } 117 if (typeof this.yterm === 'string') { 118 this.notifyParents(this.yterm); 119 } 120 121 this.methodMap = Type.deepCopy(this.methodMap, { 122 generateTerm: 'generateTerm', 123 setTerm: 'generateTerm' 124 }); 125 }; 126 127 JXG.Curve.prototype = new GeometryElement(); 128 129 130 JXG.extend(JXG.Curve.prototype, /** @lends JXG.Curve.prototype */ { 131 132 /** 133 * Gives the default value of the left bound for the curve. 134 * May be overwritten in {@link JXG.Curve#generateTerm}. 135 * @returns {Number} Left bound for the curve. 136 */ 137 minX: function () { 138 var leftCoords; 139 140 if (this.visProp.curvetype === 'polar') { 141 return 0; 142 } 143 144 leftCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board, false); 145 return leftCoords.usrCoords[1]; 146 }, 147 148 /** 149 * Gives the default value of the right bound for the curve. 150 * May be overwritten in {@link JXG.Curve#generateTerm}. 151 * @returns {Number} Right bound for the curve. 152 */ 153 maxX: function () { 154 var rightCoords; 155 156 if (this.visProp.curvetype === 'polar') { 157 return 2 * Math.PI; 158 } 159 rightCoords = new Coords(Const.COORDS_BY_SCREEN, [this.board.canvasWidth, 0], this.board, false); 160 161 return rightCoords.usrCoords[1]; 162 }, 163 164 /** 165 * Treat the curve as curve with homogeneous coordinates. 166 * @param {Number} t A number between 0.0 and 1.0. 167 * @return {Number} Always 1.0 168 */ 169 Z: function (t) { 170 return 1; 171 }, 172 173 /** 174 * Checks whether (x,y) is near the curve. 175 * @param {Number} x Coordinate in x direction, screen coordinates. 176 * @param {Number} y Coordinate in y direction, screen coordinates. 177 * @param {Number} start Optional start index for search on data plots. 178 * @return {Boolean} True if (x,y) is near the curve, False otherwise. 179 */ 180 hasPoint: function (x, y, start) { 181 var t, checkPoint, len, invMat, c, 182 i, tX, tY, res, 183 steps = this.visProp.numberpointslow, 184 d = (this.maxX() - this.minX()) / steps, 185 prec = this.board.options.precision.hasPoint / this.board.unitX, 186 dist = Infinity, 187 suspendUpdate = true; 188 189 checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board, false); 190 x = checkPoint.usrCoords[1]; 191 y = checkPoint.usrCoords[2]; 192 193 if (this.transformations.length > 0) { 194 /** 195 * Transform the mouse/touch coordinates 196 * back to the original position of the curve. 197 */ 198 this.updateTransformMatrix(); 199 invMat = Mat.inverse(this.transformMat); 200 c = Mat.matVecMult(invMat, [1, x, y]); 201 x = c[1]; 202 y = c[2]; 203 } 204 205 if (this.visProp.curvetype === 'parameter' || 206 this.visProp.curvetype === 'polar' || 207 this.visProp.curvetype === 'functiongraph') { 208 209 prec = prec * prec; 210 211 // Brute force search for a point on the curve close to the mouse pointer 212 for (i = 0, t = this.minX(); i < steps; i++) { 213 tX = this.X(t, suspendUpdate); 214 tY = this.Y(t, suspendUpdate); 215 216 dist = (x - tX) * (x - tX) + (y - tY) * (y - tY); 217 218 if (dist < prec) { 219 return true; 220 } 221 222 t += d; 223 } 224 } else if (this.visProp.curvetype === 'plot') { 225 if (!Type.exists(start) || start < 0) { 226 start = 0; 227 } 228 229 len = this.numberPoints; 230 for (i = start; i < len - 1; i++) { 231 232 if (this.bezierDegree === 3) { 233 res = Geometry.projectCoordsToBeziersegment([1, x, y], this, i); 234 //i += 2; 235 } else { 236 res = Geometry.projectCoordsToSegment( 237 [1, x, y], 238 [1, this.X(i), this.Y(i)], 239 [1, this.X(i + 1), this.Y(i + 1)] 240 ); 241 } 242 243 if (res[1] >= 0 && res[1] <= 1 && 244 Geometry.distance([1, x, y], res[0], 3) <= prec) { 245 return true; 246 } 247 } 248 return false; 249 } 250 return (dist < prec); 251 }, 252 253 /** 254 * Allocate points in the Coords array this.points 255 */ 256 allocatePoints: function () { 257 var i, len; 258 259 len = this.numberPoints; 260 261 if (this.points.length < this.numberPoints) { 262 for (i = this.points.length; i < len; i++) { 263 this.points[i] = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false); 264 } 265 } 266 }, 267 268 /** 269 * Computes for equidistant points on the x-axis the values of the function 270 * @returns {JXG.Curve} Reference to the curve object. 271 * @see JXG.Curve#updateCurve 272 */ 273 update: function () { 274 if (this.needsUpdate) { 275 if (this.visProp.trace) { 276 this.cloneToBackground(true); 277 } 278 this.updateCurve(); 279 } 280 281 return this; 282 }, 283 284 /** 285 * Updates the visual contents of the curve. 286 * @returns {JXG.Curve} Reference to the curve object. 287 */ 288 updateRenderer: function () { 289 var wasReal; 290 291 if (this.needsUpdate && this.visProp.visible) { 292 wasReal = this.isReal; 293 294 this.checkReal(); 295 296 if (this.isReal || wasReal) { 297 this.board.renderer.updateCurve(this); 298 } 299 300 if (this.isReal) { 301 if (wasReal !== this.isReal) { 302 this.board.renderer.show(this); 303 if (this.hasLabel && this.label.visProp.visible) { 304 this.board.renderer.show(this.label.content); 305 } 306 } 307 } else { 308 if (wasReal !== this.isReal) { 309 this.board.renderer.hide(this); 310 if (this.hasLabel && this.label.visProp.visible) { 311 this.board.renderer.hide(this.label); 312 } 313 } 314 } 315 316 // Update the label if visible. 317 if (this.hasLabel && Type.exists(this.label.visProp) && this.label.visProp.visible) { 318 this.label.update(); 319 this.board.renderer.updateText(this.label); 320 } 321 } 322 return this; 323 }, 324 325 /** 326 * For dynamic dataplots updateCurve can be used to compute new entries 327 * for the arrays {@link JXG.Curve#dataX} and {@link JXG.Curve#dataY}. It 328 * is used in {@link JXG.Curve#updateCurve}. Default is an empty method, can 329 * be overwritten by the user. 330 */ 331 updateDataArray: function () { 332 // this used to return this, but we shouldn't rely on the user to implement it. 333 }, 334 335 /** 336 * Computes for equidistant points on the x-axis the values 337 * of the function. 338 * If the mousemove event triggers this update, we use only few 339 * points. Otherwise, e.g. on mouseup, many points are used. 340 * @see JXG.Curve#update 341 * @returns {JXG.Curve} Reference to the curve object. 342 */ 343 updateCurve: function () { 344 var len, mi, ma, x, y, i, 345 suspendUpdate = false; 346 347 this.updateTransformMatrix(); 348 this.updateDataArray(); 349 mi = this.minX(); 350 ma = this.maxX(); 351 352 // Discrete data points 353 // x-coordinates are in an array 354 if (Type.exists(this.dataX)) { 355 this.numberPoints = this.dataX.length; 356 len = this.numberPoints; 357 358 // It is possible, that the array length has increased. 359 this.allocatePoints(); 360 361 for (i = 0; i < len; i++) { 362 x = i; 363 364 // y-coordinates are in an array 365 if (Type.exists(this.dataY)) { 366 y = i; 367 // The last parameter prevents rounding in usr2screen(). 368 this.points[i].setCoordinates(Const.COORDS_BY_USER, [this.dataX[i], this.dataY[i]], false); 369 } else { 370 // discrete x data, continuous y data 371 y = this.X(x); 372 // The last parameter prevents rounding in usr2screen(). 373 this.points[i].setCoordinates(Const.COORDS_BY_USER, [this.dataX[i], this.Y(y, suspendUpdate)], false); 374 } 375 376 this.updateTransform(this.points[i]); 377 suspendUpdate = true; 378 } 379 // continuous x data 380 } else { 381 if (this.visProp.doadvancedplot) { 382 this.updateParametricCurve(mi, ma, len); 383 } else { 384 if (this.board.updateQuality === this.board.BOARD_QUALITY_HIGH) { 385 this.numberPoints = this.visProp.numberpointshigh; 386 } else { 387 this.numberPoints = this.visProp.numberpointslow; 388 } 389 390 // It is possible, that the array length has increased. 391 this.allocatePoints(); 392 this.updateParametricCurveNaive(mi, ma, this.numberPoints); 393 } 394 len = this.numberPoints; 395 396 for (i = 0; i < len; i++) { 397 this.updateTransform(this.points[i]); 398 } 399 } 400 401 return this; 402 }, 403 404 updateTransformMatrix: function () { 405 var t, c, i, 406 len = this.transformations.length; 407 408 this.transformMat = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]; 409 410 for (i = 0; i < len; i++) { 411 t = this.transformations[i]; 412 t.update(); 413 this.transformMat = Mat.matMatMult(t.matrix, this.transformMat); 414 } 415 416 return this; 417 }, 418 419 /** 420 * Check if at least one point on the curve is finite and real. 421 **/ 422 checkReal: function () { 423 var b = false, i, p, 424 len = this.numberPoints; 425 426 for (i = 0; i < len; i++) { 427 p = this.points[i].usrCoords; 428 if (!isNaN(p[1]) && !isNaN(p[2]) && Math.abs(p[0]) > Mat.eps) { 429 b = true; 430 break; 431 } 432 } 433 this.isReal = b; 434 }, 435 436 /** 437 * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#doadvancedplot} is <tt>false</tt>. 438 * @param {Number} mi Left bound of curve 439 * @param {Number} ma Right bound of curve 440 * @param {Number} len Number of data points 441 * @returns {JXG.Curve} Reference to the curve object. 442 */ 443 updateParametricCurveNaive: function (mi, ma, len) { 444 var i, t, 445 suspendUpdate = false, 446 stepSize = (ma - mi) / len; 447 448 for (i = 0; i < len; i++) { 449 t = mi + i * stepSize; 450 // The last parameter prevents rounding in usr2screen(). 451 this.points[i].setCoordinates(Const.COORDS_BY_USER, [this.X(t, suspendUpdate), this.Y(t, suspendUpdate)], false); 452 suspendUpdate = true; 453 } 454 return this; 455 }, 456 457 /** 458 * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#doadvancedplot} is <tt>true</tt>. 459 * @param {Number} mi Left bound of curve 460 * @param {Number} ma Right bound of curve 461 * @returns {JXG.Curve} Reference to the curve object. 462 */ 463 updateParametricCurve: function (mi, ma) { 464 var i, t, t0, d, 465 x, y, x0, y0, top, depth, 466 MAX_DEPTH, MAX_XDIST, MAX_YDIST, 467 suspendUpdate = false, 468 po = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false), 469 dyadicStack = [], 470 depthStack = [], 471 pointStack = [], 472 divisors = [], 473 distOK = false, 474 j = 0, 475 distFromLine = function (p1, p2, p0) { 476 var lbda, d, 477 x0 = p0[1] - p1[1], 478 y0 = p0[2] - p1[2], 479 x1 = p2[0] - p1[1], 480 y1 = p2[1] - p1[2], 481 den = x1 * x1 + y1 * y1; 482 483 if (den >= Mat.eps) { 484 lbda = (x0 * x1 + y0 * y1) / den; 485 if (lbda > 0) { 486 if (lbda <= 1) { 487 x0 -= lbda * x1; 488 y0 -= lbda * y1; 489 // lbda = 1.0; 490 } else { 491 x0 -= x1; 492 y0 -= y1; 493 } 494 } 495 } 496 d = x0 * x0 + y0 * y0; 497 return Math.sqrt(d); 498 }; 499 500 if (this.board.updateQuality === this.board.BOARD_QUALITY_LOW) { 501 MAX_DEPTH = 15; 502 MAX_XDIST = 10; 503 MAX_YDIST = 10; 504 } else { 505 MAX_DEPTH = 21; 506 MAX_XDIST = 0.7; 507 MAX_YDIST = 0.7; 508 } 509 510 divisors[0] = ma - mi; 511 for (i = 1; i < MAX_DEPTH; i++) { 512 divisors[i] = divisors[i - 1] * 0.5; 513 } 514 515 i = 1; 516 dyadicStack[0] = 1; 517 depthStack[0] = 0; 518 519 t = mi; 520 po.setCoordinates(Const.COORDS_BY_USER, [this.X(t, suspendUpdate), this.Y(t, suspendUpdate)], false); 521 522 // Now, there was a first call to the functions defining the curve. 523 // Defining elements like sliders have been evaluated. 524 // Therefore, we can set suspendUpdate to false, so that these defining elements 525 // need not be evaluated anymore for the rest of the plotting. 526 suspendUpdate = true; 527 x0 = po.scrCoords[1]; 528 y0 = po.scrCoords[2]; 529 t0 = t; 530 531 t = ma; 532 po.setCoordinates(Const.COORDS_BY_USER, [this.X(t, suspendUpdate), this.Y(t, suspendUpdate)], false); 533 x = po.scrCoords[1]; 534 y = po.scrCoords[2]; 535 536 pointStack[0] = [x, y]; 537 538 top = 1; 539 depth = 0; 540 541 this.points = []; 542 this.points[j++] = new Coords(Const.COORDS_BY_SCREEN, [x0, y0], this.board, false); 543 544 do { 545 distOK = this.isDistOK(x - x0, y - y0, MAX_XDIST, MAX_YDIST) || this.isSegmentOutside(x0, y0, x, y); 546 while (depth < MAX_DEPTH && (!distOK || depth < 6) && (depth <= 7 || this.isSegmentDefined(x0, y0, x, y))) { 547 // We jump out of the loop if 548 // * depth>=MAX_DEPTH or 549 // * (depth>=6 and distOK) or 550 // * (depth>7 and segment is not defined) 551 552 dyadicStack[top] = i; 553 depthStack[top] = depth; 554 pointStack[top] = [x, y]; 555 top += 1; 556 557 i = 2 * i - 1; 558 // Here, depth is increased and may reach MAX_DEPTH 559 depth++; 560 // In that case, t is undefined and we will see a jump in the curve. 561 t = mi + i * divisors[depth]; 562 563 po.setCoordinates(Const.COORDS_BY_USER, [this.X(t, suspendUpdate), this.Y(t, suspendUpdate)], false, true); 564 x = po.scrCoords[1]; 565 y = po.scrCoords[2]; 566 distOK = this.isDistOK(x - x0, y - y0, MAX_XDIST, MAX_YDIST) || this.isSegmentOutside(x0, y0, x, y); 567 } 568 569 if (j > 1) { 570 d = distFromLine(this.points[j - 2].scrCoords, [x, y], this.points[j - 1].scrCoords); 571 if (d < 0.015) { 572 j -= 1; 573 } 574 } 575 576 this.points[j] = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board, false); 577 j += 1; 578 579 x0 = x; 580 y0 = y; 581 t0 = t; 582 583 top -= 1; 584 x = pointStack[top][0]; 585 y = pointStack[top][1]; 586 depth = depthStack[top] + 1; 587 i = dyadicStack[top] * 2; 588 589 } while (top > 0 && j < 500000); 590 591 this.numberPoints = this.points.length; 592 593 return this; 594 }, 595 596 /** 597 * Crude and cheap test if the segment defined by the two points <tt>(x0, y0)</tt> and <tt>(x1, y1)</tt> is 598 * outside the viewport of the board. All parameters have to be given in screen coordinates. 599 * @param {Number} x0 600 * @param {Number} y0 601 * @param {Number} x1 602 * @param {Number} y1 603 * @returns {Boolean} <tt>true</tt> if the given segment is outside the visible area. 604 */ 605 isSegmentOutside: function (x0, y0, x1, y1) { 606 return (y0 < 0 && y1 < 0) || (y0 > this.board.canvasHeight && y1 > this.board.canvasHeight) || 607 (x0 < 0 && x1 < 0) || (x0 > this.board.canvasWidth && x1 > this.board.canvasWidth); 608 }, 609 610 /** 611 * Compares the absolute value of <tt>dx</tt> with <tt>MAXX</tt> and the absolute value of <tt>dy</tt> 612 * with <tt>MAXY</tt>. 613 * @param {Number} dx 614 * @param {Number} dy 615 * @param {Number} MAXX 616 * @param {Number} MAXY 617 * @returns {Boolean} <tt>true</tt>, if <tt>|dx| < MAXX</tt> and <tt>|dy| < MAXY</tt>. 618 */ 619 isDistOK: function (dx, dy, MAXX, MAXY) { 620 return (Math.abs(dx) < MAXX && Math.abs(dy) < MAXY) && !isNaN(dx + dy); 621 }, 622 623 isSegmentDefined: function (x0, y0, x1, y1) { 624 return !(isNaN(x0 + y0) && isNaN(x1 + y1)); 625 }, 626 627 /** 628 * Applies the transformations of the curve to the given point <tt>p</tt>. 629 * Before using it, {@link JXG.Curve#updateTransformMatrix} has to be called. 630 * @param {JXG.Point} p 631 * @returns {JXG.Point} The given point. 632 */ 633 updateTransform: function (p) { 634 var c, 635 len = this.transformations.length; 636 637 if (len > 0) { 638 c = Mat.matVecMult(this.transformMat, p.usrCoords); 639 p.setPosition(Const.COORDS_BY_USER, [c[1], c[2]]); 640 } 641 642 return p; 643 }, 644 645 /** 646 * Add transformations to this curve. 647 * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of {@link JXG.Transformation}s. 648 * @returns {JXG.Curve} Reference to the curve object. 649 */ 650 addTransform: function (transform) { 651 var i, 652 list = Type.isArray(transform) ? transform : [transform], 653 len = list.length; 654 655 for (i = 0; i < len; i++) { 656 this.transformations.push(list[i]); 657 } 658 659 return this; 660 }, 661 662 /** 663 * Translates the object by <tt>(x, y)</tt>. 664 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 665 * @param {Array} coords array of translation vector. 666 * @returns {JXG.Curve} Reference to the curve object. 667 */ 668 setPosition: function (method, coords) { 669 var t, obj, i, 670 len = 0; 671 672 if (Type.exists(this.parents)) { 673 len = this.parents.length; 674 } 675 676 for (i = 0; i < len; i++) { 677 obj = this.board.select(this.parents[i]); 678 679 if (!obj.draggable()) { 680 return this; 681 } 682 } 683 684 // We distinguish two cases: 685 // 1) curves which depend on free elements, i.e. arcs and sectors 686 // 2) other curves 687 // 688 // In the first case we simply transform the parents elements 689 // In the second case we add a transform to the curve. 690 // 691 coords = new Coords(method, coords, this.board, false); 692 t = this.board.create('transform', coords.usrCoords.slice(1), {type: 'translate'}); 693 694 if (len > 0) { 695 for (i = 0; i < len; i++) { 696 obj = this.board.select(this.parents[i]); 697 t.applyOnce(obj); 698 } 699 } else { 700 if (this.transformations.length > 0 && 701 this.transformations[this.transformations.length - 1].isNumericMatrix) { 702 this.transformations[this.transformations.length - 1].melt(t); 703 } else { 704 this.addTransform(t); 705 } 706 } 707 return this; 708 }, 709 710 /** 711 * Moves the cuvre by the difference of two coordinates. 712 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 713 * @param {Array} coords coordinates in screen/user units 714 * @param {Array} oldcoords previous coordinates in screen/user units 715 * @returns {JXG.Curve} this element 716 */ 717 setPositionDirectly: function (method, coords, oldcoords) { 718 var c = new Coords(method, coords, this.board, false), 719 oldc = new Coords(method, oldcoords, this.board, false), 720 dc = Statistics.subtract(c.usrCoords, oldc.usrCoords); 721 722 this.setPosition(Const.COORDS_BY_USER, dc); 723 724 return this; 725 }, 726 727 /** 728 * Generate the method curve.X() in case curve.dataX is an array 729 * and generate the method curve.Y() in case curve.dataY is an array. 730 * @private 731 * @param {String} which Either 'X' or 'Y' 732 * @returns {function} 733 **/ 734 interpolationFunctionFromArray: function (which) { 735 var data = 'data' + which; 736 737 return function (t, suspendedUpdate) { 738 var i, j, f1, f2, z, t0, t1, 739 arr = this[data], 740 len = arr.length, 741 f = []; 742 743 if (isNaN(t)) { 744 return NaN; 745 } 746 747 if (t < 0) { 748 if (Type.isFunction(arr[0])) { 749 return arr[0](); 750 } 751 752 return arr[0]; 753 } 754 755 if (this.bezierDegree === 3) { 756 len /= 3; 757 if (t >= len) { 758 if (Type.isFunction(arr[arr.length - 1])) { 759 return arr[arr.length - 1](); 760 } 761 762 return arr[arr.length - 1]; 763 } 764 765 i = Math.floor(t) * 3; 766 t0 = t % 1; 767 t1 = 1 - t0; 768 769 for (j = 0; j < 4; j++) { 770 if (Type.isFunction(arr[i + j])) { 771 f[j] = arr[i + j](); 772 } else { 773 f[j] = arr[i + j]; 774 } 775 } 776 777 return t1 * t1 * (t1 * f[0] + 3 * t0 * f[1]) + (3 * t1 * f[2] + t0 * f[3]) * t0 * t0; 778 } 779 780 if (t > len - 2) { 781 i = len - 2; 782 } else { 783 i = parseInt(Math.floor(t), 10); 784 } 785 786 if (i === t) { 787 if (Type.isFunction(arr[i])) { 788 return arr[i](); 789 } 790 return arr[i]; 791 } 792 793 for (j = 0; j < 2; j++) { 794 if (Type.isFunction(arr[i + j])) { 795 f[j] = arr[i + j](); 796 } else { 797 f[j] = arr[i + j]; 798 } 799 } 800 return f[0] + (f[1] - f[0]) * (t - i); 801 }; 802 }, 803 /** 804 * Converts the GEONExT syntax of the defining function term into JavaScript. 805 * New methods X() and Y() for the Curve object are generated, further 806 * new methods for minX() and maxX(). 807 * @see JXG.GeonextParser.geonext2JS. 808 */ 809 generateTerm: function (varname, xterm, yterm, mi, ma) { 810 var fx, fy; 811 812 // Generate the methods X() and Y() 813 if (Type.isArray(xterm)) { 814 // Discrete data 815 this.dataX = xterm; 816 817 this.numberPoints = this.dataX.length; 818 this.X = this.interpolationFunctionFromArray('X'); 819 this.visProp.curvetype = 'plot'; 820 this.isDraggable = true; 821 } else { 822 // Continuous data 823 this.X = Type.createFunction(xterm, this.board, varname); 824 if (Type.isString(xterm)) { 825 this.visProp.curvetype = 'functiongraph'; 826 } else if (Type.isFunction(xterm) || Type.isNumber(xterm)) { 827 this.visProp.curvetype = 'parameter'; 828 } 829 830 this.isDraggable = true; 831 } 832 833 if (Type.isArray(yterm)) { 834 this.dataY = yterm; 835 this.Y = this.interpolationFunctionFromArray('Y'); 836 } else { 837 this.Y = Type.createFunction(yterm, this.board, varname); 838 } 839 840 /** 841 * Polar form 842 * Input data is function xterm() and offset coordinates yterm 843 */ 844 if (Type.isFunction(xterm) && Type.isArray(yterm)) { 845 // Xoffset, Yoffset 846 fx = Type.createFunction(yterm[0], this.board, ''); 847 fy = Type.createFunction(yterm[1], this.board, ''); 848 849 this.X = function (phi) { 850 return xterm(phi) * Math.cos(phi) + fx(); 851 }; 852 853 this.Y = function (phi) { 854 return xterm(phi) * Math.sin(phi) + fy(); 855 }; 856 857 this.visProp.curvetype = 'polar'; 858 } 859 860 // Set the bounds lower bound 861 if (Type.exists(mi)) { 862 this.minX = Type.createFunction(mi, this.board, ''); 863 } 864 if (Type.exists(ma)) { 865 this.maxX = Type.createFunction(ma, this.board, ''); 866 } 867 }, 868 869 /** 870 * Finds dependencies in a given term and notifies the parents by adding the 871 * dependent object to the found objects child elements. 872 * @param {String} contentStr String containing dependencies for the given object. 873 */ 874 notifyParents: function (contentStr) { 875 GeonextParser.findDependencies(this, contentStr, this.board); 876 }, 877 878 // documented in geometry element 879 getLabelAnchor: function () { 880 var c, x, y, 881 ax = 0.05 * this.board.canvasWidth, 882 ay = 0.05 * this.board.canvasHeight, 883 bx = 0.95 * this.board.canvasWidth, 884 by = 0.95 * this.board.canvasHeight; 885 886 switch (this.visProp.label.position) { 887 case 'ulft': 888 x = ax; 889 y = ay; 890 break; 891 case 'llft': 892 x = ax; 893 y = by; 894 break; 895 case 'rt': 896 x = bx; 897 y = 0.5 * by; 898 break; 899 case 'lrt': 900 x = bx; 901 y = by; 902 break; 903 case 'urt': 904 x = bx; 905 y = ay; 906 break; 907 case 'top': 908 x = 0.5 * bx; 909 y = ay; 910 break; 911 case 'bot': 912 x = 0.5 * bx; 913 y = by; 914 break; 915 default: 916 // includes case 'lft' 917 x = ax; 918 y = 0.5 * by; 919 } 920 921 c = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board, false); 922 return Geometry.projectCoordsToCurve(c.usrCoords[1], c.usrCoords[2], 0, this, this.board)[0]; 923 }, 924 925 // documented in geometry element 926 cloneToBackground: function () { 927 var er, 928 copy = { 929 id: this.id + 'T' + this.numTraces, 930 elementClass: Const.OBJECT_CLASS_CURVE, 931 932 points: this.points.slice(0), 933 bezierDegree: this.bezierDegree, 934 numberPoints: this.numberPoints, 935 board: this.board, 936 visProp: Type.deepCopy(this.visProp, this.visProp.traceattributes, true) 937 }; 938 939 copy.visProp.layer = this.board.options.layer.trace; 940 copy.visProp.curvetype = this.visProp.curvetype; 941 this.numTraces++; 942 943 Type.clearVisPropOld(copy); 944 945 er = this.board.renderer.enhancedRendering; 946 this.board.renderer.enhancedRendering = true; 947 this.board.renderer.drawCurve(copy); 948 this.board.renderer.enhancedRendering = er; 949 this.traces[copy.id] = copy.rendNode; 950 951 return this; 952 }, 953 954 // already documented in GeometryElement 955 bounds: function () { 956 var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity, 957 l = this.points.length, i; 958 959 for (i = 0; i < l; i++) { 960 if (minX > this.points[i].usrCoords[1]) { 961 minX = this.points[i].usrCoords[1]; 962 } 963 964 if (maxX < this.points[i].usrCoords[1]) { 965 maxX = this.points[i].usrCoords[1]; 966 } 967 968 if (minY > this.points[i].usrCoords[2]) { 969 minY = this.points[i].usrCoords[2]; 970 } 971 972 if (maxY < this.points[i].usrCoords[2]) { 973 maxY = this.points[i].usrCoords[2]; 974 } 975 } 976 977 return [minX, maxY, maxX, minY]; 978 } 979 }); 980 981 982 /** 983 * @class This element is used to provide a constructor for curve, which is just a wrapper for element {@link Curve}. 984 * 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]. 985 * <p> 986 * The following types of curves can be plotted: 987 * <ul> 988 * <li> parametric curves: t mapsto (x(t),y(t)), where x() and y() are univariate functions. 989 * <li> polar curves: curves commonly written with polar equations like spirals and cardioids. 990 * <li> data plots: plot linbe segments through a given list of coordinates. 991 * </ul> 992 * @pseudo 993 * @description 994 * @name Curve 995 * @augments JXG.Curve 996 * @constructor 997 * @type JXG.Curve 998 * 999 * @param {function,number_function,number_function,number_function,number} x,y,a_,b_ Parent elements for Parametric Curves. 1000 * <p> 1001 * x describes the x-coordinate of the curve. It may be a function term in one variable, e.g. x(t). 1002 * In case of x being of type number, x(t) is set to a constant function. 1003 * this function at the values of the array. 1004 * </p> 1005 * <p> 1006 * y describes the y-coordinate of the curve. In case of a number, y(t) is set to the constant function 1007 * returning this number. 1008 * </p> 1009 * <p> 1010 * Further parameters are an optional number or function for the left interval border a, 1011 * and an optional number or function for the right interval border b. 1012 * </p> 1013 * <p> 1014 * Default values are a=-10 and b=10. 1015 * </p> 1016 * @param {array_array,function,number} x,y Parent elements for Data Plots. 1017 * <p> 1018 * x and y are arrays contining the x and y coordinates of the data points which are connected by 1019 * line segments. The individual entries of x and y may also be functions. 1020 * In case of x being an array the curve type is data plot, regardless of the second parameter and 1021 * if additionally the second parameter y is a function term the data plot evaluates. 1022 * </p> 1023 * @param {function_array,function,number_function,number_function,number} r,offset_,a_,b_ Parent elements for Polar Curves. 1024 * <p> 1025 * The first parameter is a function term r(phi) describing the polar curve. 1026 * </p> 1027 * <p> 1028 * The second parameter is the offset of the curve. It has to be 1029 * an array containing numbers or functions describing the offset. Default value is the origin [0,0]. 1030 * </p> 1031 * <p> 1032 * Further parameters are an optional number or function for the left interval border a, 1033 * and an optional number or function for the right interval border b. 1034 * </p> 1035 * <p> 1036 * Default values are a=-10 and b=10. 1037 * </p> 1038 * @see JXG.Curve 1039 * @example 1040 * // Parametric curve 1041 * // Create a curve of the form (t-sin(t), 1-cos(t), i.e. 1042 * // the cycloid curve. 1043 * var graph = board.create('curve', 1044 * [function(t){ return t-Math.sin(t);}, 1045 * function(t){ return 1-Math.cos(t);}, 1046 * 0, 2*Math.PI] 1047 * ); 1048 * </pre><div id="af9f818b-f3b6-4c4d-8c4c-e4a4078b726d" style="width: 300px; height: 300px;"></div> 1049 * <script type="text/javascript"> 1050 * var c1_board = JXG.JSXGraph.initBoard('af9f818b-f3b6-4c4d-8c4c-e4a4078b726d', {boundingbox: [-1, 5, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1051 * var graph1 = c1_board.create('curve', [function(t){ return t-Math.sin(t);},function(t){ return 1-Math.cos(t);},0, 2*Math.PI]); 1052 * </script><pre> 1053 * @example 1054 * // Data plots 1055 * // Connect a set of points given by coordinates with dashed line segments. 1056 * // The x- and y-coordinates of the points are given in two separate 1057 * // arrays. 1058 * var x = [0,1,2,3,4,5,6,7,8,9]; 1059 * var y = [9.2,1.3,7.2,-1.2,4.0,5.3,0.2,6.5,1.1,0.0]; 1060 * var graph = board.create('curve', [x,y], {dash:2}); 1061 * </pre><div id="7dcbb00e-b6ff-481d-b4a8-887f5d8c6a83" style="width: 300px; height: 300px;"></div> 1062 * <script type="text/javascript"> 1063 * var c3_board = JXG.JSXGraph.initBoard('7dcbb00e-b6ff-481d-b4a8-887f5d8c6a83', {boundingbox: [-1,10,10,-1], axis: true, showcopyright: false, shownavigation: false}); 1064 * var x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 1065 * var y = [9.2, 1.3, 7.2, -1.2, 4.0, 5.3, 0.2, 6.5, 1.1, 0.0]; 1066 * var graph3 = c3_board.create('curve', [x,y], {dash:2}); 1067 * </script><pre> 1068 * @example 1069 * // Polar plot 1070 * // Create a curve with the equation r(phi)= a*(1+phi), i.e. 1071 * // a cardioid. 1072 * var a = board.create('slider',[[0,2],[2,2],[0,1,2]]); 1073 * var graph = board.create('curve', 1074 * [function(phi){ return a.Value()*(1-Math.cos(phi));}, 1075 * [1,0], 1076 * 0, 2*Math.PI] 1077 * ); 1078 * </pre><div id="d0bc7a2a-8124-45ca-a6e7-142321a8f8c2" style="width: 300px; height: 300px;"></div> 1079 * <script type="text/javascript"> 1080 * var c2_board = JXG.JSXGraph.initBoard('d0bc7a2a-8124-45ca-a6e7-142321a8f8c2', {boundingbox: [-3,3,3,-3], axis: true, showcopyright: false, shownavigation: false}); 1081 * var a = c2_board.create('slider',[[0,2],[2,2],[0,1,2]]); 1082 * var graph2 = c2_board.create('curve', [function(phi){ return a.Value()*(1-Math.cos(phi));}, [1,0], 0, 2*Math.PI]); 1083 * </script><pre> 1084 */ 1085 JXG.createCurve = function (board, parents, attributes) { 1086 var attr = Type.copyAttributes(attributes, board.options, 'curve'); 1087 return new JXG.Curve(board, ['x'].concat(parents), attr); 1088 }; 1089 1090 JXG.registerElement('curve', JXG.createCurve); 1091 1092 /** 1093 * @class This element is used to provide a constructor for functiongraph, which is just a wrapper for element {@link Curve} with {@link JXG.Curve#X()} 1094 * set to x. The graph is drawn for x in the interval [a,b]. 1095 * @pseudo 1096 * @description 1097 * @name Functiongraph 1098 * @augments JXG.Curve 1099 * @constructor 1100 * @type JXG.Curve 1101 * @param {function_number,function_number,function} f,a_,b_ Parent elements are a function term f(x) describing the function graph. 1102 * <p> 1103 * Further, an optional number or function for the left interval border a, 1104 * and an optional number or function for the right interval border b. 1105 * <p> 1106 * Default values are a=-10 and b=10. 1107 * @see JXG.Curve 1108 * @example 1109 * // Create a function graph for f(x) = 0.5*x*x-2*x 1110 * var graph = board.create('functiongraph', 1111 * [function(x){ return 0.5*x*x-2*x;}, -2, 4] 1112 * ); 1113 * </pre><div id="efd432b5-23a3-4846-ac5b-b471e668b437" style="width: 300px; height: 300px;"></div> 1114 * <script type="text/javascript"> 1115 * var alex1_board = JXG.JSXGraph.initBoard('efd432b5-23a3-4846-ac5b-b471e668b437', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 1116 * var graph = alex1_board.create('functiongraph', [function(x){ return 0.5*x*x-2*x;}, -2, 4]); 1117 * </script><pre> 1118 * @example 1119 * // Create a function graph for f(x) = 0.5*x*x-2*x with variable interval 1120 * var s = board.create('slider',[[0,4],[3,4],[-2,4,5]]); 1121 * var graph = board.create('functiongraph', 1122 * [function(x){ return 0.5*x*x-2*x;}, 1123 * -2, 1124 * function(){return s.Value();}] 1125 * ); 1126 * </pre><div id="4a203a84-bde5-4371-ad56-44619690bb50" style="width: 300px; height: 300px;"></div> 1127 * <script type="text/javascript"> 1128 * var alex2_board = JXG.JSXGraph.initBoard('4a203a84-bde5-4371-ad56-44619690bb50', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 1129 * var s = alex2_board.create('slider',[[0,4],[3,4],[-2,4,5]]); 1130 * var graph = alex2_board.create('functiongraph', [function(x){ return 0.5*x*x-2*x;}, -2, function(){return s.Value();}]); 1131 * </script><pre> 1132 */ 1133 JXG.createFunctiongraph = function (board, parents, attributes) { 1134 var attr, 1135 par = ['x', 'x'].concat(parents); 1136 1137 attr = Type.copyAttributes(attributes, board.options, 'curve'); 1138 attr.curvetype = 'functiongraph'; 1139 return new JXG.Curve(board, par, attr); 1140 }; 1141 1142 JXG.registerElement('functiongraph', JXG.createFunctiongraph); 1143 JXG.registerElement('plot', JXG.createFunctiongraph); 1144 1145 1146 /** 1147 * TODO 1148 * Create a dynamic spline interpolated curve given by sample points p_1 to p_n. 1149 * @param {JXG.Board} board Reference to the board the spline is drawn on. 1150 * @param {Array} parents Array of points the spline interpolates 1151 * @param {Object} attributes Define color, width, ... of the spline 1152 * @returns {JXG.Curve} Returns reference to an object of type JXG.Curve. 1153 */ 1154 JXG.createSpline = function (board, parents, attributes) { 1155 var f; 1156 1157 f = function () { 1158 var D, x = [], y = []; 1159 1160 return function (t, suspended) { 1161 var i, j; 1162 1163 if (!suspended) { 1164 x = []; 1165 y = []; 1166 1167 // given as [x[], y[]] 1168 if (parents.length === 2 && Type.isArray(parents[0]) && Type.isArray(parents[1]) && parents[0].length === parents[1].length) { 1169 for (i = 0; i < parents[0].length; i++) { 1170 if (typeof parents[0][i] === 'function') { 1171 x.push(parents[0][i]()); 1172 } else { 1173 x.push(parents[0][i]); 1174 } 1175 1176 if (typeof parents[1][i] === 'function') { 1177 y.push(parents[1][i]()); 1178 } else { 1179 y.push(parents[1][i]); 1180 } 1181 } 1182 } else { 1183 for (i = 0; i < parents.length; i++) { 1184 if (Type.isPoint(parents[i])) { 1185 x.push(parents[i].X()); 1186 y.push(parents[i].Y()); 1187 // given as [[x1,y1], [x2, y2], ...] 1188 } else if (Type.isArray(parents[i]) && parents[i].length === 2) { 1189 for (i = 0; i < parents.length; i++) { 1190 if (typeof parents[i][0] === 'function') { 1191 x.push(parents[i][0]()); 1192 } else { 1193 x.push(parents[i][0]); 1194 } 1195 1196 if (typeof parents[i][1] === 'function') { 1197 y.push(parents[i][1]()); 1198 } else { 1199 y.push(parents[i][1]); 1200 } 1201 } 1202 } 1203 } 1204 } 1205 1206 // The array D has only to be calculated when the position of one or more sample point 1207 // changes. otherwise D is always the same for all points on the spline. 1208 D = Numerics.splineDef(x, y); 1209 } 1210 return Numerics.splineEval(t, x, y, D); 1211 }; 1212 }; 1213 return board.create('curve', ["x", f()], attributes); 1214 }; 1215 1216 /** 1217 * Register the element type spline at JSXGraph 1218 * @private 1219 */ 1220 JXG.registerElement('spline', JXG.createSpline); 1221 1222 /** 1223 * @class This element is used to provide a constructor for Riemann sums, which is realized as a special curve. 1224 * The returned element has the method Value() which returns the sum of the areas of the rectangles. 1225 * @pseudo 1226 * @description 1227 * @name Riemannsum 1228 * @augments JXG.Curve 1229 * @constructor 1230 * @type JXG.Curve 1231 * @param {function_number,function_string,function_function,number_function,number} f,n,type_,a_,b_ Parent elements of Riemannsum are a 1232 * function term f(x) describing the function graph which is filled by the Riemann rectangles. 1233 * <p> 1234 * n determines the number of rectangles, it is either a fixed number or a function. 1235 * <p> 1236 * type is a string or function returning one of the values: 'left', 'right', 'middle', 'lower', 'upper', 'random', 'simpson', or 'trapezodial'. 1237 * Default value is 'left'. 1238 * <p> 1239 * Further parameters are an optional number or function for the left interval border a, 1240 * and an optional number or function for the right interval border b. 1241 * <p> 1242 * Default values are a=-10 and b=10. 1243 * @see JXG.Curve 1244 * @example 1245 * // Create Riemann sums for f(x) = 0.5*x*x-2*x. 1246 * var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1}); 1247 * var f = function(x) { return 0.5*x*x-2*x; }; 1248 * var r = board.create('riemannsum', 1249 * [f, function(){return s.Value();}, 'upper', -2, 5], 1250 * {fillOpacity:0.4} 1251 * ); 1252 * var g = board.create('functiongraph',[f, -2, 5]); 1253 * var t = board.create('text',[-1,-1, function(){ return 'Sum=' + r.Value().toFixed(4); }]); 1254 * </pre><div id="940f40cc-2015-420d-9191-c5d83de988cf" style="width: 300px; height: 300px;"></div> 1255 * <script type="text/javascript"> 1256 * var rs1_board = JXG.JSXGraph.initBoard('940f40cc-2015-420d-9191-c5d83de988cf', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 1257 * var f = function(x) { return 0.5*x*x-2*x; }; 1258 * var s = rs1_board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1}); 1259 * var r = rs1_board.create('riemannsum', [f, function(){return s.Value();}, 'upper', -2, 5], {fillOpacity:0.4}); 1260 * var g = rs1_board.create('functiongraph', [f, -2, 5]); 1261 * var t = board.create('text',[-1,-1, function(){ return 'Sum=' + r.Value().toFixed(4); }]); 1262 * </script><pre> 1263 */ 1264 JXG.createRiemannsum = function (board, parents, attributes) { 1265 var n, type, f, par, c, attr; 1266 1267 attr = Type.copyAttributes(attributes, board.options, 'riemannsum'); 1268 attr.curvetype = 'plot'; 1269 1270 f = parents[0]; 1271 n = Type.createFunction(parents[1], board, ''); 1272 1273 if (!Type.exists(n)) { 1274 throw new Error("JSXGraph: JXG.createRiemannsum: argument '2' n has to be number or function." + 1275 "\nPossible parent types: [function,n:number|function,type,start:number|function,end:number|function]"); 1276 } 1277 1278 type = Type.createFunction(parents[2], board, '', false); 1279 if (!Type.exists(type)) { 1280 throw new Error("JSXGraph: JXG.createRiemannsum: argument 3 'type' has to be string or function." + 1281 "\nPossible parent types: [function,n:number|function,type,start:number|function,end:number|function]"); 1282 } 1283 1284 par = [[0], [0]].concat(parents.slice(3)); 1285 1286 c = board.create('curve', par, attr); 1287 1288 c.sum = 0.0; 1289 c.Value = function () { 1290 return this.sum; 1291 }; 1292 1293 c.updateDataArray = function () { 1294 var u = Numerics.riemann(f, n(), type(), this.minX(), this.maxX()); 1295 this.dataX = u[0]; 1296 this.dataY = u[1]; 1297 1298 // Update "Riemann sum" 1299 this.sum = u[2]; 1300 }; 1301 1302 return c; 1303 }; 1304 1305 JXG.registerElement('riemannsum', JXG.createRiemannsum); 1306 1307 /** 1308 * @class This element is used to provide a constructor for travce curve (simple locus curve), which is realized as a special curve. 1309 * @pseudo 1310 * @description 1311 * @name Tracecurve 1312 * @augments JXG.Curve 1313 * @constructor 1314 * @type JXG.Curve 1315 * @param {Point,Point} Parent elements of Tracecurve are a 1316 * glider point and a point whose locus is traced. 1317 * @see JXG.Curve 1318 * @example 1319 * // Create trace curve. 1320 var c1 = board.create('circle',[[0, 0], [2, 0]]), 1321 p1 = board.create('point',[-3, 1]), 1322 g1 = board.create('glider',[2, 1, c1]), 1323 s1 = board.create('segment',[g1, p1]), 1324 p2 = board.create('midpoint',[s1]), 1325 curve = board.create('tracecurve', [g1, p2]); 1326 1327 * </pre><div id="5749fb7d-04fc-44d2-973e-45c1951e29ad" style="width: 300px; height: 300px;"></div> 1328 * <script type="text/javascript"> 1329 * var tc1_board = JXG.JSXGraph.initBoard('5749fb7d-04fc-44d2-973e-45c1951e29ad', {boundingbox: [-4, 4, 4, -4], axis: false, showcopyright: false, shownavigation: false}); 1330 * var c1 = tc1_board.create('circle',[[0, 0], [2, 0]]), 1331 * p1 = tc1_board.create('point',[-3, 1]), 1332 * g1 = tc1_board.create('glider',[2, 1, c1]), 1333 * s1 = tc1_board.create('segment',[g1, p1]), 1334 * p2 = tc1_board.create('midpoint',[s1]), 1335 * curve = tc1_board.create('tracecurve', [g1, p2]); 1336 * </script><pre> 1337 */ 1338 JXG.createTracecurve = function (board, parents, attributes) { 1339 var c, glider, tracepoint, attr; 1340 1341 if (parents.length !== 2) { 1342 throw new Error("JSXGraph: Can't create trace curve with given parent'" + 1343 "\nPossible parent types: [glider, point]"); 1344 } 1345 1346 glider = board.select(parents[0]); 1347 tracepoint = board.select(parents[1]); 1348 1349 if (glider.type !== Const.OBJECT_TYPE_GLIDER || !Type.isPoint(tracepoint)) { 1350 throw new Error("JSXGraph: Can't create trace curve with parent types '" + 1351 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1352 "\nPossible parent types: [glider, point]"); 1353 } 1354 1355 attr = Type.copyAttributes(attributes, board.options, 'tracecurve'); 1356 attr.curvetype = 'plot'; 1357 c = board.create('curve', [[0], [0]], attr); 1358 1359 c.updateDataArray = function () { 1360 var i, step, t, el, pEl, x, y, v, from, savetrace, 1361 le = attr.numberpoints, 1362 savePos = glider.position, 1363 slideObj = glider.slideObject, 1364 mi = slideObj.minX(), 1365 ma = slideObj.maxX(); 1366 1367 // set step width 1368 step = (ma - mi) / le; 1369 this.dataX = []; 1370 this.dataY = []; 1371 1372 /* 1373 * For gliders on circles and lines a closed curve is computed. 1374 * For gliders on curves the curve is not closed. 1375 */ 1376 if (slideObj.elementClass !== Const.OBJECT_CLASS_CURVE) { 1377 le++; 1378 } 1379 1380 // Loop over all steps 1381 for (i = 0; i < le; i++) { 1382 t = mi + i * step; 1383 x = slideObj.X(t) / slideObj.Z(t); 1384 y = slideObj.Y(t) / slideObj.Z(t); 1385 1386 // Position the glider 1387 glider.setPositionDirectly(Const.COORDS_BY_USER, [x, y]); 1388 from = false; 1389 1390 // Update all elements from the glider up to the trace element 1391 for (el in this.board.objects) { 1392 if (this.board.objects.hasOwnProperty(el)) { 1393 pEl = this.board.objects[el]; 1394 1395 if (pEl === glider) { 1396 from = true; 1397 } 1398 1399 if (from && pEl.needsRegularUpdate) { 1400 // Save the trace mode of the element 1401 savetrace = pEl.visProp.trace; 1402 pEl.visProp.trace = false; 1403 pEl.needsUpdate = true; 1404 pEl.update(true); 1405 1406 // Restore the trace mode 1407 pEl.visProp.trace = savetrace; 1408 if (pEl === tracepoint) { 1409 break; 1410 } 1411 } 1412 } 1413 } 1414 1415 // Store the position of the trace point 1416 this.dataX[i] = tracepoint.X(); 1417 this.dataY[i] = tracepoint.Y(); 1418 } 1419 1420 // Restore the original position of the glider 1421 glider.position = savePos; 1422 from = false; 1423 1424 // Update all elements from the glider to the trace point 1425 for (el in this.board.objects) { 1426 if (this.board.objects.hasOwnProperty(el)) { 1427 pEl = this.board.objects[el]; 1428 if (pEl === glider) { 1429 from = true; 1430 } 1431 1432 if (from && pEl.needsRegularUpdate) { 1433 savetrace = pEl.visProp.trace; 1434 pEl.visProp.trace = false; 1435 pEl.needsUpdate = true; 1436 pEl.update(true); 1437 pEl.visProp.trace = savetrace; 1438 1439 if (pEl === tracepoint) { 1440 break; 1441 } 1442 } 1443 } 1444 } 1445 }; 1446 1447 return c; 1448 }; 1449 1450 JXG.registerElement('tracecurve', JXG.createTracecurve); 1451 1452 return { 1453 Curve: JXG.Curve, 1454 createCurve: JXG.createCurve, 1455 createFunctiongraph: JXG.createFunctiongraph, 1456 createPlot: JXG.createPlot, 1457 createSpline: JXG.createSpline, 1458 createRiemannsum: JXG.createRiemannsum, 1459 createTracecurve: JXG.createTracecurve 1460 }; 1461 }); 1462