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