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