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 The geometry object Point is defined in this file. Point stores all 28 * style and functional properties that are required to draw and move a point on 29 * a board. 30 */ 31 32 33 /** 34 * A point is the basic geometric element. Based on points lines and circles can be constructed which can be intersected 35 * which in turn are points again which can be used to construct new lines, circles, polygons, etc. This class holds methods for 36 * all kind of points like free points, gliders, and intersection points. 37 * @class Creates a new point object. Do not use this constructor to create a point. Use {@link JXG.Board#create} with 38 * type {@link Point}, {@link Glider}, or {@link Intersection} instead. 39 * @augments JXG.GeometryElement 40 * @param {string|JXG.Board} board The board the new point is drawn on. 41 * @param {Array} coordinates An array with the affine user coordinates of the point. 42 * @param {Object} attributes An object containing visual properties like in {@link JXG.Options#point} and 43 * {@link JXG.Options#elements}, and optional a name and a id. 44 * @see JXG.Board#generateName 45 * @see JXG.Board#addPoint 46 */ 47 JXG.Point = function (board, coordinates, attributes) { 48 this.constructor(board, attributes, JXG.OBJECT_TYPE_POINT, JXG.OBJECT_CLASS_POINT); 49 50 if (coordinates==null) { 51 coordinates=[0,0]; 52 } 53 /** 54 * Coordinates of the point. 55 * @type JXG.Coords 56 * @private 57 */ 58 this.coords = new JXG.Coords(JXG.COORDS_BY_USER, coordinates, this.board); 59 this.initialCoords = new JXG.Coords(JXG.COORDS_BY_USER, coordinates, this.board); 60 61 /** 62 * Relative position on a line if point is a glider on a line. 63 * @type Number 64 * @private 65 */ 66 this.position = null; 67 68 /** 69 * Determines whether the point slides on a polygon if point is a glider. 70 * @type boolean 71 * @default false 72 * @private 73 */ 74 this.onPolygon = false; 75 76 /** 77 * When used as a glider this member stores the object, where to glide on. To set the object to glide on use the method 78 * {@link JXG.Point#makeGlider} and DO NOT set this property directly as it will break the dependency tree. 79 * TODO: Requires renaming to glideObject 80 * @type JXG.GeometryElement 81 * @name Glider#slideObject 82 */ 83 this.slideObject = null; 84 85 this.Xjc = null; 86 this.Yjc = null; 87 88 // documented in GeometryElement 89 this.methodMap = JXG.deepCopy(this.methodMap, { 90 move: 'moveTo', 91 glide: 'makeGlider', 92 X: 'X', 93 Y: 'Y', 94 free: 'free', 95 setPosition: 'setGliderPosition' 96 }); 97 98 /** 99 * Stores the groups of this point in an array of Group. 100 * @type array 101 * @see JXG.Group 102 * @private 103 */ 104 this.group = []; 105 106 this.elType = 'point'; 107 108 /* Register point at board. */ 109 this.id = this.board.setId(this, 'P'); 110 this.board.renderer.drawPoint(this); 111 this.board.finalizeAdding(this); 112 113 this.createLabel(); 114 }; 115 116 /** 117 * Inherits here from {@link JXG.GeometryElement}. 118 */ 119 JXG.Point.prototype = new JXG.GeometryElement(); 120 121 122 JXG.extend(JXG.Point.prototype, /** @lends JXG.Point.prototype */ { 123 /** 124 * Checks whether (x,y) is near the point. 125 * @param {int} x Coordinate in x direction, screen coordinates. 126 * @param {int} y Coordinate in y direction, screen coordinates. 127 * @type boolean 128 * @return True if (x,y) is near the point, False otherwise. 129 * @private 130 */ 131 hasPoint: function (x,y) { 132 var coordsScr = this.coords.scrCoords, r; 133 r = parseFloat(this.visProp.size); 134 if(r < this.board.options.precision.hasPoint) { 135 r = this.board.options.precision.hasPoint; 136 } 137 138 return ((Math.abs(coordsScr[1]-x) < r+2) && (Math.abs(coordsScr[2]-y)) < r+2); 139 }, 140 141 /** 142 * Dummy function for unconstrained points or gliders. 143 * @private 144 */ 145 updateConstraint: function() { return this; }, 146 147 /** 148 * Updates the position of the point. 149 */ 150 update: function (fromParent) { 151 if (!this.needsUpdate) { return this; } 152 153 if(typeof fromParent == 'undefined') { 154 fromParent = false; 155 } 156 157 /* 158 * We need to calculate the new coordinates no matter of the points visibility because 159 * a child could be visible and depend on the coordinates of the point (e.g. perpendicular). 160 * 161 * Check if point is a glider and calculate new coords in dependency of this.slideObject. 162 * This function is called with fromParent==true for example if 163 * the defining elements of the line or circle have been changed. 164 */ 165 if(this.type == JXG.OBJECT_TYPE_GLIDER) { 166 if (fromParent) { 167 this.updateGliderFromParent(); 168 } else { 169 this.updateGlider(); 170 } 171 } 172 173 /** 174 * If point is a calculated point, call updateConstraint() to calculate new coords. 175 * The second test is for dynamic axes. 176 */ 177 if (this.type == JXG.OBJECT_TYPE_CAS || this.type == JXG.OBJECT_TYPE_AXISPOINT) { 178 this.updateConstraint(); 179 } 180 181 this.updateTransform(); 182 183 if(this.visProp.trace) { 184 this.cloneToBackground(true); 185 } 186 187 return this; 188 }, 189 190 /** 191 * Update of glider in case of dragging the glider or setting the postion of the glider. 192 * The relative position of the glider has to be updated. 193 * @private 194 */ 195 updateGlider: function() { 196 var i, p1c, p2c, d, v, poly, cc, pos; 197 198 if(this.slideObject.elementClass == JXG.OBJECT_CLASS_CIRCLE) { 199 this.coords = JXG.Math.Geometry.projectPointToCircle(this, this.slideObject, this.board); 200 this.position = JXG.Math.Geometry.rad([this.slideObject.center.X()+1.0,this.slideObject.center.Y()],this.slideObject.center,this); 201 } else if(this.slideObject.elementClass == JXG.OBJECT_CLASS_LINE) { 202 203 /** 204 * onPolygon==true: the point is a slider on a seg,ent and this segment is one of the 205 * "borders" of a polygon. 206 * This is a GEONExT feature. 207 **/ 208 if (this.onPolygon) { 209 p1c = this.slideObject.point1.coords.usrCoords; 210 p2c = this.slideObject.point2.coords.usrCoords; 211 i = 1; 212 d = p2c[i] - p1c[i]; 213 if (Math.abs(d)<JXG.Math.eps) { 214 i = 2; 215 d = p2c[i] - p1c[i]; 216 } 217 cc = JXG.Math.Geometry.projectPointToLine(this, this.slideObject, this.board); 218 pos = (cc.usrCoords[i] - p1c[i]) / d; 219 poly = this.slideObject.parentPolygon; 220 221 if (pos<0.0) { 222 for (i=0; i<poly.borders.length; i++) { 223 if (this.slideObject == poly.borders[i]) { 224 this.slideObject = poly.borders[(i - 1 + poly.borders.length) % poly.borders.length]; 225 break; 226 } 227 } 228 } else if (pos>1.0) { 229 for (i=0; i<poly.borders.length; i++) { 230 if(this.slideObject == poly.borders[i]) { 231 this.slideObject = poly.borders[(i + 1 + poly.borders.length) % poly.borders.length]; 232 break; 233 } 234 } 235 } 236 } 237 238 p1c = this.slideObject.point1.coords; 239 p2c = this.slideObject.point2.coords; 240 // Distance between the two defining points 241 d = p1c.distance(JXG.COORDS_BY_USER, p2c); 242 p1c = p1c.usrCoords.slice(0); 243 p2c = p2c.usrCoords.slice(0); 244 245 if (d<JXG.Math.eps) { // The defining points are identical 246 this.coords.setCoordinates(JXG.COORDS_BY_USER, p1c); 247 this.position = 0.0; 248 } else { 249 if (d==Number.POSITIVE_INFINITY) { // At least one point is an ideal point 250 d = 1.0/JXG.Math.eps; 251 if (Math.abs(p2c[0])<JXG.Math.eps) { // The second point is an ideal point 252 d /= JXG.Math.Geometry.distance([0,0,0], p2c); 253 p2c = [1, p1c[1]+p2c[1]*d, p1c[2]+p2c[2]*d]; 254 } else { // The first point is an ideal point 255 d /= JXG.Math.Geometry.distance([0,0,0], p1c); 256 p1c = [1, p2c[1]+p1c[1]*d, p2c[2]+p1c[2]*d]; 257 } 258 } 259 i = 1; 260 d = p2c[i] - p1c[i]; 261 if (Math.abs(d)<JXG.Math.eps) { 262 i = 2; 263 d = p2c[i] - p1c[i]; 264 } 265 266 this.coords = JXG.Math.Geometry.projectPointToLine(this, this.slideObject, this.board); 267 this.position = (this.coords.usrCoords[i] - p1c[i]) / d; 268 } 269 270 // Snap the glider point of the slider into its appropiate position 271 // First, recalculate the new value of this.position 272 // Second, call update(fromParent==true) to make the positioning snappier. 273 if (this.visProp.snapwidth>0.0 && Math.abs(this._smax-this._smin)>=JXG.Math.eps) { 274 if (this.position<0.0) this.position = 0.0; 275 if (this.position>1.0) this.position = 1.0; 276 277 v = this.position*(this._smax-this._smin)+this._smin; 278 v = Math.round(v/this.visProp.snapwidth)*this.visProp.snapwidth; 279 this.position = (v-this._smin)/(this._smax-this._smin); 280 this.update(true); 281 } 282 283 p1c = this.slideObject.point1.coords.usrCoords; 284 if (!this.slideObject.visProp.straightfirst && Math.abs(p1c[0])>JXG.Math.eps && this.position<0) { 285 this.coords.setCoordinates(JXG.COORDS_BY_USER, p1c); 286 this.position = 0; 287 } 288 p2c = this.slideObject.point2.coords.usrCoords; 289 if (!this.slideObject.visProp.straightlast && Math.abs(p2c[0])>JXG.Math.eps && this.position>1) { 290 this.coords.setCoordinates(JXG.COORDS_BY_USER, p2c); 291 this.position = 1; 292 } 293 294 295 } else if(this.slideObject.type == JXG.OBJECT_TYPE_TURTLE) { 296 this.updateConstraint(); // In case, the point is a constrained glider. 297 this.coords = JXG.Math.Geometry.projectPointToTurtle(this, this.slideObject, this.board); // side-effect: this.position is overwritten 298 } else if(this.slideObject.elementClass == JXG.OBJECT_CLASS_CURVE) { 299 this.updateConstraint(); // In case, the point is a constrained glider. 300 this.coords = JXG.Math.Geometry.projectPointToCurve(this, this.slideObject, this.board); // side-effect: this.position is overwritten 301 } else if(this.slideObject.elementClass == JXG.OBJECT_CLASS_POINT) { 302 this.coords = JXG.Math.Geometry.projectPointToPoint(this, this.slideObject, this.board); 303 } 304 }, 305 306 /** 307 * Update of a glider in case a parent element has been updated. That means the 308 * relative position of the glider stays the same. 309 * @private 310 */ 311 updateGliderFromParent: function() { 312 var p1c, p2c, r, d; 313 314 if(this.slideObject.elementClass == JXG.OBJECT_CLASS_CIRCLE) { 315 r = this.slideObject.Radius(); 316 this.coords.setCoordinates(JXG.COORDS_BY_USER, [ 317 this.slideObject.center.X() + r*Math.cos(this.position), 318 this.slideObject.center.Y() + r*Math.sin(this.position) 319 ]); 320 } else if(this.slideObject.elementClass == JXG.OBJECT_CLASS_LINE) { 321 p1c = this.slideObject.point1.coords; 322 p2c = this.slideObject.point2.coords; 323 d = p1c.distance(JXG.COORDS_BY_USER, p2c); 324 p1c = p1c.usrCoords.slice(0); 325 p2c = p2c.usrCoords.slice(0); 326 327 if (d==Number.POSITIVE_INFINITY) { // At least one point is an ideal point 328 d = 1.0/JXG.Math.eps; 329 if (Math.abs(p2c[0])<JXG.Math.eps) { // The second point is an ideal point 330 d /= JXG.Math.Geometry.distance([0,0,0], p2c); 331 p2c = [1, p1c[1]+p2c[1]*d, p1c[2]+p2c[2]*d]; 332 } else { // The first point is an ideal point 333 d /= JXG.Math.Geometry.distance([0,0,0], p1c); 334 p1c = [1, p2c[1]+p1c[1]*d, p2c[2]+p1c[2]*d]; 335 } 336 } 337 this.coords.setCoordinates(JXG.COORDS_BY_USER, [ 338 p1c[1] + this.position*(p2c[1] - p1c[1]), 339 p1c[2] + this.position*(p2c[2] - p1c[2]) 340 ]); 341 } else if(this.slideObject.type == JXG.OBJECT_TYPE_TURTLE) { 342 this.coords.setCoordinates(JXG.COORDS_BY_USER, [this.slideObject.Z(this.position), this.slideObject.X(this.position), this.slideObject.Y(this.position)]); 343 this.updateConstraint(); // In case, the point is a constrained glider. 344 this.coords = JXG.Math.Geometry.projectPointToTurtle(this, this.slideObject, this.board); // side-effect: this.position is overwritten 345 } else if(this.slideObject.elementClass == JXG.OBJECT_CLASS_CURVE) { 346 this.coords.setCoordinates(JXG.COORDS_BY_USER, [this.slideObject.Z(this.position), this.slideObject.X(this.position), this.slideObject.Y(this.position)]); 347 this.updateConstraint(); // In case, the point is a constrained glider. 348 this.coords = JXG.Math.Geometry.projectPointToCurve(this, this.slideObject, this.board); // side-effect: this.position is overwritten 349 } else if(this.slideObject.elementClass == JXG.OBJECT_CLASS_POINT) { 350 this.coords = JXG.Math.Geometry.projectPointToPoint(this, this.slideObject, this.board); 351 } 352 }, 353 354 /** 355 * Calls the renderer to update the drawing. 356 * @private 357 */ 358 updateRenderer: function () { 359 if (!this.needsUpdate) { return this; } 360 361 /* Call the renderer only if point is visible. */ 362 if(this.visProp.visible) { 363 var wasReal = this.isReal; 364 this.isReal = (!isNaN(this.coords.usrCoords[1] + this.coords.usrCoords[2])); 365 this.isReal = (Math.abs(this.coords.usrCoords[0])>JXG.Math.eps)?this.isReal:false; //Homogeneous coords: ideal point 366 if (this.isReal) { 367 if (wasReal!=this.isReal) { 368 this.board.renderer.show(this); 369 if(this.hasLabel && this.label.content.visProp.visible) this.board.renderer.show(this.label.content); 370 } 371 this.board.renderer.updatePoint(this); 372 } else { 373 if (wasReal!=this.isReal) { 374 this.board.renderer.hide(this); 375 if(this.hasLabel && this.label.content.visProp.visible) this.board.renderer.hide(this.label.content); 376 } 377 } 378 } 379 380 /* Update the label if visible. */ 381 if(this.hasLabel && this.visProp.visible && this.label.content && this.label.content.visProp.visible && this.isReal) { 382 this.label.content.update(); 383 this.board.renderer.updateText(this.label.content); 384 } 385 386 this.needsUpdate = false; 387 return this; 388 }, 389 390 /** 391 * Getter method for x, this is used by for CAS-points to access point coordinates. 392 * @return User coordinate of point in x direction. 393 * @type Number 394 */ 395 X: function () { 396 return this.coords.usrCoords[1]; 397 }, 398 399 /** 400 * Getter method for y, this is used by CAS-points to access point coordinates. 401 * @return User coordinate of point in y direction. 402 * @type Number 403 */ 404 Y: function () { 405 return this.coords.usrCoords[2]; 406 }, 407 408 /** 409 * Getter method for z, this is used by CAS-points to access point coordinates. 410 * @return User coordinate of point in z direction. 411 * @type Number 412 */ 413 Z: function () { 414 return this.coords.usrCoords[0]; 415 }, 416 417 /** 418 * New evaluation of the function term. 419 * This is required for CAS-points: Their XTerm() method is overwritten in {@link #addConstraint} 420 * @return User coordinate of point in x direction. 421 * @type Number 422 * @private 423 */ 424 XEval: function () { 425 return this.coords.usrCoords[1]; 426 }, 427 428 /** 429 * New evaluation of the function term. 430 * This is required for CAS-points: Their YTerm() method is overwritten in {@link #addConstraint} 431 * @return User coordinate of point in y direction. 432 * @type Number 433 * @private 434 */ 435 YEval: function () { 436 return this.coords.usrCoords[2]; 437 }, 438 439 /** 440 * New evaluation of the function term. 441 * This is required for CAS-points: Their ZTerm() method is overwritten in {@link #addConstraint} 442 * @return User coordinate of point in z direction. 443 * @type Number 444 * @private 445 */ 446 ZEval: function () { 447 return this.coords.usrCoords[0]; 448 }, 449 450 // documented in JXG.GeometryElement 451 bounds: function () { 452 return this.coords.usrCoords.slice(1).concat(this.coords.usrCoords.slice(1)); 453 }, 454 455 /** 456 * Getter method for the distance to a second point, this is required for CAS-elements. 457 * Here, function inlining seems to be worthwile (for plotting). 458 * @param {JXG.Point} point2 The point to which the distance shall be calculated. 459 * @return Distance in user coordinate to the given point 460 * @type Number 461 */ 462 Dist: function(point2) { 463 var sum, 464 c = point2.coords.usrCoords, 465 ucr = this.coords.usrCoords, 466 f; 467 468 f = ucr[0]-c[0]; 469 sum = f*f; 470 f = ucr[1]-c[1]; 471 sum += f*f; 472 f = ucr[2]-c[2]; 473 sum += f*f; 474 return Math.sqrt(sum); 475 }, 476 477 /** 478 * Move a point to its nearest grid point. 479 * The function uses the coords object of the point as 480 * its actual position. 481 **/ 482 handleSnapToGrid: function() { 483 var x, y, sX = this.visProp.snapsizex, sY = this.visProp.snapsizey; 484 485 if (this.visProp.snaptogrid) { 486 x = this.coords.usrCoords[1]; 487 y = this.coords.usrCoords[2]; 488 489 if (sX <= 0 && this.board.defaultAxes && this.board.defaultAxes.x.defaultTicks) { 490 sX = this.board.defaultAxes.x.defaultTicks.ticksDelta*(this.board.defaultAxes.x.defaultTicks.visProp.minorticks+1); 491 } 492 493 if (sY <= 0 && this.board.defaultAxes && this.board.defaultAxes.y.defaultTicks) { 494 sY = this.board.defaultAxes.y.defaultTicks.ticksDelta*(this.board.defaultAxes.y.defaultTicks.visProp.minorticks+1); 495 } 496 497 // if no valid snap sizes are available, don't change the coords. 498 if (sX > 0 && sY > 0) { 499 this.coords = new JXG.Coords(JXG.COORDS_BY_USER, [Math.round(x/sX)*sX, Math.round(y/sY)*sY], this.board); 500 } 501 } 502 return this; 503 }, 504 505 /** 506 * Let a point snap to the nearest point in distance of 507 * {@link JXG.Point#attractorDistance}. 508 * The function uses the coords object of the point as 509 * its actual position. 510 **/ 511 handleSnapToPoints: function() { 512 var el, pEl, pCoords, d=0.0, dMax=Infinity, c=null; 513 514 if (this.visProp.snaptopoints) { 515 for (el in this.board.objects) { 516 pEl = this.board.objects[el]; 517 if (pEl.elementClass==JXG.OBJECT_CLASS_POINT && pEl!==this && pEl.visProp.visible) { 518 pCoords = JXG.Math.Geometry.projectPointToPoint(this, pEl, this.board); 519 d = pCoords.distance(JXG.COORDS_BY_USER, this.coords); 520 if (d<this.visProp.attractordistance && d<dMax) { 521 dMax = d; 522 c = pCoords; 523 } 524 } 525 } 526 if (c!=null) { 527 this.coords.setCoordinates(JXG.COORDS_BY_USER, c.usrCoords); 528 } 529 } 530 531 return this; 532 }, 533 534 /** 535 * A point can change its type from free point to glider 536 * and vice versa. If it is given an array of attractor elements 537 * (attribute attractors) and the attribute attractorDistance 538 * then the pint will be made a glider if it less than attractorDistance 539 * apart from one of its attractor elements. 540 * If attractorDistance is equal to zero, the point stays in its 541 * current form. 542 **/ 543 handleAttractors: function() { 544 var len = this.visProp.attractors.length, 545 found, i, el, projCoords, d = 0.0; 546 547 if (this.visProp.attractordistance==0.0) { 548 return; 549 } 550 found = false; 551 for (i=0; i<len; i++) { 552 el = JXG.getRef(this.board, this.visProp.attractors[i]); 553 if (!JXG.exists(el) || el===this) { 554 continue; 555 } 556 if (el.elementClass==JXG.OBJECT_CLASS_POINT) { 557 projCoords = JXG.Math.Geometry.projectPointToPoint(this, el, this.board); 558 } else if (el.elementClass==JXG.OBJECT_CLASS_LINE) { 559 projCoords = JXG.Math.Geometry.projectPointToLine(this, el, this.board); 560 } else if (el.elementClass==JXG.OBJECT_CLASS_CIRCLE) { 561 projCoords = JXG.Math.Geometry.projectPointToCircle(this, el, this.board); 562 } else if (el.elementClass==JXG.OBJECT_CLASS_CURVE) { 563 projCoords = JXG.Math.Geometry.projectPointToCurve(this, el, this.board); 564 } else if (el.type == JXG.OBJECT_TYPE_TURTLE) { 565 projCoords = JXG.Math.Geometry.projectPointToTurtle(this, el, this.board); 566 } 567 d = projCoords.distance(JXG.COORDS_BY_USER, this.coords); 568 if (d<this.visProp.attractordistance) { 569 found = true; 570 if (!(this.type==JXG.OBJECT_TYPE_GLIDER && this.slideObject==el)) { 571 this.makeGlider(el); 572 } 573 break; 574 } else { 575 if (el==this.slideObject && d>=this.visProp.snatchdistance) { 576 this.type = JXG.OBJECT_TYPE_POINT; 577 } 578 } 579 } 580 581 /* 582 if (found==false) { 583 this.type = JXG.OBJECT_TYPE_POINT; 584 } 585 */ 586 return this; 587 }, 588 589 /** 590 * Sets x and y coordinate and calls the point's update() method. 591 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 592 * @param {Number} x x coordinate in screen/user units 593 * @param {Number} y y coordinate in screen/user units 594 * @param {Number} x optional: previous x coordinate in screen/user units (ignored) 595 * @param {Number} y optional: previous y coordinate in screen/user units (ignored) 596 */ 597 setPositionDirectly: function (method, x, y) { 598 var i, dx, dy, el, p, 599 oldCoords = this.coords, 600 newCoords; 601 602 this.coords = new JXG.Coords(method, [x, y], this.board); 603 604 this.handleSnapToGrid(); 605 this.handleSnapToPoints(); 606 this.handleAttractors(); 607 608 if(this.group.length != 0) { 609 // Update the initial coordinates. This is needed for free points 610 // that have a transformation bound to it. 611 dx = this.coords.usrCoords[1]-oldCoords.usrCoords[1]; 612 dy = this.coords.usrCoords[2]-oldCoords.usrCoords[2]; 613 for (i=0;i<this.group.length;i++) { 614 for (el in this.group[i].objects) { 615 p = this.group[i].objects[el]; 616 p.initialCoords = new JXG.Coords(JXG.COORDS_BY_USER, 617 [p.initialCoords.usrCoords[1]+dx,p.initialCoords.usrCoords[2]+dy], 618 this.board); 619 } 620 } 621 622 this.group[this.group.length-1].dX = this.coords.scrCoords[1] - oldCoords.scrCoords[1]; 623 this.group[this.group.length-1].dY = this.coords.scrCoords[2] - oldCoords.scrCoords[2]; 624 this.group[this.group.length-1].update(this); 625 } else { 626 // Update the initial coordinates. This is needed for free points 627 // that have a transformation bound to it. 628 for (i=this.transformations.length-1;i>=0;i--) { 629 if (method == JXG.COORDS_BY_SCREEN) { 630 newCoords = (new JXG.Coords(method, [x, y], this.board)).usrCoords; 631 } else { 632 newCoords = [1,x,y]; 633 } 634 this.initialCoords = new JXG.Coords(JXG.COORDS_BY_USER, 635 JXG.Math.matVecMult(JXG.Math.inverse(this.transformations[i].matrix), newCoords), 636 this.board); 637 } 638 this.update(); 639 } 640 641 return this; 642 }, 643 644 /** 645 * TODO 646 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 647 * @param {Number} x x coordinate in screen/user units 648 * @param {Number} y y coordinate in screen/user units 649 */ 650 setPositionByTransform: function (method, x, y) { 651 var t = this.board.create('transform', [x,y], {type:'translate'}); 652 653 if (this.transformations.length > 0 && this.transformations[this.transformations.length - 1].isNumericMatrix) { 654 this.transformations[this.transformations.length - 1].melt(t); 655 } else { 656 this.addTransform(this, t); 657 } 658 659 if (this.group.length == 0) { 660 this.update(); 661 } 662 return this; 663 }, 664 665 /** 666 * Sets x and y coordinate and calls the point's update() method. 667 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 668 * @param {Number} x x coordinate in screen/user units 669 * @param {Number} y y coordinate in screen/user units 670 */ 671 setPosition: function (method, x, y) { 672 this.setPositionDirectly(method, x, y); 673 return this; 674 }, 675 676 /** 677 * Sets the position of a glider relative to the defining elements of the {@link JXG.Point#slideObject}. 678 * @param {Number} x 679 * @returns {JXG.Point} Reference to the point element. 680 */ 681 setGliderPosition: function (x) { 682 if (this.type = JXG.OBJECT_TYPE_GLIDER) { 683 this.position = x; 684 this.board.update(); 685 } 686 687 return this; 688 }, 689 690 /** 691 * Convert the point to glider and update the construction. 692 * @param {String|Object} glideObject The Object the point will be bound to. 693 */ 694 makeGlider: function (glideObject) { 695 //var c = this.coords.usrCoords.slice(1); 696 this.slideObject = JXG.getRef(this.board, glideObject); 697 this.type = JXG.OBJECT_TYPE_GLIDER; 698 this.elType = 'glider'; 699 this.visProp.snapwidth = -1; // By default, deactivate snapWidth 700 this.slideObject.addChild(this); 701 this.isDraggable = true; 702 703 this.generatePolynomial = function() { 704 return this.slideObject.generatePolynomial(this); 705 }; 706 707 this.updateGlider(); // Determine the initial value of this.position 708 //this.moveTo(c); 709 //this.prepareUpdate().update().updateRenderer(); 710 return this; 711 }, 712 713 /** 714 * Converts a glider into a free point. 715 */ 716 free: function () { 717 var anc; 718 719 if (this.type !== JXG.OBJECT_TYPE_GLIDER) { 720 if (!this.isDraggable) { 721 this.isDraggable = true; 722 this.type = JXG.OBJECT_TYPE_POINT; 723 724 this.XEval = function () { 725 return this.coords.usrCoords[1]; 726 }; 727 728 this.YEval = function () { 729 return this.coords.usrCoords[2]; 730 }; 731 732 this.ZEval = function () { 733 return this.coords.usrCoords[0]; 734 }; 735 736 this.Xjc = null; 737 this.Yjc = null; 738 } else { 739 return; 740 } 741 } 742 743 for (anc in this.ancestors) { 744 delete this.ancestors[anc].descendants[this.id]; 745 delete this.ancestors[anc].childElements[this.id]; 746 } 747 748 this.ancestors = []; 749 this.slideObject = null; 750 this.elType = 'point'; 751 this.type = JXG.OBJECT_TYPE_POINT; 752 }, 753 754 /** 755 * Convert the point to CAS point and call update(). 756 * @param {Array} terms [[zterm], xterm, yterm] defining terms for the z, x and y coordinate. 757 * The z-coordinate is optional and it is used for homogeneaous coordinates. 758 * The coordinates may be either <ul> 759 * <li>a JavaScript function,</li> 760 * <li>a string containing GEONExT syntax. This string will be converted into a JavaScript 761 * function here,</li> 762 * <li>a Number</li> 763 * <li>a pointer to a slider object. This will be converted into a call of the Value()-method 764 * of this slider.</li> 765 * </ul> 766 * @see JXG.GeonextParser#geonext2JS 767 */ 768 addConstraint: function (terms) { 769 this.type = JXG.OBJECT_TYPE_CAS; 770 var newfuncs = [], 771 fs, i, v, t, 772 what = ['X', 'Y']; 773 774 this.isDraggable = false; 775 for (i=0;i<terms.length;i++) { 776 v = terms[i]; 777 if (typeof v=='string') { 778 // Convert GEONExT syntax into JavaScript syntax 779 //t = JXG.GeonextParser.geonext2JS(v, this.board); 780 //newfuncs[i] = new Function('','return ' + t + ';'); 781 newfuncs[i] = this.board.jc.snippet(v, true, null, true); 782 783 if (terms.length === 2) { 784 this[what[i] + 'jc'] = terms[i]; 785 } 786 } else if (typeof v=='function') { 787 newfuncs[i] = v; 788 } else if (typeof v=='number') { 789 newfuncs[i] = function(z){ return function() { return z; }; }(v); 790 } else if (typeof v == 'object' && typeof v.Value == 'function') { // Slider 791 newfuncs[i] = (function(a) { return function() { return a.Value(); };})(v); 792 } 793 794 newfuncs[i].origin = v; 795 } 796 if (terms.length==1) { // Intersection function 797 this.updateConstraint = function() { 798 var c = newfuncs[0](); 799 if (JXG.isArray(c)) { // Array 800 this.coords.setCoordinates(JXG.COORDS_BY_USER,c); 801 } else { // Coords object 802 this.coords = c; 803 } 804 }; 805 } else if (terms.length==2) { // Euclidean coordinates 806 this.XEval = newfuncs[0]; 807 this.YEval = newfuncs[1]; 808 fs = 'this.coords.setCoordinates(JXG.COORDS_BY_USER,[this.XEval(),this.YEval()]);'; 809 this.updateConstraint = new Function('',fs); 810 } else { // Homogeneous coordinates 811 this.ZEval = newfuncs[0]; 812 this.XEval = newfuncs[1]; 813 this.YEval = newfuncs[2]; 814 fs = 'this.coords.setCoordinates(JXG.COORDS_BY_USER,[this.ZEval(),this.XEval(),this.YEval()]);'; 815 this.updateConstraint = new Function('',fs); 816 } 817 818 if (!this.board.isSuspendedUpdate) { this.prepareUpdate().update().updateRenderer(); } 819 return this; 820 }, 821 822 /** 823 * Applies the transformations of the curve to {@link JXG.Point#baseElement}. 824 * @returns {JXG.Point} Reference to this point object. 825 */ 826 updateTransform: function () { 827 if (this.transformations.length==0 || this.baseElement==null) { 828 return this; 829 } 830 var c, i; 831 832 if (this===this.baseElement) { // case of bindTo 833 c = this.transformations[0].apply(this.baseElement, 'self'); 834 } else { // case of board.create('point',[baseElement,transform]); 835 c = this.transformations[0].apply(this.baseElement); 836 } 837 this.coords.setCoordinates(JXG.COORDS_BY_USER,c); 838 for (i = 1; i < this.transformations.length; i++) { 839 this.coords.setCoordinates(JXG.COORDS_BY_USER, this.transformations[i].apply(this)); 840 } 841 return this; 842 }, 843 844 /** 845 * Add transformations to this point. 846 * @param {JXG.GeometryElement} el TODO base element 847 * @param {JXG.Transform|Array} transform Either one {@link JXG.Transform} or an array of {@link JXG.Transform}s. 848 * @returns {JXG.Point} Reference to this point object. 849 */ 850 addTransform: function (el, transform) { 851 var i, 852 list = JXG.isArray(transform) ? transform : [transform], 853 len = list.length; 854 855 if (this.transformations.length === 0) { // There is only one baseElement possible 856 this.baseElement = el; 857 } 858 859 for (i = 0; i < len; i++) { 860 this.transformations.push(list[i]); 861 } 862 return this; 863 }, 864 865 /** 866 * Animate the point. 867 * @param {Number} direction The direction the glider is animated. Can be +1 or -1. 868 * @param {Number} stepCount The number of steps. 869 * @name Glider#startAnimation 870 * @see Glider#stopAnimation 871 * @function 872 */ 873 startAnimation: function(direction, stepCount) { 874 if((this.type == JXG.OBJECT_TYPE_GLIDER) && (typeof this.intervalCode == 'undefined')) { 875 this.intervalCode = window.setInterval('JXG.JSXGraph.boards[\'' + this.board.id + '\'].objects[\'' + this.id + '\']._anim(' 876 + direction + ', ' + stepCount + ')', 250); 877 if(typeof this.intervalCount == 'undefined') 878 this.intervalCount = 0; 879 } 880 return this; 881 }, 882 883 /** 884 * Stop animation. 885 * @name Glider#stopAnimation 886 * @see Glider#startAnimation 887 * @function 888 */ 889 stopAnimation: function() { 890 if(typeof this.intervalCode != 'undefined') { 891 window.clearInterval(this.intervalCode); 892 delete(this.intervalCode); 893 } 894 return this; 895 }, 896 897 /** 898 * Starts an animation which moves the point along a given path in given time. 899 * @param {Array,function} path The path the point is moved on. This can be either an array of arrays containing x and y values of the points of 900 * the path, or function taking the amount of elapsed time since the animation has started and returns an array containing a x and a y value or NaN. 901 * In case of NaN the animation stops. 902 * @param {Number} time The time in milliseconds in which to finish the animation 903 * @param {Object} [options] Optional settings for the animation:<ul><li>callback: A function that is called as soon as the animation is finished.</li></ul> 904 * @returns {JXG.Point} Reference to the point. 905 */ 906 moveAlong: function(path, time, options) { 907 options = options || {}; 908 var interpath = [], 909 delay = 35, 910 makeFakeFunction = function (i, j) { 911 return function() { 912 return path[i][j]; 913 }; 914 }, 915 p = [], i, neville, 916 steps = time/delay; 917 918 if (JXG.isArray(path)) { 919 for (i = 0; i < path.length; i++) { 920 if (JXG.isPoint(path[i])) { 921 p[i] = path[i]; 922 } else { 923 p[i] = { 924 elementClass: JXG.OBJECT_CLASS_POINT, 925 X: makeFakeFunction(i, 0), 926 Y: makeFakeFunction(i, 1) 927 }; 928 } 929 } 930 931 time = time || 0; 932 if (time === 0) { 933 this.setPosition(JXG.COORDS_BY_USER, p[p.length - 1].X(), p[p.length - 1].Y()); 934 return this.board.update(this); 935 } 936 937 neville = JXG.Math.Numerics.Neville(p); 938 for (i = 0; i < steps; i++) { 939 interpath[i] = []; 940 interpath[i][0] = neville[0]((steps - i) / steps * neville[3]()); 941 interpath[i][1] = neville[1]((steps - i) / steps * neville[3]()); 942 } 943 944 this.animationPath = interpath; 945 } else if (JXG.isFunction(path)) { 946 this.animationPath = path; 947 this.animationStart = new Date().getTime(); 948 } 949 this.animationCallback = options.callback; 950 951 this.board.addAnimation(this); 952 return this; 953 }, 954 955 /** 956 * Starts an animated point movement towards the given coordinates <tt>where</tt>. The animation is done after <tt>time</tt> milliseconds. 957 * If the second parameter is not given or is equal to 0, setPosition() is called, see #setPosition. 958 * @param {Array} where Array containing the x and y coordinate of the target location. 959 * @param {Number} [time] Number of milliseconds the animation should last. 960 * @param {Object} [options] Optional settings for the animation:<ul><li>callback: A function that is called as soon as the animation is finished.</li> 961 * <li>effect: animation effects like speed fade in and out. possible values are '<>' for speed increase on start and slow down at the end (default) 962 * and '--' for constant speed during the whole animation.</li></ul> 963 * @returns {JXG.Point} Reference to itself. 964 * @see #animate 965 */ 966 moveTo: function(where, time, options) { 967 if (typeof time == 'undefined' || time == 0) { 968 this.setPosition(JXG.COORDS_BY_USER, where[0], where[1]); 969 return this.board.update(this); 970 } 971 972 options = options || {}; 973 974 var delay = 35, 975 steps = Math.ceil(time/(delay * 1.0)), 976 coords = new Array(steps+1), 977 X = this.coords.usrCoords[1], 978 Y = this.coords.usrCoords[2], 979 dX = (where[0] - X), 980 dY = (where[1] - Y), 981 i, 982 stepFun = function (i) { 983 if (options.effect && options.effect == '<>') { 984 return Math.pow(Math.sin((i/(steps*1.0))*Math.PI/2.), 2); 985 } 986 return i/steps; 987 }; 988 989 if(Math.abs(dX) < JXG.Math.eps && Math.abs(dY) < JXG.Math.eps) 990 return this; 991 992 for(i=steps; i>=0; i--) { 993 coords[steps-i] = [X + dX * stepFun(i), Y+ dY * stepFun(i)]; 994 } 995 996 this.animationPath = coords; 997 this.animationCallback = options.callback; 998 this.board.addAnimation(this); 999 return this; 1000 }, 1001 1002 /** 1003 * Starts an animated point movement towards the given coordinates <tt>where</tt>. After arriving at <tt>where</tt> the point moves back to where it started. 1004 * The animation is done after <tt>time</tt> milliseconds. 1005 * @param {Array} where Array containing the x and y coordinate of the target location. 1006 * @param {Number} time Number of milliseconds the animation should last. 1007 * @param {Object} [options] Optional settings for the animation:<ul><li>callback: A function that is called as soon as the animation is finished.</li> 1008 * <li>effect: animation effects like speed fade in and out. possible values are '<>' for speed increase on start and slow down at the end (default) 1009 * and '--' for constant speed during the whole animation.</li><li>repeat: How often this animation should be repeated (default: 1)</li></ul> 1010 * @returns {JXG.Point} Reference to itself. 1011 * @see #animate 1012 */ 1013 visit: function(where, time, options) { 1014 // support legacy interface where the third parameter was the number of repeats 1015 if (typeof options == 'number') { 1016 options = {repeat: options}; 1017 } else { 1018 options = options || {}; 1019 if(typeof options.repeat == 'undefined') 1020 options.repeat = 1; 1021 } 1022 1023 var delay = 35, 1024 steps = Math.ceil(time/(delay*options.repeat)), 1025 coords = new Array(options.repeat*(steps+1)), 1026 X = this.coords.usrCoords[1], 1027 Y = this.coords.usrCoords[2], 1028 dX = (where[0] - X), 1029 dY = (where[1] - Y), 1030 i, j, 1031 stepFun = function (i) { 1032 var x = (i < steps/2 ? 2*i/steps : 2*(steps-i)/steps); 1033 if (options.effect && options.effect == '<>') { 1034 return Math.pow(Math.sin((x)*Math.PI/2.), 2); 1035 } 1036 return x; 1037 }; 1038 1039 for (j = 0; j < options.repeat; j++) { 1040 for(i = steps; i >= 0; i--) { 1041 coords[j*(steps+1) + steps-i] = [X + dX * stepFun(i), 1042 Y+ dY * stepFun(i)]; 1043 } 1044 } 1045 this.animationPath = coords; 1046 this.animationCallback = options.callback; 1047 this.board.addAnimation(this); 1048 return this; 1049 }, 1050 1051 /** 1052 * Animates a glider. Is called by the browser after startAnimation is called. 1053 * @param {Number} direction The direction the glider is animated. 1054 * @param {Number} stepCount The number of steps. 1055 * @see #startAnimation 1056 * @see #stopAnimation 1057 * @private 1058 */ 1059 _anim: function(direction, stepCount) { 1060 var distance, slope, dX, dY, alpha, startPoint, 1061 factor = 1, newX, radius; 1062 1063 this.intervalCount++; 1064 if(this.intervalCount > stepCount) 1065 this.intervalCount = 0; 1066 1067 if(this.slideObject.elementClass == JXG.OBJECT_CLASS_LINE) { 1068 distance = this.slideObject.point1.coords.distance(JXG.COORDS_BY_SCREEN, this.slideObject.point2.coords); 1069 slope = this.slideObject.getSlope(); 1070 if(slope != 'INF') { 1071 alpha = Math.atan(slope); 1072 dX = Math.round((this.intervalCount/stepCount) * distance*Math.cos(alpha)); 1073 dY = Math.round((this.intervalCount/stepCount) * distance*Math.sin(alpha)); 1074 } else { 1075 dX = 0; 1076 dY = Math.round((this.intervalCount/stepCount) * distance); 1077 } 1078 1079 if(direction < 0) { 1080 startPoint = this.slideObject.point2; 1081 if(this.slideObject.point2.coords.scrCoords[1] - this.slideObject.point1.coords.scrCoords[1] > 0) 1082 factor = -1; 1083 else if(this.slideObject.point2.coords.scrCoords[1] - this.slideObject.point1.coords.scrCoords[1] == 0) { 1084 if(this.slideObject.point2.coords.scrCoords[2] - this.slideObject.point1.coords.scrCoords[2] > 0) 1085 factor = -1; 1086 } 1087 } else { 1088 startPoint = this.slideObject.point1; 1089 if(this.slideObject.point1.coords.scrCoords[1] - this.slideObject.point2.coords.scrCoords[1] > 0) 1090 factor = -1; 1091 else if(this.slideObject.point1.coords.scrCoords[1] - this.slideObject.point2.coords.scrCoords[1] == 0) { 1092 if(this.slideObject.point1.coords.scrCoords[2] - this.slideObject.point2.coords.scrCoords[2] > 0) 1093 factor = -1; 1094 } 1095 } 1096 1097 this.coords.setCoordinates(JXG.COORDS_BY_SCREEN, [startPoint.coords.scrCoords[1] + factor*dX, 1098 startPoint.coords.scrCoords[2] + factor*dY]); 1099 } else if(this.slideObject.elementClass == JXG.OBJECT_CLASS_CURVE) { 1100 if(direction > 0) { 1101 newX = Math.round(this.intervalCount/stepCount * this.board.canvasWidth); 1102 } else { 1103 newX = Math.round((stepCount - this.intervalCount)/stepCount * this.board.canvasWidth); 1104 } 1105 1106 this.coords.setCoordinates(JXG.COORDS_BY_SCREEN, [newX, 0]); 1107 this.coords = JXG.Math.Geometry.projectPointToCurve(this, this.slideObject, this.board); 1108 } else if(this.slideObject.elementClass == JXG.OBJECT_CLASS_CIRCLE) { 1109 if(direction < 0) { 1110 alpha = this.intervalCount/stepCount * 2*Math.PI; 1111 } else { 1112 alpha = (stepCount - this.intervalCount)/stepCount * 2*Math.PI; 1113 } 1114 1115 radius = this.slideObject.Radius(); 1116 1117 this.coords.setCoordinates(JXG.COORDS_BY_USER, [this.slideObject.center.coords.usrCoords[1] + radius*Math.cos(alpha), 1118 this.slideObject.center.coords.usrCoords[2] + radius*Math.sin(alpha)]); 1119 } 1120 1121 this.board.update(this); 1122 return this; 1123 }, 1124 1125 /** 1126 * Set the style of a point. Used for GEONExT import and should not be used to set the point's face and size. 1127 * @param {Number} i Integer to determine the style. 1128 * @private 1129 */ 1130 setStyle: function(i) { 1131 var facemap = [ 1132 // 0-2 1133 'cross', 'cross', 'cross', 1134 // 3-6 1135 'circle', 'circle', 'circle', 'circle', 1136 // 7-9 1137 'square', 'square', 'square', 1138 // 10-12 1139 'plus', 'plus', 'plus' 1140 ], sizemap = [ 1141 // 0-2 1142 2, 3, 4, 1143 // 3-6 1144 1, 2, 3, 4, 1145 // 7-9 1146 2, 3, 4, 1147 // 10-12 1148 2, 3, 4 1149 ]; 1150 1151 this.visProp.face = facemap[i]; 1152 this.visProp.size = sizemap[i]; 1153 1154 this.board.renderer.changePointStyle(this); 1155 return this; 1156 }, 1157 1158 /** 1159 * All point faces can be defined with more than one name, e.g. a cross faced point can be given 1160 * by face equal to 'cross' or equal to 'x'. This method maps all possible values to fixed ones to 1161 * simplify if- and switch-clauses regarding point faces. The translation table is as follows: 1162 * <table> 1163 * <tr><th>Input</th><th>Output</th></tr> 1164 * <tr><td>cross, x</td><td>x</td></tr> 1165 * <tr><td>circle, o</td><td>o</td></tr> 1166 * <tr><td>square, []</td><td>[]</td></tr> 1167 * <tr><td>plus, +</td><td>+</td></tr> 1168 * <tr><td>diamond, <></td><td><></td></tr> 1169 * <tr><td>triangleup, a, ^</td><td>A</td></tr> 1170 * <tr><td>triangledown, v</td><td>v</td></tr> 1171 * <tr><td>triangleleft, <</td><td><</td></tr> 1172 * <tr><td>triangleright, ></td><td>></td></tr> 1173 * </table> 1174 * @param {String} s A string which should determine a valid point face. 1175 * @returns {String} Returns a normalized string or undefined if the given string is not a valid 1176 * point face. 1177 */ 1178 normalizeFace: function(s) { 1179 var map = { 1180 cross: 'x', 1181 x: 'x', 1182 circle: 'o', 1183 o: 'o', 1184 square: '[]', 1185 '[]': '[]', 1186 plus: '+', 1187 '+': '+', 1188 diamond: '<>', 1189 '<>': '<>', 1190 triangleup: '^', 1191 a: '^', 1192 '^': '^', 1193 triangledown: 'v', 1194 v: 'v', 1195 triangleleft: '<', 1196 '<': '<', 1197 triangleright: '>', 1198 '>': '>' 1199 }; 1200 1201 return map[s]; 1202 }, 1203 1204 /** 1205 * Remove the point from the drawing. This only removes the SVG or VML node of the point and its label from the renderer, to remove 1206 * the object completely you should use {@link JXG.Board#removeObject}. 1207 */ 1208 remove: function() { 1209 if (this.hasLabel) { 1210 this.board.renderer.remove(this.board.renderer.getElementById(this.label.content.id)); 1211 } 1212 this.board.renderer.remove(this.board.renderer.getElementById(this.id)); 1213 }, 1214 1215 // documented in GeometryElement 1216 getTextAnchor: function() { 1217 return this.coords; 1218 }, 1219 1220 // documented in GeometryElement 1221 getLabelAnchor: function() { 1222 return this.coords; 1223 }, 1224 1225 /** 1226 * Set the face of a point element. 1227 * @param {string} f String which determines the face of the point. See {@link JXG.GeometryElement#face} for a list of available faces. 1228 * @see JXG.GeometryElement#face 1229 */ 1230 face: function(f) { 1231 this.setProperty({face:f}); 1232 }, 1233 1234 /** 1235 * Set the size of a point element 1236 * @param {int} s Integer which determines the size of the point. 1237 * @see JXG.GeometryElement#size 1238 */ 1239 size: function(s) { 1240 this.setProperty({size:s}); 1241 }, 1242 1243 // already documented in GeometryElement 1244 cloneToBackground: function() { 1245 var copy = {}; 1246 1247 copy.id = this.id + 'T' + this.numTraces; 1248 this.numTraces++; 1249 1250 copy.coords = this.coords; 1251 copy.visProp = JXG.deepCopy(this.visProp, this.visProp.traceattributes, true); 1252 copy.visProp.layer = this.board.options.layer.trace; 1253 copy.elementClass = JXG.OBJECT_CLASS_POINT; 1254 copy.board = this.board; 1255 JXG.clearVisPropOld(copy); 1256 1257 this.board.renderer.drawPoint(copy); 1258 this.traces[copy.id] = copy.rendNode; 1259 1260 return this; 1261 }, 1262 1263 getParents: function () { 1264 var p = [this.Z(), this.X(), this.Y()]; 1265 1266 if (this.parents) { 1267 p = this.parents; 1268 } 1269 1270 if (this.type == JXG.OBJECT_TYPE_GLIDER) { 1271 p = [this.X(), this.Y(), this.slideObject.id]; 1272 1273 } 1274 1275 return p; 1276 } 1277 }); 1278 1279 1280 /** 1281 * @class This element is used to provide a constructor for a general point. A free point is created if the given parent elements are all numbers 1282 * and the property fixed is not set or set to false. If one or more parent elements is not a number but a string containing a GEONE<sub>x</sub>T 1283 * constraint or a function the point will be considered as constrained). That means that the user won't be able to change the point's 1284 * position directly. 1285 * @pseudo 1286 * @description 1287 * @name Point 1288 * @augments JXG.Point 1289 * @constructor 1290 * @type JXG.Point 1291 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1292 * @param {Number,string,function_Number,string,function_Number,string,function} z_,x,y Parent elements can be two or three elements of type number, a string containing a GEONE<sub>x</sub>T 1293 * constraint, or a function which takes no parameter and returns a number. Every parent element determines one coordinate. If a coordinate is 1294 * given by a number, the number determines the initial position of a free point. If given by a string or a function that coordinate will be constrained 1295 * that means the user won't be able to change the point's position directly by mouse because it will be calculated automatically depending on the string 1296 * or the function's return value. If two parent elements are given the coordinates will be interpreted as 2D affine euclidean coordinates, if three such 1297 * parent elements are given they will be interpreted as homogeneous coordinates. 1298 * @param {JXG.Point_JXG.Transformation} Point,Transformation A point can also be created providing a transformation. The resulting point is a clone of the base 1299 * point transformed by the given Transformation. {@see JXG.Transformation}. 1300 * @example 1301 * // Create a free point using affine euclidean coordinates 1302 * var p1 = board.create('point', [3.5, 2.0]); 1303 * </pre><div id="672f1764-7dfa-4abc-a2c6-81fbbf83e44b" style="width: 200px; height: 200px;"></div> 1304 * <script type="text/javascript"> 1305 * var board = JXG.JSXGraph.initBoard('672f1764-7dfa-4abc-a2c6-81fbbf83e44b', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false}); 1306 * var p1 = board.create('point', [3.5, 2.0]); 1307 * </script><pre> 1308 * @example 1309 * // Create a constrained point using anonymous function 1310 * var p2 = board.create('point', [3.5, function () { return p1.X(); }]); 1311 * </pre><div id="4fd4410c-3383-4e80-b1bb-961f5eeef224" style="width: 200px; height: 200px;"></div> 1312 * <script type="text/javascript"> 1313 * var fpex1_board = JXG.JSXGraph.initBoard('4fd4410c-3383-4e80-b1bb-961f5eeef224', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false}); 1314 * var fpex1_p1 = fpex1_board.create('point', [3.5, 2.0]); 1315 * var fpex1_p2 = fpex1_board.create('point', [3.5, function () { return fpex1_p1.X(); }]); 1316 * </script><pre> 1317 * @example 1318 * // Create a point using transformations 1319 * var trans = board.create('transform', [2, 0.5], {type:'scale'}); 1320 * var p3 = board.create('point', [p2, trans]); 1321 * </pre><div id="630afdf3-0a64-46e0-8a44-f51bd197bb8d" style="width: 400px; height: 400px;"></div> 1322 * <script type="text/javascript"> 1323 * var fpex2_board = JXG.JSXGraph.initBoard('630afdf3-0a64-46e0-8a44-f51bd197bb8d', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 1324 * var fpex2_trans = fpex2_board.create('transform', [2, 0.5], {type:'scale'}); 1325 * var fpex2_p2 = fpex2_board.create('point', [3.5, 2.0]); 1326 * var fpex2_p3 = fpex2_board.create('point', [fpex2_p2, fpex2_trans]); 1327 * </script><pre> 1328 */ 1329 JXG.createPoint = function(board, parents, attributes) { 1330 var el, isConstrained = false, i, attr; 1331 1332 attr = JXG.copyAttributes(attributes, board.options, 'point'); 1333 1334 for (i=0;i<parents.length;i++) { 1335 if (typeof parents[i]=='function' || typeof parents[i]=='string') { 1336 isConstrained = true; 1337 } 1338 } 1339 if (!isConstrained) { 1340 if ( (JXG.isNumber(parents[0])) && (JXG.isNumber(parents[1])) ) { 1341 el = new JXG.Point(board, parents, attr); 1342 if ( JXG.exists(attr["slideobject"]) ) { 1343 el.makeGlider(attr["slideobject"]); 1344 } else { 1345 el.baseElement = el; // Free point 1346 } 1347 el.isDraggable = true; 1348 } else if ( (typeof parents[0]=='object') && (typeof parents[1]=='object') ) { // Transformation 1349 el = new JXG.Point(board, [0,0], attr); 1350 el.addTransform(parents[0],parents[1]); 1351 el.isDraggable = false; 1352 1353 el.parents = [parents[0].id, parents[1].id]; 1354 } 1355 else {// Failure 1356 throw new Error("JSXGraph: Can't create point with parent types '" + 1357 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1358 "\nPossible parent types: [x,y], [z,x,y], [point,transformation]"); 1359 } 1360 1361 } else { 1362 el = new JXG.Point(board, [NaN, NaN], attr); 1363 el.addConstraint(parents); 1364 } 1365 1366 return el; 1367 }; 1368 1369 /** 1370 * @class This element is used to provide a constructor for a glider point. 1371 * @pseudo 1372 * @description A glider is a point which lives on another geometric element like a line, circle, curve, turtle. 1373 * @name Glider 1374 * @augments JXG.Point 1375 * @constructor 1376 * @type JXG.Point 1377 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1378 * @param {Number_Number_Number_JXG.GeometryElement} z_,x_,y_,GlideObject Parent elements can be two or three elements of type number and the object the glider lives on. 1379 * The coordinates are completely optional. If not given the origin is used. If you provide two numbers for coordinates they will be interpreted as affine euclidean 1380 * coordinates, otherwise they will be interpreted as homogeneous coordinates. In any case the point will be projected on the glide object. 1381 * @example 1382 * // Create a glider with user defined coordinates. If the coordinates are not on 1383 * // the circle (like in this case) the point will be projected onto the circle. 1384 * var p1 = board.create('point', [2.0, 2.0]); 1385 * var c1 = board.create('circle', [p1, 2.0]); 1386 * var p2 = board.create('glider', [2.0, 1.5, c1]); 1387 * </pre><div id="4f65f32f-e50a-4b50-9b7c-f6ec41652930" style="width: 300px; height: 300px;"></div> 1388 * <script type="text/javascript"> 1389 * var gpex1_board = JXG.JSXGraph.initBoard('4f65f32f-e50a-4b50-9b7c-f6ec41652930', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false}); 1390 * var gpex1_p1 = gpex1_board.create('point', [2.0, 2.0]); 1391 * var gpex1_c1 = gpex1_board.create('circle', [gpex1_p1, 2.0]); 1392 * var gpex1_p2 = gpex1_board.create('glider', [2.0, 1.5, gpex1_c1]); 1393 * </script><pre> 1394 * @example 1395 * // Create a glider with default coordinates (1,0,0). Same premises as above. 1396 * var p1 = board.create('point', [2.0, 2.0]); 1397 * var c1 = board.create('circle', [p1, 2.0]); 1398 * var p2 = board.create('glider', [c1]); 1399 * </pre><div id="4de7f181-631a-44b1-a12f-bc4d995609e8" style="width: 200px; height: 200px;"></div> 1400 * <script type="text/javascript"> 1401 * var gpex2_board = JXG.JSXGraph.initBoard('4de7f181-631a-44b1-a12f-bc4d995609e8', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false}); 1402 * var gpex2_p1 = gpex2_board.create('point', [2.0, 2.0]); 1403 * var gpex2_c1 = gpex2_board.create('circle', [gpex2_p1, 2.0]); 1404 * var gpex2_p2 = gpex2_board.create('glider', [gpex2_c1]); 1405 * </script><pre> 1406 */ 1407 JXG.createGlider = function(board, parents, attributes) { 1408 var el, 1409 attr = JXG.copyAttributes(attributes, board.options, 'point'); 1410 1411 if (parents.length === 1) { 1412 el = board.create('point', [0, 0], attr); 1413 } else { 1414 el = board.create('point', parents.slice(0, 2), attr); 1415 } 1416 1417 // eltype is set in here 1418 el.makeGlider(parents[parents.length-1]); 1419 1420 return el; 1421 }; 1422 1423 /** 1424 * @class This element is used to provide a constructor for an intersection point. 1425 * @pseudo 1426 * @description An intersection point is a point which lives on two Lines or Circles or one Line and one Circle at the same time, i.e. 1427 * an intersection point of the two elements. 1428 * @name Intersection 1429 * @augments JXG.Point 1430 * @constructor 1431 * @type JXG.Point 1432 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1433 * @param {JXG.Line,JXG.Circle_JXG.Line,JXG.Circle_Number} el1,el2,i The result will be a intersection point on el1 and el2. i determines the 1434 * intersection point if two points are available: <ul> 1435 * <li>i==0: use the positive square root,</li> 1436 * <li>i==1: use the negative square root.</li></ul> 1437 * @example 1438 * // Create an intersection point of circle and line 1439 * var p1 = board.create('point', [2.0, 2.0]); 1440 * var c1 = board.create('circle', [p1, 2.0]); 1441 * 1442 * var p2 = board.create('point', [2.0, 2.0]); 1443 * var p3 = board.create('point', [2.0, 2.0]); 1444 * var l1 = board.create('line', [p2, p3]); 1445 * 1446 * var i = board.create('intersection', [c1, l1, 0]); 1447 * </pre><div id="e5b0e190-5200-4bc3-b995-b6cc53dc5dc0" style="width: 300px; height: 300px;"></div> 1448 * <script type="text/javascript"> 1449 * var ipex1_board = JXG.JSXGraph.initBoard('e5b0e190-5200-4bc3-b995-b6cc53dc5dc0', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1450 * var ipex1_p1 = ipex1_board.create('point', [4.0, 4.0]); 1451 * var ipex1_c1 = ipex1_board.create('circle', [ipex1_p1, 2.0]); 1452 * var ipex1_p2 = ipex1_board.create('point', [1.0, 1.0]); 1453 * var ipex1_p3 = ipex1_board.create('point', [5.0, 3.0]); 1454 * var ipex1_l1 = ipex1_board.create('line', [ipex1_p2, ipex1_p3]); 1455 * var ipex1_i = ipex1_board.create('intersection', [ipex1_c1, ipex1_l1, 0]); 1456 * </script><pre> 1457 */ 1458 JXG.createIntersectionPoint = function(board, parents, attributes) { 1459 var el; 1460 if (parents.length>=3) { 1461 if(parents.length == 3) 1462 parents.push(null); 1463 el = board.create('point', [board.intersection(parents[0], parents[1], parents[2], parents[3])], attributes); 1464 } 1465 1466 parents[0].addChild(el); 1467 parents[1].addChild(el); 1468 1469 el.elType = 'intersection'; 1470 el.parents = [parents[0].id, parents[1].id, parents[2]]; 1471 1472 if (parents[3] != null) { 1473 el.parents.push(parents[3]); 1474 } 1475 1476 el.generatePolynomial = function () { 1477 var poly1 = parents[0].generatePolynomial(el); 1478 var poly2 = parents[1].generatePolynomial(el); 1479 1480 if((poly1.length == 0) || (poly2.length == 0)) 1481 return []; 1482 else 1483 return [poly1[0], poly2[0]]; 1484 }; 1485 1486 return el; 1487 }; 1488 1489 /** 1490 * @class This element is used to provide a constructor for the "other" intersection point. 1491 * @pseudo 1492 * @description An intersection point is a point which lives on two Lines or Circles or one Line and one Circle at the same time, i.e. 1493 * an intersection point of the two elements. Additionally, one intersection point is provided. The function returns the other intersection point. 1494 * @name OtherIntersection 1495 * @augments JXG.Point 1496 * @constructor 1497 * @type JXG.Point 1498 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1499 * @param {JXG.Line,JXG.Circle_JXG.Line,JXG.Circle_JXG.Point} el1,el2,p The result will be a intersection point on el1 and el2. i determines the 1500 * intersection point different from p: 1501 * @example 1502 * // Create an intersection point of circle and line 1503 * var p1 = board.create('point', [2.0, 2.0]); 1504 * var c1 = board.create('circle', [p1, 2.0]); 1505 * 1506 * var p2 = board.create('point', [2.0, 2.0]); 1507 * var p3 = board.create('point', [2.0, 2.0]); 1508 * var l1 = board.create('line', [p2, p3]); 1509 * 1510 * var i = board.create('intersection', [c1, l1, 0]); 1511 * var j = board.create('otherintersection', [c1, l1, i]); 1512 * </pre><div id="45e25f12-a1de-4257-a466-27a2ae73614c" style="width: 300px; height: 300px;"></div> 1513 * <script type="text/javascript"> 1514 * var ipex2_board = JXG.JSXGraph.initBoard('45e25f12-a1de-4257-a466-27a2ae73614c', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1515 * var ipex2_p1 = ipex2_board.create('point', [4.0, 4.0]); 1516 * var ipex2_c1 = ipex2_board.create('circle', [ipex2_p1, 2.0]); 1517 * var ipex2_p2 = ipex2_board.create('point', [1.0, 1.0]); 1518 * var ipex2_p3 = ipex2_board.create('point', [5.0, 3.0]); 1519 * var ipex2_l1 = ipex2_board.create('line', [ipex2_p2, ipex2_p3]); 1520 * var ipex2_i = ipex2_board.create('intersection', [ipex2_c1, ipex2_l1, 0], {name:'D'}); 1521 * var ipex2_j = ipex2_board.create('otherintersection', [ipex2_c1, ipex2_l1, ipex2_i], {name:'E'}); 1522 * </script><pre> 1523 */ 1524 JXG.createOtherIntersectionPoint = function(board, parents, attributes) { 1525 var el; 1526 if (parents.length!=3 || 1527 !JXG.isPoint(parents[2]) || 1528 (parents[0].elementClass != JXG.OBJECT_CLASS_LINE && parents[0].elementClass != JXG.OBJECT_CLASS_CIRCLE) || 1529 (parents[1].elementClass != JXG.OBJECT_CLASS_LINE && parents[1].elementClass != JXG.OBJECT_CLASS_CIRCLE) ) { 1530 // Failure 1531 throw new Error("JSXGraph: Can't create 'other intersection point' with parent types '" + 1532 (typeof parents[0]) + "', '" + (typeof parents[1])+ "'and '" + (typeof parents[2]) + "'." + 1533 "\nPossible parent types: [circle|line,circle|line,point]"); 1534 } 1535 else { 1536 el = board.create('point', [board.otherIntersection(parents[0], parents[1], parents[2])], attributes); 1537 } 1538 el.elType = 'otherintersection'; 1539 el.parents = [parents[0].id, parents[1].id, parents[2]]; 1540 1541 parents[0].addChild(el); 1542 parents[1].addChild(el); 1543 1544 el.generatePolynomial = function () { 1545 var poly1 = parents[0].generatePolynomial(el); 1546 var poly2 = parents[1].generatePolynomial(el); 1547 1548 if((poly1.length == 0) || (poly2.length == 0)) 1549 return []; 1550 else 1551 return [poly1[0], poly2[0]]; 1552 }; 1553 1554 return el; 1555 }; 1556 1557 1558 JXG.JSXGraph.registerElement('point',JXG.createPoint); 1559 JXG.JSXGraph.registerElement('glider', JXG.createGlider); 1560 JXG.JSXGraph.registerElement('intersection', JXG.createIntersectionPoint); 1561 JXG.JSXGraph.registerElement('otherintersection', JXG.createOtherIntersectionPoint); 1562