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