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, adn 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} defining terms An array with the functon terms, data points of the curve. 37 * @param {String} id Unique identifier for the point. If null or an empty string is given, 38 * an unique id will be generated by Board 39 * @param {String} name Not necessarily unique name for the point. If null or an 40 * empty string is given, an unique name will be generated 41 * @param {boolean} show False if the point is invisible, True otherwise 42 * @see JXG.Board#generateName 43 * @see JXG.Board#addCurve 44 */ 45 JXG.Curve = function (board, parents, id, name, withLabel, layer) { 46 this.constructor(); 47 48 this.points = []; 49 50 this.type = JXG.OBJECT_TYPE_CURVE; 51 this.elementClass = JXG.OBJECT_CLASS_CURVE; 52 53 this.init(board, id, name); 54 55 /** 56 * Set the display layer. 57 */ 58 if (layer == null) layer = board.options.layer['curve']; 59 this.layer = layer; 60 61 /** Use the algorithm by Gillam and Hohenwarter for plotting. 62 * If false the naive algorithm is used. 63 * It is much slower, but the result is better. 64 */ 65 this.doAdvancedPlot = this.board.options.curve.doAdvancedPlot; 66 67 /** 68 * Number of points on curves after mouseUp, i.e. high quality output. 69 * Only used if this.doAdvancedPlot==false 70 * May be overwritten. 71 **/ 72 this.numberPointsHigh = this.board.options.curve.numberPointsHigh; 73 /** 74 * Number of points on curves after mousemove, i.e. low quality output. 75 * Only used if this.doAdvancedPlot==false 76 * May be overwritten. 77 **/ 78 this.numberPointsLow = this.board.options.curve.numberPointsLow; 79 /** 80 * Number of points on curves. This value changes 81 * between numberPointsLow and numberPointsHigh. 82 * It is set in {@link #updateCurve}. 83 */ 84 this.numberPoints = this.numberPointsHigh; 85 86 this.visProp['strokeWidth'] = this.board.options.curve.strokeWidth; 87 this.visProp['highlightStrokeWidth'] = this.visProp['strokeWidth']; 88 89 this.visProp['visible'] = true; 90 this.dataX = null; 91 this.dataY = null; 92 93 /** 94 * This is just for the hasPoint() method. 95 * @type int 96 */ 97 //this.r = this.board.options.precision.hasPoint; 98 99 /** 100 * The curveType is set in @see generateTerm and used in 101 * {@link updateCurve} 102 * Possible values are: 103 * 'none' 104 * 'plot': Data plot 105 * 'parameter': we can not distinguish function graphs and parameter curves 106 * 'functiongraph': function graph 107 * 'polar' 108 * 'implicit' (not yet) 109 * 110 * Only parameter and plot are set directly. 111 * polar is set with setProperties only. 112 **/ 113 // this.curveType = 'none'; 114 this.curveType = null; 115 116 if (parents[0]!=null) { 117 this.varname = parents[0]; 118 } else { 119 this.varname = 'x'; 120 } 121 this.xterm = parents[1]; // function graphs: "x" 122 this.yterm = parents[2]; // function graphs: e.g. "x^2" 123 this.generateTerm(this.varname,this.xterm,this.yterm,parents[3],parents[4]); // Converts GEONExT syntax into JavaScript syntax 124 this.updateCurve(); // First evaluation of the curve 125 126 this.createLabel(withLabel); 127 this.id = this.board.setId(this,'G'); 128 this.board.renderer.drawCurve(this); 129 this.board.finalizeAdding(this); 130 131 if (typeof this.xterm=='string') { 132 this.notifyParents(this.xterm); 133 } 134 if (typeof this.yterm=='string') { 135 this.notifyParents(this.yterm); 136 } 137 }; 138 JXG.Curve.prototype = new JXG.GeometryElement; 139 140 /** 141 * Gives the default value of the left bound for the curve. 142 * May be overwritten in @see generateTerm. 143 */ 144 JXG.Curve.prototype.minX = function () { 145 if (this.curveType=='polar') { 146 return 0.0; 147 } else { 148 var leftCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0, 0], this.board); 149 return leftCoords.usrCoords[1]; 150 } 151 }; 152 153 /** 154 * Gives the default value of the right bound for the curve. 155 * May be overwritten in @see generateTerm. 156 */ 157 JXG.Curve.prototype.maxX = function () { 158 var rightCoords; 159 if (this.curveType=='polar') { 160 return 2.0*Math.PI; 161 } else { 162 rightCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [this.board.canvasWidth, 0], this.board); 163 return rightCoords.usrCoords[1]; 164 } 165 }; 166 167 /** 168 * Checks whether (x,y) is near the curve. 169 * @param {int} x Coordinate in x direction, screen coordinates. 170 * @param {int} y Coordinate in y direction, screen coordinates. 171 * @param {y} Find closest point on the curve to (x,y) 172 * @return {bool} True if (x,y) is near the curve, False otherwise. 173 */ 174 JXG.Curve.prototype.hasPoint = function (x,y) { 175 var t, dist = Infinity, 176 c, trans, i, j, tX, tY, 177 xi, xi1, yi, yi1, 178 lbda, x0, y0, x1, y1, xy, den, 179 steps = this.numberPointsLow, 180 d = (this.maxX()-this.minX())/steps, 181 prec = this.board.options.precision.hasPoint/(this.board.unitX*this.board.zoomX), 182 checkPoint, len, 183 suspendUpdate = true; 184 185 prec = prec*prec; 186 checkPoint = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x,y], this.board); 187 x = checkPoint.usrCoords[1]; 188 y = checkPoint.usrCoords[2]; 189 if (this.curveType=='parameter' || this.curveType=='polar' || this.curveType=='functiongraph') { 190 // Brute fore search for a point on the curve close to the mouse pointer 191 len = this.transformations.length; 192 for (i=0,t=this.minX(); i<steps; i++) { 193 tX = this.X(t,suspendUpdate); 194 tY = this.Y(t,suspendUpdate); 195 for (j=0; j<len; j++) { 196 trans = this.transformations[j]; 197 trans.update(); 198 c = JXG.Math.matVecMult(trans.matrix,[1,tX,tY]); 199 tX = c[1]; 200 tY = c[2]; 201 } 202 dist = (x-tX)*(x-tX)+(y-tY)*(y-tY); 203 if (dist<prec) { return true; } 204 t+=d; 205 } 206 } else if (this.curveType == 'plot') { 207 //$('debug').innerHTML +='. '; 208 len = this.numberPoints; // Rough search quality 209 for (i=0;i<len-1;i++) { 210 xi = this.X(i); 211 xi1 = this.X(i+1); 212 //if (i!=xi) { 213 // yi = this.Y(xi); 214 // yi1 = this.Y(xi1); 215 //} else { 216 yi = this.Y(i); 217 yi1 = this.Y(i+1); 218 // $('debug').innerHTML = this.Y.toString(); 219 //} 220 x1 = xi1 - xi; 221 y1 = yi1-yi; 222 223 x0 = x-xi; //this.X(i); 224 y0 = y-yi; //this.Y(i); 225 den = x1*x1+y1*y1; 226 227 if (den>=JXG.Math.eps) { 228 xy = x0*x1+y0*y1; 229 lbda = xy/den; 230 dist = x0*x0+y0*y0 - lbda*xy; 231 } else { 232 lbda = 0.0; 233 dist = x0*x0+y0*y0; 234 } 235 if (lbda>=0.0 && lbda<=1.0 && dist<prec) { 236 return true; 237 } 238 } 239 return false; 240 } 241 return (dist<prec); 242 }; 243 244 /** 245 * Allocate points in the Coords array this.points 246 */ 247 JXG.Curve.prototype.allocatePoints = function () { 248 var i, len; 249 len = this.numberPoints; 250 if (this.points.length<this.numberPoints) { 251 for (i=this.points.length; i<len; i++) { 252 this.points[i] = new JXG.Coords(JXG.COORDS_BY_USER, [0,0], this.board); 253 } 254 } 255 }; 256 257 /** 258 * Computes for equidistant points on the x-axis the values 259 * of the function, {@link #updateCurve} 260 * Then, the update function of the renderer 261 * is called. 262 */ 263 JXG.Curve.prototype.update = function () { 264 if (this.needsUpdate) { 265 this.updateCurve(); 266 } 267 if(this.traced) { 268 this.cloneToBackground(true); 269 } 270 return this; 271 }; 272 273 /** 274 * Then, the update function of the renderer 275 * is called. 276 */ 277 JXG.Curve.prototype.updateRenderer = function () { 278 if (this.needsUpdate) { 279 this.board.renderer.updateCurve(this); 280 this.needsUpdate = false; 281 } 282 283 /* Update the label if visible. */ 284 if(this.hasLabel && this.label.content.visProp['visible']) { 285 //this.label.setCoordinates(this.coords); 286 this.label.content.update(); 287 //this.board.renderer.updateLabel(this.label); 288 this.board.renderer.updateText(this.label.content); 289 } 290 return this; 291 }; 292 293 /** 294 * For dynamic dataplots updateCurve 295 * can be used to compute new entries 296 * for the arrays this.dataX and 297 * this.dataY. It is used in @see updateCurve. 298 * Default is an empty method, can be overwritten 299 * by the user. 300 */ 301 JXG.Curve.prototype.updateDataArray = function () { return this; }; 302 303 /** 304 * Computes for equidistant points on the x-axis the values 305 * of the function. @see #update 306 * If the mousemove event triggers this update, we use only few 307 * points. Otherwise, e.g. on mouseup, many points are used. 308 */ 309 JXG.Curve.prototype.updateCurve = function () { 310 var len, mi, ma, x, y, i, 311 suspendUpdate = false; 312 313 this.updateDataArray(); 314 mi = this.minX(); 315 ma = this.maxX(); 316 317 // Discrete data points 318 if (this.dataX!=null) { // x-coordinates are in an array 319 this.numberPoints = this.dataX.length; 320 len = this.numberPoints; 321 this.allocatePoints(); // It is possible, that the array length has increased. 322 for (i=0; i<len; i++) { 323 x = i; 324 if (this.dataY!=null) { // y-coordinates are in an array 325 y = i; 326 } else { 327 y = this.X(x); // discrete x data, continuous y data 328 } 329 this.points[i].setCoordinates(JXG.COORDS_BY_USER, [this.X(x,suspendUpdate),this.Y(y,suspendUpdate)], false); // The last parameter prevents rounding in usr2screen(). 330 this.updateTransform(this.points[i]); 331 suspendUpdate = true; 332 } 333 } else { // continuous x data 334 if (this.doAdvancedPlot) { 335 this.updateParametricCurve(mi,ma,len); 336 } else { 337 if (this.board.updateQuality==this.board.BOARD_QUALITY_HIGH) { 338 this.numberPoints = this.numberPointsHigh; 339 } else { 340 this.numberPoints = this.numberPointsLow; 341 } 342 len = this.numberPoints; 343 this.allocatePoints(); // It is possible, that the array length has increased. 344 this.updateParametricCurveNaive(mi,ma,len); 345 } 346 } 347 this.getLabelAnchor(); 348 return this; 349 }; 350 351 JXG.Curve.prototype.updateParametricCurveNaive = function(mi,ma,len) { 352 var i, t, 353 suspendUpdate = false, 354 stepSize = (ma-mi)/len; 355 356 for (i=0; i<len; i++) { 357 t = mi+i*stepSize; 358 this.points[i].setCoordinates(JXG.COORDS_BY_USER, [this.X(t,suspendUpdate),this.Y(t,suspendUpdate)], false); // The last parameter prevents rounding in usr2screen(). 359 this.updateTransform(this.points[i]); 360 suspendUpdate = true; 361 } 362 return this; 363 }; 364 365 JXG.Curve.prototype.updateParametricCurve = function(mi,ma,len) { 366 var i, t, t0, 367 suspendUpdate = false, 368 po = new JXG.Coords(JXG.COORDS_BY_USER, [0,0], this.board), 369 x, y, x0, y0, top, depth, 370 MAX_DEPTH, 371 MAX_XDIST, 372 MAX_YDIST, 373 dyadicStack = [], 374 depthStack = [], 375 pointStack = [], 376 divisors = [], 377 //xd_ = NaN, yd_ = NaN, 378 distOK = false, 379 j = 0; 380 381 382 if (this.board.updateQuality==this.board.BOARD_QUALITY_LOW) { 383 MAX_DEPTH = 12; 384 MAX_XDIST = 12; 385 MAX_YDIST = 12; 386 } else { 387 MAX_DEPTH = 20; 388 MAX_XDIST = 2; 389 MAX_YDIST = 2; 390 } 391 392 divisors[0] = ma-mi; 393 for (i=1;i<MAX_DEPTH;i++) { 394 divisors[i] = divisors[i-1]*0.5; 395 } 396 397 i = 1; 398 dyadicStack[0] = 1; 399 depthStack[0] = 0; 400 t = mi; 401 po.setCoordinates(JXG.COORDS_BY_USER, [this.X(t,suspendUpdate),this.Y(t,suspendUpdate)], false); 402 suspendUpdate = true; 403 x0 = po.scrCoords[1]; 404 y0 = po.scrCoords[2]; 405 t0 = t; 406 407 t = ma; 408 po.setCoordinates(JXG.COORDS_BY_USER, [this.X(t,suspendUpdate),this.Y(t,suspendUpdate)], false); 409 x = po.scrCoords[1]; 410 y = po.scrCoords[2]; 411 412 pointStack[0] = [x,y]; 413 414 top = 1; 415 depth = 0; 416 417 this.points = []; 418 this.points[j++] = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x0, y0], this.board); 419 420 do { 421 distOK = this.isDistOK(x0,y0,x,y,MAX_XDIST,MAX_YDIST)||this.isSegmentOutside(x0,y0,x,y); 422 while ( depth<MAX_DEPTH && 423 (!distOK || depth<3 /*|| (j>1 &&!this.bendOK(xd_,yd_,x-x0,y-y0))*/) && 424 !(!this.isSegmentDefined(x0,y0,x,y) && depth>8) 425 ) { 426 dyadicStack[top] = i; 427 depthStack[top] = depth; 428 pointStack[top] = [x,y]; 429 top++; 430 431 i = 2*i-1; 432 depth++; 433 t = mi+i*divisors[depth]; 434 po.setCoordinates(JXG.COORDS_BY_USER, [this.X(t,suspendUpdate),this.Y(t,suspendUpdate)], false); 435 x = po.scrCoords[1]; 436 y = po.scrCoords[2]; 437 distOK = this.isDistOK(x0,y0,x,y,MAX_XDIST,MAX_YDIST)||this.isSegmentOutside(x0,y0,x,y); 438 } 439 /* 440 if (this.board.updateQuality==this.board.BOARD_QUALITY_HIGH && !this.isContinuous(t0,t,MAX_DEPTH)) { 441 //$('debug').innerHTML += 'x '; 442 this.points[j] = new JXG.Coords(JXG.COORDS_BY_SCREEN, [NaN, NaN], this.board); 443 //this.points[j] = new JXG.Coords(JXG.COORDS_BY_SCREEN, [1, 1], this.board); 444 j++; 445 } 446 */ 447 this.points[j] = new JXG.Coords(JXG.COORDS_BY_SCREEN, [x, y], this.board); 448 this.updateTransform(this.points[j]); 449 j++; 450 //xd_ = x-x0; 451 //yd_ = x-y0; 452 x0 = x; 453 y0 = y; 454 t0 = t; 455 456 top--; 457 x = pointStack[top][0]; 458 y = pointStack[top][1]; 459 depth = depthStack[top]+1; 460 i = dyadicStack[top]*2; 461 462 } while (top != 0); 463 this.numberPoints = this.points.length; 464 //$('debug').innerHTML = ' '+this.numberPoints; 465 return this; 466 467 }; 468 469 JXG.Curve.prototype.isSegmentOutside = function (x0,y0,x1,y1) { 470 if (y0<0 && y1<0) { return true; } 471 else if (y0>this.board.canvasHeight && y1>this.board.canvasHeight) { return true; } 472 else if (x0<0 && x1<0) { return true; } 473 else if (x0>this.board.canvasWidth && x1>this.board.canvasWidth) { return true; } 474 return false; 475 }; 476 477 JXG.Curve.prototype.isDistOK = function (x0,y0,x1,y1,MAXX,MAXY) { 478 if (isNaN(x0+y0+x1+y1)) { return false; } 479 return (Math.abs(x1-x0)<MAXY && Math.abs(y1-y0)<MAXY); 480 }; 481 482 JXG.Curve.prototype.isSegmentDefined = function (x0,y0,x1,y1) { 483 return !(isNaN(x0 + y0) && isNaN(x1 + y1)); 484 485 }; 486 /* 487 JXG.Curve.prototype.isContinuous = function (t0, t1, MAX_ITER) { 488 var left, middle, right, tm, 489 iter = 0, 490 initDist, dist = Infinity, 491 dl, dr; 492 493 if (Math.abs(t0-t1)<JXG.Math.eps) { return true; } 494 left = new JXG.Coords(JXG.COORDS_BY_USER, [0,0], this.board); 495 middle = new JXG.Coords(JXG.COORDS_BY_USER, [0,0], this.board); 496 right = new JXG.Coords(JXG.COORDS_BY_USER, [0,0], this.board); 497 498 left.setCoordinates(JXG.COORDS_BY_USER, [this.X(t0,true),this.Y(t0,true)], false); 499 right.setCoordinates(JXG.COORDS_BY_USER, [this.X(t1,true),this.Y(t1,true)], false); 500 501 initDist = Math.max(Math.abs(left.scrCoords[1]-right.scrCoords[1]),Math.abs(left.scrCoords[2]-right.scrCoords[2])); 502 while (iter++<MAX_ITER && dist>initDist*0.9) { 503 tm = (t0+t1)*0.5; 504 middle.setCoordinates(JXG.COORDS_BY_USER, [this.X(tm,true),this.Y(tm,true)], false); 505 dl = Math.max(Math.abs(left.scrCoords[1]-middle.scrCoords[1]),Math.abs(left.scrCoords[2]-middle.scrCoords[2])); 506 dr = Math.max(Math.abs(middle.scrCoords[1]-right.scrCoords[1]),Math.abs(middle.scrCoords[2]-right.scrCoords[2])); 507 508 if (dl>dr) { 509 dist = dl; 510 t1 = tm; 511 } else { 512 dist = dr; 513 t0 = tm; 514 } 515 if (Math.abs(t0-t1)<JXG.Math.eps) { return true;} 516 } 517 if (dist>initDist*0.9) { 518 return false; 519 } else { 520 return true; 521 } 522 }; 523 */ 524 525 /* 526 JXG.Curve.prototype.bendOK = function (xd_,yd_,xd,yd) { 527 var ip = xd_*xd+yd_*yd, 528 MAX_BEND = Math.tan(45*Math.PI/180.0); 529 530 if (isNaN(ip)) { 531 return true; 532 } else if (ip<=0.0) { 533 return false; 534 } else { 535 return Math.abs(xd_*yd-yd_*xd)<MAX_BEND*ip; 536 } 537 }; 538 */ 539 540 JXG.Curve.prototype.updateTransform = function (p) { 541 var t, c, i, 542 len = this.transformations.length; 543 if (len==0) { 544 return p; 545 } 546 for (i=0; i<len; i++) { 547 t = this.transformations[i]; 548 t.update(); 549 c = JXG.Math.matVecMult(t.matrix,p.usrCoords); 550 p.setCoordinates(JXG.COORDS_BY_USER,[c[1],c[2]]); 551 } 552 return p; 553 }; 554 555 JXG.Curve.prototype.addTransform = function (transform) { 556 var list, i, len; 557 if (JXG.isArray(transform)) { 558 list = transform; 559 } else { 560 list = [transform]; 561 } 562 len = list.length; 563 for (i=0; i<len; i++) { 564 this.transformations.push(list[i]); 565 } 566 return this; 567 }; 568 569 JXG.Curve.prototype.setPosition = function (method, x, y) { 570 //if(this.group.length != 0) { 571 // AW: Do we need this for lines? 572 //} else { 573 var t = this.board.create('transform',[x,y],{type:'translate'}); 574 if (this.transformations.length>0 && this.transformations[this.transformations.length-1].isNumericMatrix) { 575 this.transformations[this.transformations.length-1].melt(t); 576 } else { 577 this.addTransform(t); 578 } 579 //this.update(); 580 //} 581 return this; 582 }; 583 584 /** 585 * Converts the GEONExT syntax of the defining function term into JavaScript. 586 * New methods X() and Y() for the Curve object are generated, further 587 * new methods for minX() and maxX(). 588 * 589 * Also, all objects whose name appears in the term are searched and 590 * the curve is added as child to these objects. (Commented out!!!!) 591 * @see Algebra 592 * @see #geonext2JS. 593 */ 594 JXG.Curve.prototype.generateTerm = function (varname, xterm, yterm, mi, ma) { 595 var fx, fy; 596 597 // Generate the methods X() and Y() 598 if (JXG.isArray(xterm)) { 599 this.dataX = xterm; 600 this.X = function(i) { return this.dataX[i]; }; 601 this.curveType = 'plot'; 602 this.numberPoints = this.dataX.length; 603 } else { 604 this.X = JXG.createFunction(xterm,this.board,varname); 605 if (JXG.isString(xterm)) { 606 this.curveType = 'functiongraph'; 607 } else if (JXG.isFunction(xterm) || JXG.isNumber(xterm)) { 608 this.curveType = 'parameter'; 609 } 610 } 611 612 if (JXG.isArray(yterm)) { 613 this.dataY = yterm; 614 this.Y = function(i) { 615 if (JXG.isFunction(this.dataY[i])) { 616 return this.dataY[i](); 617 } else { 618 return this.dataY[i]; 619 } 620 }; 621 } else { 622 this.Y = JXG.createFunction(yterm,this.board,varname); 623 } 624 625 // polar form 626 if (JXG.isFunction(xterm) && JXG.isArray(yterm)) { 627 // Xoffset, Yoffset 628 fx = JXG.createFunction(yterm[0],this.board,''); 629 fy = JXG.createFunction(yterm[1],this.board,''); 630 this.X = function(phi){return (xterm)(phi)*Math.cos(phi)+fx();}; 631 this.Y = function(phi){return (xterm)(phi)*Math.sin(phi)+fy();}; 632 this.curveType = 'polar'; 633 } 634 635 // Set the bounds 636 // lower bound 637 if (mi!=null) this.minX = JXG.createFunction(mi,this.board,''); 638 if (ma!=null) this.maxX = JXG.createFunction(ma,this.board,''); 639 640 /* 641 // Find dependencies 642 var elements = this.board.elementsByName; 643 for (el in elements) { 644 if (el != this.name) { 645 var s1 = "X(" + el + ")"; 646 var s2 = "Y(" + el + ")"; 647 if (xterm.indexOf(s1)>=0 || xterm.indexOf(s2)>=0 || 648 yterm.indexOf(s1)>=0 || yterm.indexOf(s2)>=0) { 649 elements[el].addChild(this); 650 } 651 } 652 } 653 */ 654 }; 655 656 /** 657 * Finds dependencies in a given term and notifies the parents by adding the 658 * dependent object to the found objects child elements. 659 * @param {String} term String containing dependencies for the given object. 660 */ 661 JXG.Curve.prototype.notifyParents = function (contentStr) { 662 //var res = null; 663 //var elements = this.board.elementsByName; 664 JXG.GeonextParser.findDependencies(this,contentStr,this.board); 665 }; 666 667 /** 668 * Calculates LabelAnchor. 669 * @type JXG.Coords 670 * @return Text anchor coordinates as JXG.Coords object. 671 */ 672 JXG.Curve.prototype.getLabelAnchor = function() { 673 var c = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0, this.board.canvasHeight*0.5], this.board); 674 c = JXG.Math.Geometry.projectCoordsToCurve(c.usrCoords[1],c.usrCoords[2],0.0,this,this.board)[0]; 675 return c; 676 }; 677 678 /** 679 * Clone curve to the background. 680 * @param addToTrace Not used yet. Always true. 681 */ 682 JXG.Curve.prototype.cloneToBackground = function(addToTrace) { 683 var copy = {}, r, s, i, er; 684 685 copy.id = this.id + 'T' + this.numTraces; 686 copy.elementClass = JXG.OBJECT_CLASS_CURVE; 687 this.numTraces++; 688 689 copy.points = this.points.slice(0); 690 copy.numberPoints = this.numberPoints; 691 copy.curveType = this.curveType; 692 693 copy.board = {}; 694 copy.board.unitX = this.board.unitX; 695 copy.board.unitY = this.board.unitY; 696 copy.board.zoomX = this.board.zoomX; 697 copy.board.zoomY = this.board.zoomY; 698 copy.board.stretchX = this.board.stretchX; 699 copy.board.stretchY = this.board.stretchY; 700 copy.board.origin = this.board.origin; 701 copy.board.canvasHeight = this.board.canvasHeight; 702 copy.board.canvasWidth = this.board.canvasWidth; 703 copy.board.dimension = this.board.dimension; 704 //copy.board.algebra = this.board.algebra; 705 copy.board.options = this.board.options; 706 707 copy.visProp = this.visProp; 708 JXG.clearVisPropOld(copy); 709 er = this.board.renderer.enhancedRendering; 710 this.board.renderer.enhancedRendering = true; 711 this.board.renderer.drawCurve(copy); 712 this.board.renderer.enhancedRendering = er; 713 this.traces[copy.id] = copy.rendNode; //this.board.renderer.getElementById(copy.id); 714 715 delete copy; 716 }; 717 718 /** 719 * @class This element is used to provide a constructor for curve, which is just a wrapper for element {@link Curve}. 720 * 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]. 721 * <p> 722 * The following types of curves can be plotted: 723 * <ul> 724 * <li> parametric curves: t mapsto (x(t),y(t)), where x() and y() are univariate functions. 725 * <li> polar curves: curves commonly written with polar equations like spirals and cardioids. 726 * <li> data plots: plot linbe segments through a given list of coordinates. 727 * </ul> 728 * @pseudo 729 * @description 730 * @name Curve 731 * @augments JXG.Curve 732 * @constructor 733 * @type JXG.Curve 734 * 735 * @param {function,number_function,number_function,number_function,number} x,y,a_,b_ Parent elements for Parametric Curves. 736 * <p> 737 * x describes the x-coordinate of the curve. It may be a function term in one variable, e.g. x(t). 738 * In case of x being of type number, x(t) is set to a constant function. 739 * this function at the values of the array. 740 * <p> 741 * y describes the y-coordinate of the curve. In case of a number, y(t) is set to the constant function 742 * returning this number. 743 * <p> 744 * Further parameters are an optional number or function for the left interval border a, 745 * and an optional number or function for the right interval border b. 746 * <p> 747 * Default values are a=-10 and b=10. 748 * @param {array_array,function,number} x,y Parent elements for Data Plots. 749 * <p> 750 * x and y are arrays contining the x and y coordinates of the data points which are connected by 751 * line segments. The individual entries of x and y may also be functions. 752 * In case of x being an array the curve type is data plot, regardless of the second parameter and 753 * if additionally the second parameter y is a function term the data plot evaluates. 754 * @param {function_array,function,number_function,number_function,number} r,offset_,a_,b_ Parent elements for Polar Curves. 755 * <p> 756 * The first parameter is a function term r(phi) describing the polar curve. 757 * <p> 758 * The second parameter is the offset of the curve. It has to be 759 * an array containing numbers or functions describing the offset. Default value is the origin [0,0]. 760 * <p> 761 * Further parameters are an optional number or function for the left interval border a, 762 * and an optional number or function for the right interval border b. 763 * <p> 764 * Default values are a=-10 and b=10. 765 * @see JXG.Curve 766 * @example 767 * // Parametric curve 768 * // Create a curve of the form (t-sin(t), 1-cos(t), i.e. 769 * // the cycloid curve. 770 * var graph = board.create('curve', 771 * [function(t){ return t-Math.sin(t);}, 772 * function(t){ return 1-Math.cos(t);}, 773 * 0, 2*Math.PI] 774 * ); 775 * </pre><div id="af9f818b-f3b6-4c4d-8c4c-e4a4078b726d" style="width: 300px; height: 300px;"></div> 776 * <script type="text/javascript"> 777 * var c1_board = JXG.JSXGraph.initBoard('af9f818b-f3b6-4c4d-8c4c-e4a4078b726d', {boundingbox: [-1, 5, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 778 * var graph1 = c1_board.create('curve', [function(t){ return t-Math.sin(t);},function(t){ return 1-Math.cos(t);},0, 2*Math.PI]); 779 * </script><pre> 780 * @example 781 * // Data plots 782 * // Connect a set of points given by coordinates with dashed line segments. 783 * // The x- and y-coordinates of the points are given in two separate 784 * // arrays. 785 * var x = [0,1,2,3,4,5,6,7,8,9]; 786 * var y = [9.2,1.3,7.2,-1.2,4.0,5.3,0.2,6.5,1.1,0.0]; 787 * var graph = board.create('curve', [x,y], {dash:2}); 788 * </pre><div id="7dcbb00e-b6ff-481d-b4a8-887f5d8c6a83" style="width: 300px; height: 300px;"></div> 789 * <script type="text/javascript"> 790 * var c3_board = JXG.JSXGraph.initBoard('7dcbb00e-b6ff-481d-b4a8-887f5d8c6a83', {boundingbox: [-1,10,10,-1], axis: true, showcopyright: false, shownavigation: false}); 791 * var x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 792 * var y = [9.2, 1.3, 7.2, -1.2, 4.0, 5.3, 0.2, 6.5, 1.1, 0.0]; 793 * var graph3 = c3_board.create('curve', [x,y], {dash:2}); 794 * </script><pre> 795 * @example 796 * // Polar plot 797 * // Create a curve with the equation r(phi)= a*(1+phi), i.e. 798 * // a cardioid. 799 * var a = board.create('slider',[[0,2],[2,2],[0,1,2]]); 800 * var graph = board.create('curve', 801 * [function(phi){ return a.Value()*(1-Math.cos(phi));}, 802 * [1,0], 803 * 0, 2*Math.PI] 804 * ); 805 * </pre><div id="d0bc7a2a-8124-45ca-a6e7-142321a8f8c2" style="width: 300px; height: 300px;"></div> 806 * <script type="text/javascript"> 807 * var c2_board = JXG.JSXGraph.initBoard('d0bc7a2a-8124-45ca-a6e7-142321a8f8c2', {boundingbox: [-3,3,3,-3], axis: true, showcopyright: false, shownavigation: false}); 808 * var a = c2_board.create('slider',[[0,2],[2,2],[0,1,2]]); 809 * var graph2 = c2_board.create('curve', [function(phi){ return a.Value()*(1-Math.cos(phi));}, [1,0], 0, 2*Math.PI]); 810 * </script><pre> 811 */ 812 JXG.createCurve = function(board, parents, attributes) { 813 attributes = JXG.checkAttributes(attributes,{withLabel:JXG.readOption(board.options,'curve','withLabel'), layer:null}); 814 return new JXG.Curve(board, ['x'].concat(parents), attributes['id'], attributes['name'], 815 attributes['withLabel'],attributes['layer']); 816 }; 817 818 JXG.JSXGraph.registerElement('curve', JXG.createCurve); 819 820 /** 821 * @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()} 822 * set to x. The graph is drawn for x in the interval [a,b]. 823 * @pseudo 824 * @description 825 * @name Functiongraph 826 * @augments JXG.Curve 827 * @constructor 828 * @type JXG.Curve 829 * @param {function_number,function_number,function} f,a_,b_ Parent elements are a function term f(x) describing the function graph. 830 * <p> 831 * Further, an optional number or function for the left interval border a, 832 * and an optional number or function for the right interval border b. 833 * <p> 834 * Default values are a=-10 and b=10. 835 * @see JXG.Curve 836 * @example 837 * // Create a function graph for f(x) = 0.5*x*x-2*x 838 * var graph = board.create('functiongraph', 839 * [function(x){ return 0.5*x*x-2*x;}, -2, 4] 840 * ); 841 * </pre><div id="efd432b5-23a3-4846-ac5b-b471e668b437" style="width: 300px; height: 300px;"></div> 842 * <script type="text/javascript"> 843 * var alex1_board = JXG.JSXGraph.initBoard('efd432b5-23a3-4846-ac5b-b471e668b437', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 844 * var graph = alex1_board.create('functiongraph', [function(x){ return 0.5*x*x-2*x;}, -2, 4]); 845 * </script><pre> 846 * @example 847 * // Create a function graph for f(x) = 0.5*x*x-2*x with variable interval 848 * var s = board.create('slider',[[0,4],[3,4],[-2,4,5]]); 849 * var graph = board.create('functiongraph', 850 * [function(x){ return 0.5*x*x-2*x;}, 851 * -2, 852 * function(){return s.Value();}] 853 * ); 854 * </pre><div id="4a203a84-bde5-4371-ad56-44619690bb50" style="width: 300px; height: 300px;"></div> 855 * <script type="text/javascript"> 856 * var alex2_board = JXG.JSXGraph.initBoard('4a203a84-bde5-4371-ad56-44619690bb50', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 857 * var s = alex2_board.create('slider',[[0,4],[3,4],[-2,4,5]]); 858 * var graph = alex2_board.create('functiongraph', [function(x){ return 0.5*x*x-2*x;}, -2, function(){return s.Value();}]); 859 * </script><pre> 860 */ 861 JXG.createFunctiongraph = function(board, parents, attributes) { 862 var par = ["x","x"].concat(parents); 863 attributes = JXG.checkAttributes(attributes,{withLabel:JXG.readOption(board.options,'curve','withLabel'), layer:null}); 864 attributes['curveType'] = 'functiongraph'; 865 return new JXG.Curve(board, par, attributes['id'], attributes['name'],attributes['withLabel'],attributes['layer']); 866 }; 867 868 JXG.JSXGraph.registerElement('functiongraph', JXG.createFunctiongraph); 869 870 871 /** 872 * TODO 873 * Create a dynamic spline interpolated curve given by sample points p_1 to p_n. 874 * @param {JXG.Board} board Reference to the board the spline is drawn on. 875 * @param {Array} parents Array of points the spline interpolates 876 * @param {Object} attributes Define color, width, ... of the spline 877 * @type JXG.Curve 878 * @return Returns reference to an object of type JXG.Curve. 879 */ 880 JXG.createSpline = function(board, parents, attributes) { 881 var F; 882 attributes = JXG.checkAttributes(attributes,{withLabel:JXG.readOption(board.options,'curve','withLabel'), layer:null}); 883 F = function() { 884 var D, x=[], y=[]; 885 886 var fct = function (t,suspended) { 887 var i, j; 888 889 if (!suspended) { 890 x = []; 891 y = []; 892 893 // given as [x[], y[]] 894 if(parents.length == 2 && JXG.isArray(parents[0]) && JXG.isArray(parents[1]) && parents[0].length == parents[1].length) { 895 for(i=0; i<parents[0].length; i++) { 896 if(typeof parents[0][i] == 'function') 897 x.push(parents[0][i]()); 898 else 899 x.push(parents[0][i]); 900 if(typeof parents[1][i] == 'function') 901 y.push(parents[1][i]()); 902 else 903 y.push(parents[1][i]); 904 } 905 } else { 906 for(i=0; i<parents.length; i++) { 907 if(JXG.isPoint(parents[i])) { 908 //throw new Error("JSXGraph: JXG.createSpline: Parents has to be an array of JXG.Point."); 909 x.push(parents[i].X()); 910 y.push(parents[i].Y()); 911 } else if (JXG.isArray(parents[i]) && parents[i].length == 2) { // given as [[x1,y1], [x2, y2], ...] 912 for(i=0; i<parents.length; i++) { 913 if(typeof parents[i][0] == 'function') 914 x.push(parents[i][0]()); 915 else 916 x.push(parents[i][0]); 917 if(typeof parents[i][1] == 'function') 918 y.push(parents[i][1]()); 919 else 920 y.push(parents[i][1]); 921 } 922 } 923 } 924 } 925 926 // The array D has only to be calculated when the position of one or more sample point 927 // changes. otherwise D is always the same for all points on the spline. 928 D = JXG.Math.Numerics.splineDef(x, y); 929 } 930 return JXG.Math.Numerics.splineEval(t, x, y, D); 931 }; 932 return fct; 933 }; 934 return new JXG.Curve(board, ["x","x", F()], attributes["id"], attributes["name"], 935 attributes['withLabel'],attributes['layer']); 936 }; 937 938 /** 939 * Register the element type spline at JSXGraph 940 * @private 941 */ 942 JXG.JSXGraph.registerElement('spline', JXG.createSpline); 943 944 /** 945 * @class This element is used to provide a constructor for Riemann sums, which is relaized as a special curve. 946 * @pseudo 947 * @description 948 * @name Riemannsum 949 * @augments JXG.Curve 950 * @constructor 951 * @type JXG.Curve 952 * @param {function_number,function_string,function_function,number_function,number} f,n,type_,a_,b_ Parent elements of Riemannsum are a 953 * function term f(x) describing the function graph which is filled by the Riemann rectangles. 954 * <p> 955 * n determines the number of rectangles, it is either a fixed number or a function. 956 * <p> 957 * type is a string or function returning one of the values: 'left', 'right', 'middle', 'lower', 'upper', or 'trapezodial'. 958 * Default value is 'left'. 959 * <p> 960 * Further parameters are an optional number or function for the left interval border a, 961 * and an optional number or function for the right interval border b. 962 * <p> 963 * Default values are a=-10 and b=10. 964 * @see JXG.Curve 965 * @example 966 * // Create Riemann sums for f(x) = 0.5*x*x-2*x. 967 * var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1}); 968 * var f = function(x) { return 0.5*x*x-2*x; }; 969 * var r = board.create('riemannsum', 970 * [f, function(){return s.Value();}, 'upper', -2, 5], 971 * {fillOpacity:0.4} 972 * ); 973 * var g = board.create('functiongraph',[f, -2, 5]); 974 * </pre><div id="940f40cc-2015-420d-9191-c5d83de988cf" style="width: 300px; height: 300px;"></div> 975 * <script type="text/javascript"> 976 * var rs1_board = JXG.JSXGraph.initBoard('940f40cc-2015-420d-9191-c5d83de988cf', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 977 * var f = function(x) { return 0.5*x*x-2*x; }; 978 * var s = rs1_board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1}); 979 * var r = rs1_board.create('riemannsum', [f, function(){return s.Value();}, 'upper', -2, 5], {fillOpacity:0.4}); 980 * var g = rs1_board.create('functiongraph', [f, -2, 5]); 981 * </script><pre> 982 */ 983 JXG.createRiemannsum = function(board, parents, attributes) { 984 var n, type, f, par, c; 985 986 attributes = JXG.checkAttributes(attributes, 987 {withLabel:JXG.readOption(board.options,'curve','withLabel'),layer:null,fillOpacity:0.3,fillColor:'#ffff00', curveType:'plot'}); 988 989 f = parents[0]; 990 n = JXG.createFunction(parents[1],board,''); 991 if (n==null) { 992 throw new Error("JSXGraph: JXG.createRiemannsum: argument '2' n has to be number or function." + 993 "\nPossible parent types: [function,n:number|function,type,start:number|function,end:number|function]"); 994 } 995 type = JXG.createFunction(parents[2],board,'',false); 996 if (type==null) { 997 throw new Error("JSXGraph: JXG.createRiemannsum: argument 3 'type' has to be string or function." + 998 "\nPossible parent types: [function,n:number|function,type,start:number|function,end:number|function]"); 999 } 1000 1001 par = ['x', [0], [0]].concat(parents.slice(3)); 1002 /** 1003 * @private 1004 */ 1005 c = new JXG.Curve(board, par, attributes['id'], attributes['name'], attributes['withLabel'],attributes['layer']); 1006 /** 1007 * @private 1008 */ 1009 c.updateDataArray = function() { 1010 var u = JXG.Math.Numerics.riemann(f,n(),type(),this.minX(),this.maxX()); 1011 this.dataX = u[0]; 1012 this.dataY = u[1]; 1013 }; 1014 return c; 1015 }; 1016 1017 JXG.JSXGraph.registerElement('riemannsum', JXG.createRiemannsum); 1018