1 /* 2 Copyright 2008-2013 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 dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/> 29 and <http://opensource.org/licenses/MIT/>. 30 */ 31 32 33 /*global JXG: true, define: true, console: true, window: true*/ 34 /*jslint nomen: true, plusplus: true*/ 35 36 /* depends: 37 jxg 38 options 39 math/math 40 math/geometry 41 math/numerics 42 base/coords 43 base/constants 44 base/element 45 parser/geonext 46 utils/type 47 elements: 48 transform 49 */ 50 51 /** 52 * @fileoverview The geometry object Point is defined in this file. Point stores all 53 * style and functional properties that are required to draw and move a point on 54 * a board. 55 */ 56 57 define([ 58 'jxg', 'options', 'math/math', 'math/geometry', 'math/numerics', 'base/coords', 'base/constants', 'base/element', 59 'parser/geonext', 'utils/type', 'base/transformation' 60 ], function (JXG, Options, Mat, Geometry, Numerics, Coords, Const, GeometryElement, GeonextParser, Type, Transform) { 61 62 "use strict"; 63 64 /** 65 * A point is the basic geometric element. Based on points lines and circles can be constructed which can be intersected 66 * which in turn are points again which can be used to construct new lines, circles, polygons, etc. This class holds methods for 67 * all kind of points like free points, gliders, and intersection points. 68 * @class Creates a new point object. Do not use this constructor to create a point. Use {@link JXG.Board#create} with 69 * type {@link Point}, {@link Glider}, or {@link Intersection} instead. 70 * @augments JXG.GeometryElement 71 * @param {string|JXG.Board} board The board the new point is drawn on. 72 * @param {Array} coordinates An array with the affine user coordinates of the point. 73 * @param {Object} attributes An object containing visual properties like in {@link JXG.Options#point} and 74 * {@link JXG.Options#elements}, and optional a name and a id. 75 * @see JXG.Board#generateName 76 * @see JXG.Board#addPoint 77 */ 78 JXG.Point = function (board, coordinates, attributes) { 79 this.constructor(board, attributes, Const.OBJECT_TYPE_POINT, Const.OBJECT_CLASS_POINT); 80 81 if (!Type.exists(coordinates)) { 82 coordinates = [0, 0]; 83 } 84 85 /** 86 * Coordinates of the point. 87 * @type JXG.Coords 88 * @private 89 */ 90 this.coords = new Coords(Const.COORDS_BY_USER, coordinates, this.board); 91 this.initialCoords = new Coords(Const.COORDS_BY_USER, coordinates, this.board); 92 93 /** 94 * Relative position on a line if point is a glider on a line. 95 * @type Number 96 * @private 97 */ 98 this.position = null; 99 100 /** 101 * Determines whether the point slides on a polygon if point is a glider. 102 * @type boolean 103 * @default false 104 * @private 105 */ 106 this.onPolygon = false; 107 108 /** 109 * When used as a glider this member stores the object, where to glide on. To set the object to glide on use the method 110 * {@link JXG.Point#makeGlider} and DO NOT set this property directly as it will break the dependency tree. 111 * @type JXG.GeometryElement 112 * @name Glider#slideObject 113 */ 114 this.slideObject = null; 115 116 /** 117 * List of elements the point is bound to, i.e. the point glides on. 118 * Only the last entry is active. 119 * Use {@link JXG.Point#popSlideObject} to remove the currently active slideObject. 120 */ 121 this.slideObjects = []; 122 123 /** 124 * A {@link JXG.Point#updateGlider} call is usually followed by a general {@link JXG.Board#update} which calls 125 * {@link JXG.Point#updateGliderFromParent}. To prevent double updates, {@link JXG.Point#needsUpdateFromParent} 126 * is set to false in updateGlider() and reset to true in the following call to 127 * {@link JXG.Point#updateGliderFromParent} 128 * @type {Boolean} 129 */ 130 this.needsUpdateFromParent = true; 131 132 this.Xjc = null; 133 this.Yjc = null; 134 135 // documented in GeometryElement 136 this.methodMap = Type.deepCopy(this.methodMap, { 137 move: 'moveTo', 138 moveTo: 'moveTo', 139 moveAlong: 'moveAlong', 140 visit: 'visit', 141 glide: 'makeGlider', 142 makeGlider: 'makeGlider', 143 X: 'X', 144 Y: 'Y', 145 free: 'free', 146 setPosition: 'setGliderPosition', 147 setGliderPosition: 'setGliderPosition', 148 addConstraint: 'addConstraint', 149 dist: 'Dist', 150 onPolygon: 'onPolygon' 151 }); 152 153 /** 154 * Stores the groups of this point in an array of Group. 155 * @type array 156 * @see JXG.Group 157 * @private 158 */ 159 this.group = []; 160 161 this.elType = 'point'; 162 163 /* Register point at board. */ 164 this.id = this.board.setId(this, 'P'); 165 this.board.renderer.drawPoint(this); 166 this.board.finalizeAdding(this); 167 168 this.createLabel(); 169 }; 170 171 /** 172 * Inherits here from {@link JXG.GeometryElement}. 173 */ 174 JXG.Point.prototype = new GeometryElement(); 175 176 177 JXG.extend(JXG.Point.prototype, /** @lends JXG.Point.prototype */ { 178 /** 179 * Checks whether (x,y) is near the point. 180 * @param {Number} x Coordinate in x direction, screen coordinates. 181 * @param {Number} y Coordinate in y direction, screen coordinates. 182 * @returns {Boolean} True if (x,y) is near the point, False otherwise. 183 * @private 184 */ 185 hasPoint: function (x, y) { 186 var coordsScr = this.coords.scrCoords, r; 187 r = parseFloat(this.visProp.size) + parseFloat(this.visProp.strokewidth) * 0.5; 188 if (r < this.board.options.precision.hasPoint) { 189 r = this.board.options.precision.hasPoint; 190 } 191 192 return ((Math.abs(coordsScr[1] - x) < r + 2) && (Math.abs(coordsScr[2] - y)) < r + 2); 193 }, 194 195 /** 196 * Dummy function for unconstrained points or gliders. 197 * @private 198 */ 199 updateConstraint: function () { 200 return this; 201 }, 202 203 /** 204 * Updates the position of the point. 205 */ 206 update: function (fromParent) { 207 if (!this.needsUpdate) { 208 return this; 209 } 210 211 if (!Type.exists(fromParent)) { 212 fromParent = false; 213 } 214 215 /* 216 * We need to calculate the new coordinates no matter of the points visibility because 217 * a child could be visible and depend on the coordinates of the point (e.g. perpendicular). 218 * 219 * Check if point is a glider and calculate new coords in dependency of this.slideObject. 220 * This function is called with fromParent==true for example if 221 * the defining elements of the line or circle have been changed. 222 */ 223 if (this.type === Const.OBJECT_TYPE_GLIDER) { 224 if (fromParent) { 225 this.updateGliderFromParent(); 226 } else { 227 this.updateGlider(); 228 } 229 } 230 231 /** 232 * If point is a calculated point, call updateConstraint() to calculate new coords. 233 * The second test is for dynamic axes. 234 */ 235 if (this.type === Const.OBJECT_TYPE_CAS || this.type === Const.OBJECT_TYPE_INTERSECTION || this.type === Const.OBJECT_TYPE_AXISPOINT) { 236 this.updateConstraint(); 237 } 238 239 this.updateTransform(); 240 241 if (this.visProp.trace) { 242 this.cloneToBackground(true); 243 } 244 245 return this; 246 }, 247 248 /** 249 * Update of glider in case of dragging the glider or setting the postion of the glider. 250 * The relative position of the glider has to be updated. 251 * If the second point is an ideal point, then -1 < this.position < 1, 252 * this.position==+/-1 equals point2, this.position==0 equals point1 253 * 254 * If the first point is an ideal point, then 0 < this.position < 2 255 * this.position==0 or 2 equals point1, this.position==1 equals point2 256 * 257 * @private 258 */ 259 updateGlider: function () { 260 var i, p1c, p2c, d, v, poly, cc, pos, sgn, 261 alpha, beta, angle, 262 cp, c, invMat, newCoords, newPos, 263 doRound = false, 264 slide = this.slideObject; 265 266 this.needsUpdateFromParent = false; 267 268 if (slide.elementClass === Const.OBJECT_CLASS_CIRCLE) { 269 //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToCircle(this, slide, this.board).usrCoords, false); 270 newCoords = Geometry.projectPointToCircle(this, slide, this.board); 271 newPos = Geometry.rad([slide.center.X() + 1.0, slide.center.Y()], slide.center, this); 272 } else if (slide.elementClass === Const.OBJECT_CLASS_LINE) { 273 /* 274 * onPolygon==true: the point is a slider on a segment and this segment is one of the 275 * "borders" of a polygon. 276 * This is a GEONExT feature. 277 */ 278 if (this.onPolygon) { 279 p1c = slide.point1.coords.usrCoords; 280 p2c = slide.point2.coords.usrCoords; 281 i = 1; 282 d = p2c[i] - p1c[i]; 283 284 if (Math.abs(d) < Mat.eps) { 285 i = 2; 286 d = p2c[i] - p1c[i]; 287 } 288 289 cc = Geometry.projectPointToLine(this, slide, this.board); 290 pos = (cc.usrCoords[i] - p1c[i]) / d; 291 poly = slide.parentPolygon; 292 293 if (pos < 0) { 294 for (i = 0; i < poly.borders.length; i++) { 295 if (slide === poly.borders[i]) { 296 slide = poly.borders[(i - 1 + poly.borders.length) % poly.borders.length]; 297 break; 298 } 299 } 300 } else if (pos > 1.0) { 301 for (i = 0; i < poly.borders.length; i++) { 302 if (slide === poly.borders[i]) { 303 slide = poly.borders[(i + 1 + poly.borders.length) % poly.borders.length]; 304 break; 305 } 306 } 307 } 308 309 // If the slide object has changed, save the change to the glider. 310 if (slide.id !== this.slideObject.id) { 311 this.slideObject = slide; 312 } 313 } 314 315 p1c = slide.point1.coords; 316 p2c = slide.point2.coords; 317 318 // Distance between the two defining points 319 d = p1c.distance(Const.COORDS_BY_USER, p2c); 320 321 // The defining points are identical 322 if (d < Mat.eps) { 323 //this.coords.setCoordinates(Const.COORDS_BY_USER, p1c); 324 newCoords = p1c; 325 doRound = true; 326 newPos = 0.0; 327 } else { 328 //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToLine(this, slide, this.board).usrCoords, false); 329 newCoords = Geometry.projectPointToLine(this, slide, this.board); 330 p1c = p1c.usrCoords.slice(0); 331 p2c = p2c.usrCoords.slice(0); 332 333 // The second point is an ideal point 334 if (Math.abs(p2c[0]) < Mat.eps) { 335 i = 1; 336 d = p2c[i]; 337 338 if (Math.abs(d) < Mat.eps) { 339 i = 2; 340 d = p2c[i]; 341 } 342 343 d = (newCoords.usrCoords[i] - p1c[i]) / d; 344 sgn = (d >= 0) ? 1 : -1; 345 d = Math.abs(d); 346 newPos = sgn * d / (d + 1); 347 348 // The first point is an ideal point 349 } else if (Math.abs(p1c[0]) < Mat.eps) { 350 i = 1; 351 d = p1c[i]; 352 353 if (Math.abs(d) < Mat.eps) { 354 i = 2; 355 d = p1c[i]; 356 } 357 358 d = (newCoords.usrCoords[i] - p2c[i]) / d; 359 360 // 1.0 - d/(1-d); 361 if (d < 0.0) { 362 newPos = (1 - 2.0 * d) / (1.0 - d); 363 } else { 364 newPos = 1 / (d + 1); 365 } 366 } else { 367 i = 1; 368 d = p2c[i] - p1c[i]; 369 370 if (Math.abs(d) < Mat.eps) { 371 i = 2; 372 d = p2c[i] - p1c[i]; 373 } 374 newPos = (newCoords.usrCoords[i] - p1c[i]) / d; 375 } 376 } 377 378 // Snap the glider point of the slider into its appropiate position 379 // First, recalculate the new value of this.position 380 // Second, call update(fromParent==true) to make the positioning snappier. 381 if (this.visProp.snapwidth > 0.0 && Math.abs(this._smax - this._smin) >= Mat.eps) { 382 newPos = Math.max(Math.min(newPos, 1), 0); 383 384 v = newPos * (this._smax - this._smin) + this._smin; 385 v = Math.round(v / this.visProp.snapwidth) * this.visProp.snapwidth; 386 newPos = (v - this._smin) / (this._smax - this._smin); 387 this.update(true); 388 } 389 390 p1c = slide.point1.coords; 391 if (!slide.visProp.straightfirst && Math.abs(p1c.usrCoords[0]) > Mat.eps && newPos < 0) { 392 //this.coords.setCoordinates(Const.COORDS_BY_USER, p1c); 393 newCoords = p1c; 394 doRound = true; 395 newPos = 0; 396 } 397 398 p2c = slide.point2.coords; 399 if (!slide.visProp.straightlast && Math.abs(p2c.usrCoords[0]) > Mat.eps && newPos > 1) { 400 //this.coords.setCoordinates(Const.COORDS_BY_USER, p2c); 401 newCoords = p2c; 402 doRound = true; 403 newPos = 1; 404 } 405 } else if (slide.type === Const.OBJECT_TYPE_TURTLE) { 406 // In case, the point is a constrained glider. 407 // side-effect: this.position is overwritten 408 this.updateConstraint(); 409 //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToTurtle(this, slide, this.board).usrCoords, false); 410 newCoords = Geometry.projectPointToTurtle(this, slide, this.board); 411 newPos = this.position; // save position for the overwriting below 412 } else if (slide.elementClass === Const.OBJECT_CLASS_CURVE) { 413 if ((slide.type === Const.OBJECT_TYPE_ARC || 414 slide.type === Const.OBJECT_TYPE_SECTOR)) { 415 //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToCircle(this, slide, this.board).usrCoords, false); 416 newCoords = Geometry.projectPointToCircle(this, slide, this.board); 417 418 angle = Geometry.rad(slide.radiuspoint, slide.center, this); 419 alpha = 0.0; 420 beta = Geometry.rad(slide.radiuspoint, slide.center, slide.anglepoint); 421 newPos = angle; 422 423 if ((slide.visProp.type === 'minor' && beta > Math.PI) || 424 (slide.visProp.type === 'major' && beta < Math.PI)) { 425 alpha = beta; 426 beta = 2 * Math.PI; 427 } 428 429 // Correct the position if we are outside of the sector/arc 430 if (angle < alpha || angle > beta) { 431 newPos = beta; 432 433 if ((angle < alpha && angle > alpha * 0.5) || (angle > beta && angle > beta * 0.5 + Math.PI)) { 434 newPos = alpha; 435 } 436 this.updateGliderFromParent(); 437 } 438 439 } else { 440 // In case, the point is a constrained glider. 441 this.updateConstraint(); 442 443 if (slide.transformations.length > 0) { 444 slide.updateTransformMatrix(); 445 invMat = Mat.inverse(slide.transformMat); 446 c = Mat.matVecMult(invMat, this.coords.usrCoords); 447 448 cp = (new Coords(Const.COORDS_BY_USER, c, this.board)).usrCoords; 449 c = Geometry.projectCoordsToCurve(cp[1], cp[2], this.position || 0, slide, this.board); 450 451 newCoords = c[0]; 452 newPos = c[1]; 453 } else { 454 // side-effect: this.position is overwritten 455 //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToCurve(this, slide, this.board).usrCoords, false); 456 newCoords = Geometry.projectPointToCurve(this, slide, this.board); 457 newPos = this.position; // save position for the overwriting below 458 } 459 } 460 } else if (slide.elementClass === Const.OBJECT_CLASS_POINT) { 461 //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToPoint(this, slide, this.board).usrCoords, false); 462 newCoords = Geometry.projectPointToPoint(this, slide, this.board); 463 newPos = this.position; // save position for the overwriting below 464 } 465 466 this.coords.setCoordinates(Const.COORDS_BY_USER, newCoords.usrCoords, doRound); 467 this.position = newPos; 468 }, 469 470 /** 471 * Update of a glider in case a parent element has been updated. That means the 472 * relative position of the glider stays the same. 473 * @private 474 */ 475 updateGliderFromParent: function () { 476 var p1c, p2c, r, lbda, c, 477 slide = this.slideObject, alpha; 478 479 if (!this.needsUpdateFromParent) { 480 this.needsUpdateFromParent = true; 481 return; 482 } 483 484 if (slide.elementClass === Const.OBJECT_CLASS_CIRCLE) { 485 r = slide.Radius(); 486 c = [ 487 slide.center.X() + r * Math.cos(this.position), 488 slide.center.Y() + r * Math.sin(this.position) 489 ]; 490 } else if (slide.elementClass === Const.OBJECT_CLASS_LINE) { 491 p1c = slide.point1.coords.usrCoords; 492 p2c = slide.point2.coords.usrCoords; 493 494 // The second point is an ideal point 495 if (Math.abs(p2c[0]) < Mat.eps) { 496 lbda = Math.min(Math.abs(this.position), 1 - Mat.eps); 497 lbda /= (1.0 - lbda); 498 499 if (this.position < 0) { 500 lbda = -lbda; 501 } 502 503 c = [ 504 p1c[0] + lbda * p2c[0], 505 p1c[1] + lbda * p2c[1], 506 p1c[2] + lbda * p2c[2] 507 ]; 508 // The first point is an ideal point 509 } else if (Math.abs(p1c[0]) < Mat.eps) { 510 lbda = Math.max(this.position, Mat.eps); 511 lbda = Math.min(lbda, 2 - Mat.eps); 512 513 if (lbda > 1) { 514 lbda = (lbda - 1) / (lbda - 2); 515 } else { 516 lbda = (1 - lbda) / lbda; 517 } 518 519 c = [ 520 p2c[0] + lbda * p1c[0], 521 p2c[1] + lbda * p1c[1], 522 p2c[2] + lbda * p1c[2] 523 ]; 524 } else { 525 lbda = this.position; 526 c = [ 527 p1c[0] + lbda * (p2c[0] - p1c[0]), 528 p1c[1] + lbda * (p2c[1] - p1c[1]), 529 p1c[2] + lbda * (p2c[2] - p1c[2]) 530 ]; 531 } 532 } else if (slide.type === Const.OBJECT_TYPE_TURTLE) { 533 this.coords.setCoordinates(Const.COORDS_BY_USER, [slide.Z(this.position), slide.X(this.position), slide.Y(this.position)]); 534 // In case, the point is a constrained glider. 535 // side-effect: this.position is overwritten: 536 this.updateConstraint(); 537 c = Geometry.projectPointToTurtle(this, slide, this.board).usrCoords; 538 } else if (slide.elementClass === Const.OBJECT_CLASS_CURVE) { 539 this.coords.setCoordinates(Const.COORDS_BY_USER, [slide.Z(this.position), slide.X(this.position), slide.Y(this.position)]); 540 541 if (slide.type === Const.OBJECT_TYPE_ARC || slide.type === Const.OBJECT_TYPE_SECTOR) { 542 alpha = Geometry.rad([slide.center.X() + 1, slide.center.Y()], slide.center, slide.radiuspoint); 543 r = slide.Radius(); 544 c = [ 545 slide.center.X() + r * Math.cos(this.position + alpha), 546 slide.center.Y() + r * Math.sin(this.position + alpha) 547 ]; 548 } else { 549 // In case, the point is a constrained glider. 550 // side-effect: this.position is overwritten 551 this.updateConstraint(); 552 c = Geometry.projectPointToCurve(this, slide, this.board).usrCoords; 553 } 554 555 } else if (slide.elementClass === Const.OBJECT_CLASS_POINT) { 556 c = Geometry.projectPointToPoint(this, slide, this.board).usrCoords; 557 } 558 559 this.coords.setCoordinates(Const.COORDS_BY_USER, c, false); 560 }, 561 562 /** 563 * Calls the renderer to update the drawing. 564 * @private 565 */ 566 updateRenderer: function () { 567 var wasReal; 568 569 if (!this.needsUpdate) { 570 return this; 571 } 572 573 /* Call the renderer only if point is visible. */ 574 if (this.visProp.visible && this.visProp.size > 0) { 575 wasReal = this.isReal; 576 this.isReal = (!isNaN(this.coords.usrCoords[1] + this.coords.usrCoords[2])); 577 //Homogeneous coords: ideal point 578 this.isReal = (Math.abs(this.coords.usrCoords[0]) > Mat.eps) ? this.isReal : false; 579 580 if (this.isReal) { 581 if (wasReal !== this.isReal) { 582 this.board.renderer.show(this); 583 584 if (this.hasLabel && this.label.visProp.visible) { 585 this.board.renderer.show(this.label); 586 } 587 } 588 this.board.renderer.updatePoint(this); 589 } else { 590 if (wasReal !== this.isReal) { 591 this.board.renderer.hide(this); 592 593 if (this.hasLabel && this.label.visProp.visible) { 594 this.board.renderer.hide(this.label); 595 } 596 } 597 } 598 } 599 600 /* Update the label if visible. */ 601 if (this.hasLabel && this.visProp.visible && this.label && this.label.visProp.visible && this.isReal) { 602 this.label.update(); 603 this.board.renderer.updateText(this.label); 604 } 605 606 this.needsUpdate = false; 607 return this; 608 }, 609 610 /** 611 * Getter method for x, this is used by for CAS-points to access point coordinates. 612 * @returns {Number} User coordinate of point in x direction. 613 */ 614 X: function () { 615 return this.coords.usrCoords[1]; 616 }, 617 618 /** 619 * Getter method for y, this is used by CAS-points to access point coordinates. 620 * @returns {Number} User coordinate of point in y direction. 621 */ 622 Y: function () { 623 return this.coords.usrCoords[2]; 624 }, 625 626 /** 627 * Getter method for z, this is used by CAS-points to access point coordinates. 628 * @returns {Number} User coordinate of point in z direction. 629 */ 630 Z: function () { 631 return this.coords.usrCoords[0]; 632 }, 633 634 /** 635 * New evaluation of the function term. 636 * This is required for CAS-points: Their XTerm() method is overwritten in {@link #addConstraint} 637 * @returns {Number} User coordinate of point in x direction. 638 * @private 639 */ 640 XEval: function () { 641 return this.coords.usrCoords[1]; 642 }, 643 644 /** 645 * New evaluation of the function term. 646 * This is required for CAS-points: Their YTerm() method is overwritten in {@link #addConstraint} 647 * @returns {Number} User coordinate of point in y direction. 648 * @private 649 */ 650 YEval: function () { 651 return this.coords.usrCoords[2]; 652 }, 653 654 /** 655 * New evaluation of the function term. 656 * This is required for CAS-points: Their ZTerm() method is overwritten in {@link #addConstraint} 657 * @returns {Number} User coordinate of point in z direction. 658 * @private 659 */ 660 ZEval: function () { 661 return this.coords.usrCoords[0]; 662 }, 663 664 // documented in JXG.GeometryElement 665 bounds: function () { 666 return this.coords.usrCoords.slice(1).concat(this.coords.usrCoords.slice(1)); 667 }, 668 669 /** 670 * Getter method for the distance to a second point, this is required for CAS-elements. 671 * Here, function inlining seems to be worthwile (for plotting). 672 * @param {JXG.Point} point2 The point to which the distance shall be calculated. 673 * @returns {Number} Distance in user coordinate to the given point 674 */ 675 Dist: function (point2) { 676 var sum, f, 677 r = NaN, 678 c = point2.coords.usrCoords, 679 ucr = this.coords.usrCoords; 680 681 if (this.isReal && point2.isReal) { 682 f = ucr[0] - c[0]; 683 sum = f * f; 684 f = ucr[1] - c[1]; 685 sum += f * f; 686 f = ucr[2] - c[2]; 687 sum += f * f; 688 689 r = Math.sqrt(sum); 690 } 691 692 return r; 693 }, 694 695 snapToGrid: function () { 696 return this.handleSnapToGrid(); 697 }, 698 699 /** 700 * Move a point to its nearest grid point. 701 * The function uses the coords object of the point as 702 * its actual position. 703 **/ 704 handleSnapToGrid: function () { 705 var x, y, 706 sX = this.visProp.snapsizex, 707 sY = this.visProp.snapsizey; 708 709 if (this.visProp.snaptogrid) { 710 x = this.coords.usrCoords[1]; 711 y = this.coords.usrCoords[2]; 712 713 if (sX <= 0 && this.board.defaultAxes && this.board.defaultAxes.x.defaultTicks) { 714 sX = this.board.defaultAxes.x.defaultTicks.ticksDelta * (this.board.defaultAxes.x.defaultTicks.visProp.minorticks + 1); 715 } 716 717 if (sY <= 0 && this.board.defaultAxes && this.board.defaultAxes.y.defaultTicks) { 718 sY = this.board.defaultAxes.y.defaultTicks.ticksDelta * (this.board.defaultAxes.y.defaultTicks.visProp.minorticks + 1); 719 } 720 721 // if no valid snap sizes are available, don't change the coords. 722 if (sX > 0 && sY > 0) { 723 this.coords.setCoordinates(Const.COORDS_BY_USER, [Math.round(x / sX) * sX, Math.round(y / sY) * sY]); 724 } 725 } 726 return this; 727 }, 728 729 /** 730 * Let a point snap to the nearest point in distance of 731 * {@link JXG.Point#attractorDistance}. 732 * The function uses the coords object of the point as 733 * its actual position. 734 **/ 735 handleSnapToPoints: function () { 736 var i, pEl, pCoords, 737 d = 0, 738 dMax = Infinity, 739 c = null; 740 741 if (this.visProp.snaptopoints) { 742 for (i = 0; i < this.board.objectsList.length; i++) { 743 pEl = this.board.objectsList[i]; 744 745 if (pEl.elementClass === Const.OBJECT_CLASS_POINT && pEl !== this && pEl.visProp.visible) { 746 pCoords = Geometry.projectPointToPoint(this, pEl, this.board); 747 if (this.visProp.attractorunit === 'screen') { 748 d = pCoords.distance(Const.COORDS_BY_SCREEN, this.coords); 749 } else { 750 d = pCoords.distance(Const.COORDS_BY_USER, this.coords); 751 } 752 753 if (d < this.visProp.attractordistance && d < dMax) { 754 dMax = d; 755 c = pCoords; 756 } 757 } 758 } 759 760 if (c !== null) { 761 this.coords.setCoordinates(Const.COORDS_BY_USER, c.usrCoords); 762 } 763 } 764 765 return this; 766 }, 767 768 /** 769 * A point can change its type from free point to glider 770 * and vice versa. If it is given an array of attractor elements 771 * (attribute attractors) and the attribute attractorDistance 772 * then the pint will be made a glider if it less than attractorDistance 773 * apart from one of its attractor elements. 774 * If attractorDistance is equal to zero, the point stays in its 775 * current form. 776 **/ 777 handleAttractors: function () { 778 var i, el, projCoords, 779 d = 0.0, 780 len = this.visProp.attractors.length; 781 782 if (this.visProp.attractordistance === 0.0) { 783 return; 784 } 785 786 for (i = 0; i < len; i++) { 787 el = this.board.select(this.visProp.attractors[i]); 788 789 if (Type.exists(el) && el !== this) { 790 if (el.elementClass === Const.OBJECT_CLASS_POINT) { 791 projCoords = Geometry.projectPointToPoint(this, el, this.board); 792 } else if (el.elementClass === Const.OBJECT_CLASS_LINE) { 793 projCoords = Geometry.projectPointToLine(this, el, this.board); 794 } else if (el.elementClass === Const.OBJECT_CLASS_CIRCLE) { 795 projCoords = Geometry.projectPointToCircle(this, el, this.board); 796 } else if (el.elementClass === Const.OBJECT_CLASS_CURVE) { 797 projCoords = Geometry.projectPointToCurve(this, el, this.board); 798 } else if (el.type === Const.OBJECT_TYPE_TURTLE) { 799 projCoords = Geometry.projectPointToTurtle(this, el, this.board); 800 } 801 802 if (this.visProp.attractorunit === 'screen') { 803 d = projCoords.distance(Const.COORDS_BY_SCREEN, this.coords); 804 } else { 805 d = projCoords.distance(Const.COORDS_BY_USER, this.coords); 806 } 807 808 if (d < this.visProp.attractordistance) { 809 if (!(this.type === Const.OBJECT_TYPE_GLIDER && this.slideObject === el)) { 810 this.makeGlider(el); 811 } 812 813 break; // bind the point to the first attractor in its list. 814 } else { 815 if (el === this.slideObject && d >= this.visProp.snatchdistance) { 816 this.popSlideObject(); 817 } 818 } 819 } 820 } 821 822 return this; 823 }, 824 825 /** 826 * Sets coordinates and calls the point's update() method. 827 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 828 * @param {Array} coords coordinates <tt>(z, x, y)</tt> in screen/user units 829 * @returns {JXG.Point} this element 830 */ 831 setPositionDirectly: function (method, coords) { 832 var i, dx, dy, dz, el, p, 833 oldCoords = this.coords, 834 newCoords; 835 836 this.coords.setCoordinates(method, coords); 837 this.handleSnapToGrid(); 838 this.handleSnapToPoints(); 839 this.handleAttractors(); 840 841 if (this.group.length === 0) { 842 // Here used to be the udpate of the groups. I'm not sure why we don't need to execute 843 // the else branch if there are groups defined on this point, hence I'll let the if live. 844 845 // Update the initial coordinates. This is needed for free points 846 // that have a transformation bound to it. 847 for (i = this.transformations.length - 1; i >= 0; i--) { 848 if (method === Const.COORDS_BY_SCREEN) { 849 newCoords = (new Coords(method, coords, this.board)).usrCoords; 850 } else { 851 if (coords.length === 2) { 852 coords = [1].concat(coords); 853 } 854 newCoords = coords; 855 } 856 this.initialCoords.setCoordinates(Const.COORDS_BY_USER, Mat.matVecMult(Mat.inverse(this.transformations[i].matrix), newCoords)); 857 } 858 this.update(); 859 } 860 861 // if the user suspends the board updates we need to recalculate the relative position of 862 // the point on the slide object. this is done in updateGlider() which is NOT called during the 863 // update process triggered by unsuspendUpdate. 864 if (this.board.isSuspendedUpdate && this.type === Const.OBJECT_TYPE_GLIDER) { 865 this.updateGlider(); 866 } 867 868 return coords; 869 }, 870 871 /** 872 * Translates the point by <tt>tv = (x, y)</tt>. 873 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 874 * @param {Number} tv (x, y) 875 * @returns {JXG.Point} 876 */ 877 setPositionByTransform: function (method, tv) { 878 var t; 879 880 tv = new Coords(method, tv, this.board); 881 t = this.board.create('transform', tv.usrCoords.slice(1), {type: 'translate'}); 882 883 if (this.transformations.length > 0 && this.transformations[this.transformations.length - 1].isNumericMatrix) { 884 this.transformations[this.transformations.length - 1].melt(t); 885 } else { 886 this.addTransform(this, t); 887 } 888 889 this.update(); 890 891 return this; 892 }, 893 894 /** 895 * Sets coordinates and calls the point's update() method. 896 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 897 * @param {Array} coords coordinates in screen/user units 898 * @returns {JXG.Point} 899 */ 900 setPosition: function (method, coords) { 901 return this.setPositionDirectly(method, coords); 902 }, 903 904 /** 905 * Sets the position of a glider relative to the defining elements of the {@link JXG.Point#slideObject}. 906 * @param {Number} x 907 * @returns {JXG.Point} Reference to the point element. 908 */ 909 setGliderPosition: function (x) { 910 if (this.type === Const.OBJECT_TYPE_GLIDER) { 911 this.position = x; 912 this.board.update(); 913 } 914 915 return this; 916 }, 917 918 /** 919 * Convert the point to glider and update the construction. 920 * @param {String|Object} glideObject The Object the point will be bound to. 921 */ 922 makeGlider: function (glideObject) { 923 this.slideObject = this.board.select(glideObject); 924 this.slideObjects.push(this.slideObject); 925 926 this.type = Const.OBJECT_TYPE_GLIDER; 927 this.elType = 'glider'; 928 this.visProp.snapwidth = -1; // By default, deactivate snapWidth 929 this.slideObject.addChild(this); 930 this.isDraggable = true; 931 932 this.generatePolynomial = function () { 933 return this.slideObject.generatePolynomial(this); 934 }; 935 936 // Determine the initial value of this.position 937 this.updateGlider(); 938 939 return this; 940 }, 941 942 /** 943 * Remove the last slideObject. If there are more than one elements the point is bound to, 944 * the second last element is the new active slideObject. 945 */ 946 popSlideObject: function () { 947 if (this.slideObjects.length > 0) { 948 this.slideObjects.pop(); 949 950 // It may not be sufficient to remove the point from 951 // the list of childElement. For complex dependencies 952 // one may have to go to the list of ancestor and descendants. A.W. 953 // yes indeed, see #51 on github bugtracker 954 //delete this.slideObject.childElements[this.id]; 955 this.slideObject.removeChild(this); 956 957 if (this.slideObjects.length === 0) { 958 this.elType = 'point'; 959 this.type = Const.OBJECT_TYPE_POINT; 960 this.slideObject = null; 961 } else { 962 this.slideObject = this.slideObjects[this.slideObjects.length - 1]; 963 } 964 } 965 }, 966 967 /** 968 * Converts a calculated point into a free point, i.e. it will delete all ancestors and transformations and, 969 * if the point is currently a glider, will remove the slideObject reference. 970 */ 971 free: function () { 972 var ancestorId, ancestor, child; 973 974 if (this.type !== Const.OBJECT_TYPE_GLIDER) { 975 // remove all transformations 976 this.transformations.length = 0; 977 978 if (!this.isDraggable) { 979 this.isDraggable = true; 980 this.type = Const.OBJECT_TYPE_POINT; 981 982 this.XEval = function () { 983 return this.coords.usrCoords[1]; 984 }; 985 986 this.YEval = function () { 987 return this.coords.usrCoords[2]; 988 }; 989 990 this.ZEval = function () { 991 return this.coords.usrCoords[0]; 992 }; 993 994 this.Xjc = null; 995 this.Yjc = null; 996 } else { 997 return; 998 } 999 } 1000 1001 // a free point does not depend on anything. And instead of running through tons of descendants and ancestor 1002 // structures, where we eventually are going to visit a lot of objects twice or thrice with hard to read and 1003 // comprehend code, just run once through all objects and delete all references to this point and its label. 1004 for (ancestorId in this.board.objects) { 1005 if (this.board.objects.hasOwnProperty(ancestorId)) { 1006 ancestor = this.board.objects[ancestorId]; 1007 1008 if (ancestor.descendants) { 1009 delete ancestor.descendants[this.id]; 1010 delete ancestor.childElements[this.id]; 1011 1012 if (this.hasLabel) { 1013 delete ancestor.descendants[this.label.id]; 1014 delete ancestor.childElements[this.label.id]; 1015 } 1016 } 1017 } 1018 } 1019 1020 // A free point does not depend on anything. Remove all ancestors. 1021 this.ancestors = {}; // only remove the reference 1022 1023 // Completely remove all slideObjects of the point 1024 this.slideObject = null; 1025 this.slideObjects = []; 1026 this.elType = 'point'; 1027 this.type = Const.OBJECT_TYPE_POINT; 1028 }, 1029 1030 /** 1031 * Convert the point to CAS point and call update(). 1032 * @param {Array} terms [[zterm], xterm, yterm] defining terms for the z, x and y coordinate. 1033 * The z-coordinate is optional and it is used for homogeneous coordinates. 1034 * The coordinates may be either <ul> 1035 * <li>a JavaScript function,</li> 1036 * <li>a string containing GEONExT syntax. This string will be converted into a JavaScript 1037 * function here,</li> 1038 * <li>a Number</li> 1039 * <li>a pointer to a slider object. This will be converted into a call of the Value()-method 1040 * of this slider.</li> 1041 * </ul> 1042 * @see JXG.GeonextParser#geonext2JS 1043 */ 1044 addConstraint: function (terms) { 1045 var fs, i, v, t, 1046 newfuncs = [], 1047 what = ['X', 'Y'], 1048 1049 makeConstFunction = function (z) { 1050 return function () { 1051 return z; 1052 }; 1053 }, 1054 1055 makeSliderFunction = function (a) { 1056 return function () { 1057 return a.Value(); 1058 }; 1059 }; 1060 1061 this.type = Const.OBJECT_TYPE_CAS; 1062 this.isDraggable = false; 1063 1064 for (i = 0; i < terms.length; i++) { 1065 v = terms[i]; 1066 1067 if (typeof v === 'string') { 1068 // Convert GEONExT syntax into JavaScript syntax 1069 //t = JXG.GeonextParser.geonext2JS(v, this.board); 1070 //newfuncs[i] = new Function('','return ' + t + ';'); 1071 //v = GeonextParser.replaceNameById(v, this.board); 1072 newfuncs[i] = this.board.jc.snippet(v, true, null, true); 1073 1074 if (terms.length === 2) { 1075 this[what[i] + 'jc'] = terms[i]; 1076 } 1077 } else if (typeof v === 'function') { 1078 newfuncs[i] = v; 1079 } else if (typeof v === 'number') { 1080 newfuncs[i] = makeConstFunction(v); 1081 // Slider 1082 } else if (typeof v === 'object' && typeof v.Value === 'function') { 1083 newfuncs[i] = makeSliderFunction(v); 1084 } 1085 1086 newfuncs[i].origin = v; 1087 } 1088 1089 // Intersection function 1090 if (terms.length === 1) { 1091 this.updateConstraint = function () { 1092 var c = newfuncs[0](); 1093 1094 // Array 1095 if (Type.isArray(c)) { 1096 this.coords.setCoordinates(Const.COORDS_BY_USER, c); 1097 // Coords object 1098 } else { 1099 this.coords = c; 1100 } 1101 }; 1102 // Euclidean coordinates 1103 } else if (terms.length === 2) { 1104 this.XEval = newfuncs[0]; 1105 this.YEval = newfuncs[1]; 1106 1107 this.parents = [newfuncs[0].origin, newfuncs[1].origin]; 1108 1109 this.updateConstraint = function () { 1110 this.coords.setCoordinates(Const.COORDS_BY_USER, [this.XEval(), this.YEval()]); 1111 }; 1112 // Homogeneous coordinates 1113 } else { 1114 this.ZEval = newfuncs[0]; 1115 this.XEval = newfuncs[1]; 1116 this.YEval = newfuncs[2]; 1117 1118 this.parents = [newfuncs[0].origin, newfuncs[1].origin, newfuncs[2].origin]; 1119 1120 this.updateConstraint = function () { 1121 this.coords.setCoordinates(Const.COORDS_BY_USER, [this.ZEval(), this.XEval(), this.YEval()]); 1122 }; 1123 } 1124 1125 /** 1126 * We have to do an update. Otherwise, elements relying on this point will receive NaN. 1127 */ 1128 this.update(); 1129 if (!this.board.isSuspendedUpdate) { 1130 this.updateRenderer(); 1131 } 1132 1133 return this; 1134 }, 1135 1136 /** 1137 * Applies the transformations of the curve to {@link JXG.Point#baseElement}. 1138 * @returns {JXG.Point} Reference to this point object. 1139 */ 1140 updateTransform: function () { 1141 var c, i; 1142 1143 if (this.transformations.length === 0 || this.baseElement === null) { 1144 return this; 1145 } 1146 1147 // case of bindTo 1148 if (this === this.baseElement) { 1149 c = this.transformations[0].apply(this.baseElement, 'self'); 1150 // case of board.create('point',[baseElement,transform]); 1151 } else { 1152 c = this.transformations[0].apply(this.baseElement); 1153 } 1154 1155 this.coords.setCoordinates(Const.COORDS_BY_USER, c); 1156 1157 for (i = 1; i < this.transformations.length; i++) { 1158 this.coords.setCoordinates(Const.COORDS_BY_USER, this.transformations[i].apply(this)); 1159 } 1160 return this; 1161 }, 1162 1163 /** 1164 * Add transformations to this point. 1165 * @param {JXG.GeometryElement} el 1166 * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of {@link JXG.Transformation}s. 1167 * @returns {JXG.Point} Reference to this point object. 1168 */ 1169 addTransform: function (el, transform) { 1170 var i, 1171 list = Type.isArray(transform) ? transform : [transform], 1172 len = list.length; 1173 1174 // There is only one baseElement possible 1175 if (this.transformations.length === 0) { 1176 this.baseElement = el; 1177 } 1178 1179 for (i = 0; i < len; i++) { 1180 this.transformations.push(list[i]); 1181 } 1182 1183 return this; 1184 }, 1185 1186 /** 1187 * Animate the point. 1188 * @param {Number} direction The direction the glider is animated. Can be +1 or -1. 1189 * @param {Number} stepCount The number of steps. 1190 * @name Glider#startAnimation 1191 * @see Glider#stopAnimation 1192 * @function 1193 */ 1194 startAnimation: function (direction, stepCount) { 1195 var that = this; 1196 1197 if ((this.type === Const.OBJECT_TYPE_GLIDER) && !Type.exists(this.intervalCode)) { 1198 this.intervalCode = window.setInterval(function () { 1199 that._anim(direction, stepCount); 1200 }, 250); 1201 1202 if (!Type.exists(this.intervalCount)) { 1203 this.intervalCount = 0; 1204 } 1205 } 1206 return this; 1207 }, 1208 1209 /** 1210 * Stop animation. 1211 * @name Glider#stopAnimation 1212 * @see Glider#startAnimation 1213 * @function 1214 */ 1215 stopAnimation: function () { 1216 if (Type.exists(this.intervalCode)) { 1217 window.clearInterval(this.intervalCode); 1218 delete this.intervalCode; 1219 } 1220 1221 return this; 1222 }, 1223 1224 /** 1225 * Starts an animation which moves the point along a given path in given time. 1226 * @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 1227 * 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. 1228 * In case of NaN the animation stops. 1229 * @param {Number} time The time in milliseconds in which to finish the animation 1230 * @param {Object} [options] Optional settings for the animation. 1231 * @param {function} [options.callback] A function that is called as soon as the animation is finished. 1232 * @param {Boolean} [options.interpolate=true] If <tt>path</tt> is an array moveAlong() will interpolate the path 1233 * using {@link JXG.Math.Numerics#Neville}. Set this flag to false if you don't want to use interpolation. 1234 * @returns {JXG.Point} Reference to the point. 1235 */ 1236 moveAlong: function (path, time, options) { 1237 options = options || {}; 1238 1239 var i, neville, 1240 interpath = [], 1241 p = [], 1242 delay = this.board.attr.animationdelay, 1243 steps = time / delay, 1244 1245 makeFakeFunction = function (i, j) { 1246 return function () { 1247 return path[i][j]; 1248 }; 1249 }; 1250 1251 if (Type.isArray(path)) { 1252 for (i = 0; i < path.length; i++) { 1253 if (Type.isPoint(path[i])) { 1254 p[i] = path[i]; 1255 } else { 1256 p[i] = { 1257 elementClass: Const.OBJECT_CLASS_POINT, 1258 X: makeFakeFunction(i, 0), 1259 Y: makeFakeFunction(i, 1) 1260 }; 1261 } 1262 } 1263 1264 time = time || 0; 1265 if (time === 0) { 1266 this.setPosition(Const.COORDS_BY_USER, [p[p.length - 1].X(), p[p.length - 1].Y()]); 1267 return this.board.update(this); 1268 } 1269 1270 if (!Type.exists(options.interpolate) || options.interpolate) { 1271 neville = Numerics.Neville(p); 1272 for (i = 0; i < steps; i++) { 1273 interpath[i] = []; 1274 interpath[i][0] = neville[0]((steps - i) / steps * neville[3]()); 1275 interpath[i][1] = neville[1]((steps - i) / steps * neville[3]()); 1276 } 1277 } else { 1278 for (i = 0; i < steps; i++) { 1279 interpath[i] = []; 1280 interpath[i][0] = path[Math.floor((steps - i) / steps * (path.length - 1))][0]; 1281 interpath[i][1] = path[Math.floor((steps - i) / steps * (path.length - 1))][1]; 1282 } 1283 } 1284 1285 this.animationPath = interpath; 1286 } else if (Type.isFunction(path)) { 1287 this.animationPath = path; 1288 this.animationStart = new Date().getTime(); 1289 } 1290 1291 this.animationCallback = options.callback; 1292 this.board.addAnimation(this); 1293 1294 return this; 1295 }, 1296 1297 /** 1298 * Starts an animated point movement towards the given coordinates <tt>where</tt>. The animation is done after <tt>time</tt> milliseconds. 1299 * If the second parameter is not given or is equal to 0, setPosition() is called, see #setPosition. 1300 * @param {Array} where Array containing the x and y coordinate of the target location. 1301 * @param {Number} [time] Number of milliseconds the animation should last. 1302 * @param {Object} [options] Optional settings for the animation 1303 * @param {function} [options.callback] A function that is called as soon as the animation is finished. 1304 * @param {String} [options.effect='<>'] animation effects like speed fade in and out. possible values are 1305 * '<>' for speed increase on start and slow down at the end (default) and '--' for constant speed during 1306 * the whole animation. 1307 * @returns {JXG.Point} Reference to itself. 1308 * @see #animate 1309 */ 1310 moveTo: function (where, time, options) { 1311 options = options || {}; 1312 where = new Coords(Const.COORDS_BY_USER, where, this.board); 1313 1314 var i, 1315 delay = this.board.attr.animationdelay, 1316 steps = Math.ceil(time / delay), 1317 coords = [], 1318 X = this.coords.usrCoords[1], 1319 Y = this.coords.usrCoords[2], 1320 dX = (where.usrCoords[1] - X), 1321 dY = (where.usrCoords[2] - Y), 1322 1323 /** @ignore */ 1324 stepFun = function (i) { 1325 if (options.effect && options.effect === '<>') { 1326 return Math.pow(Math.sin((i / steps) * Math.PI / 2), 2); 1327 } 1328 return i / steps; 1329 }; 1330 1331 if (!Type.exists(time) || time === 0 || (Math.abs(where.usrCoords[0] - this.coords.usrCoords[0]) > Mat.eps)) { 1332 this.setPosition(Const.COORDS_BY_USER, where.usrCoords); 1333 return this.board.update(this); 1334 } 1335 1336 if (Math.abs(dX) < Mat.eps && Math.abs(dY) < Mat.eps) { 1337 return this; 1338 } 1339 1340 for (i = steps; i >= 0; i--) { 1341 coords[steps - i] = [where.usrCoords[0], X + dX * stepFun(i), Y + dY * stepFun(i)]; 1342 } 1343 1344 this.animationPath = coords; 1345 this.animationCallback = options.callback; 1346 this.board.addAnimation(this); 1347 1348 return this; 1349 }, 1350 1351 /** 1352 * Starts an animated point movement towards the given coordinates <tt>where</tt>. After arriving at 1353 * <tt>where</tt> the point moves back to where it started. The animation is done after <tt>time</tt> 1354 * milliseconds. 1355 * @param {Array} where Array containing the x and y coordinate of the target location. 1356 * @param {Number} time Number of milliseconds the animation should last. 1357 * @param {Object} [options] Optional settings for the animation 1358 * @param {function} [options.callback] A function that is called as soon as the animation is finished. 1359 * @param {String} [options.effect='<>'] animation effects like speed fade in and out. possible values are 1360 * '<>' for speed increase on start and slow down at the end (default) and '--' for constant speed during 1361 * the whole animation. 1362 * @param {Number} [options.repeat=1] How often this animation should be repeated. 1363 * @returns {JXG.Point} Reference to itself. 1364 * @see #animate 1365 */ 1366 visit: function (where, time, options) { 1367 where = new Coords(Const.COORDS_BY_USER, where, this.board); 1368 1369 var i, j, steps, 1370 delay = this.board.attr.animationdelay, 1371 coords = [], 1372 X = this.coords.usrCoords[1], 1373 Y = this.coords.usrCoords[2], 1374 dX = (where.usrCoords[1] - X), 1375 dY = (where.usrCoords[2] - Y), 1376 1377 /** @ignore */ 1378 stepFun = function (i) { 1379 var x = (i < steps / 2 ? 2 * i / steps : 2 * (steps - i) / steps); 1380 1381 if (options.effect && options.effect === '<>') { 1382 return Math.pow(Math.sin(x * Math.PI / 2), 2); 1383 } 1384 1385 return x; 1386 }; 1387 1388 // support legacy interface where the third parameter was the number of repeats 1389 if (typeof options === 'number') { 1390 options = {repeat: options}; 1391 } else { 1392 options = options || {}; 1393 if (!Type.exists(options.repeat)) { 1394 options.repeat = 1; 1395 } 1396 } 1397 1398 steps = Math.ceil(time / (delay * options.repeat)); 1399 1400 for (j = 0; j < options.repeat; j++) { 1401 for (i = steps; i >= 0; i--) { 1402 coords[j * (steps + 1) + steps - i] = [where.usrCoords[0], X + dX * stepFun(i), Y + dY * stepFun(i)]; 1403 } 1404 } 1405 this.animationPath = coords; 1406 this.animationCallback = options.callback; 1407 this.board.addAnimation(this); 1408 1409 return this; 1410 }, 1411 1412 /** 1413 * Animates a glider. Is called by the browser after startAnimation is called. 1414 * @param {Number} direction The direction the glider is animated. 1415 * @param {Number} stepCount The number of steps. 1416 * @see #startAnimation 1417 * @see #stopAnimation 1418 * @private 1419 */ 1420 _anim: function (direction, stepCount) { 1421 var distance, slope, dX, dY, alpha, startPoint, newX, radius, 1422 factor = 1; 1423 1424 this.intervalCount += 1; 1425 if (this.intervalCount > stepCount) { 1426 this.intervalCount = 0; 1427 } 1428 1429 if (this.slideObject.elementClass === Const.OBJECT_CLASS_LINE) { 1430 distance = this.slideObject.point1.coords.distance(Const.COORDS_BY_SCREEN, this.slideObject.point2.coords); 1431 slope = this.slideObject.getSlope(); 1432 if (slope !== Infinity) { 1433 alpha = Math.atan(slope); 1434 dX = Math.round((this.intervalCount / stepCount) * distance * Math.cos(alpha)); 1435 dY = Math.round((this.intervalCount / stepCount) * distance * Math.sin(alpha)); 1436 } else { 1437 dX = 0; 1438 dY = Math.round((this.intervalCount / stepCount) * distance); 1439 } 1440 1441 if (direction < 0) { 1442 startPoint = this.slideObject.point2; 1443 1444 if (this.slideObject.point2.coords.scrCoords[1] - this.slideObject.point1.coords.scrCoords[1] > 0) { 1445 factor = -1; 1446 } else if (this.slideObject.point2.coords.scrCoords[1] - this.slideObject.point1.coords.scrCoords[1] === 0) { 1447 if (this.slideObject.point2.coords.scrCoords[2] - this.slideObject.point1.coords.scrCoords[2] > 0) { 1448 factor = -1; 1449 } 1450 } 1451 } else { 1452 startPoint = this.slideObject.point1; 1453 1454 if (this.slideObject.point1.coords.scrCoords[1] - this.slideObject.point2.coords.scrCoords[1] > 0) { 1455 factor = -1; 1456 } else if (this.slideObject.point1.coords.scrCoords[1] - this.slideObject.point2.coords.scrCoords[1] === 0) { 1457 if (this.slideObject.point1.coords.scrCoords[2] - this.slideObject.point2.coords.scrCoords[2] > 0) { 1458 factor = -1; 1459 } 1460 } 1461 } 1462 1463 this.coords.setCoordinates(Const.COORDS_BY_SCREEN, [ 1464 startPoint.coords.scrCoords[1] + factor * dX, 1465 startPoint.coords.scrCoords[2] + factor * dY 1466 ]); 1467 } else if (this.slideObject.elementClass === Const.OBJECT_CLASS_CURVE) { 1468 if (direction > 0) { 1469 newX = Math.round(this.intervalCount / stepCount * this.board.canvasWidth); 1470 } else { 1471 newX = Math.round((stepCount - this.intervalCount) / stepCount * this.board.canvasWidth); 1472 } 1473 1474 this.coords.setCoordinates(Const.COORDS_BY_SCREEN, [newX, 0]); 1475 this.coords = Geometry.projectPointToCurve(this, this.slideObject, this.board); 1476 } else if (this.slideObject.elementClass === Const.OBJECT_CLASS_CIRCLE) { 1477 if (direction < 0) { 1478 alpha = this.intervalCount / stepCount * 2 * Math.PI; 1479 } else { 1480 alpha = (stepCount - this.intervalCount) / stepCount * 2 * Math.PI; 1481 } 1482 1483 radius = this.slideObject.Radius(); 1484 1485 this.coords.setCoordinates(Const.COORDS_BY_USER, [ 1486 this.slideObject.center.coords.usrCoords[1] + radius * Math.cos(alpha), 1487 this.slideObject.center.coords.usrCoords[2] + radius * Math.sin(alpha) 1488 ]); 1489 } 1490 1491 this.board.update(this); 1492 return this; 1493 }, 1494 1495 /** 1496 * Set the style of a point. Used for GEONExT import and should not be used to set the point's face and size. 1497 * @param {Number} i Integer to determine the style. 1498 * @private 1499 */ 1500 setStyle: function (i) { 1501 var facemap = [ 1502 // 0-2 1503 'cross', 'cross', 'cross', 1504 // 3-6 1505 'circle', 'circle', 'circle', 'circle', 1506 // 7-9 1507 'square', 'square', 'square', 1508 // 10-12 1509 'plus', 'plus', 'plus' 1510 ], sizemap = [ 1511 // 0-2 1512 2, 3, 4, 1513 // 3-6 1514 1, 2, 3, 4, 1515 // 7-9 1516 2, 3, 4, 1517 // 10-12 1518 2, 3, 4 1519 ]; 1520 1521 this.visProp.face = facemap[i]; 1522 this.visProp.size = sizemap[i]; 1523 1524 this.board.renderer.changePointStyle(this); 1525 return this; 1526 }, 1527 1528 /** 1529 * @deprecated Use JXG#normalizePointFace instead 1530 * @param s 1531 * @return {*} 1532 */ 1533 normalizeFace: function (s) { 1534 return Options.normalizePointFace(s); 1535 }, 1536 1537 /** 1538 * 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 1539 * the object completely you should use {@link JXG.Board#removeObject}. 1540 */ 1541 remove: function () { 1542 if (this.hasLabel) { 1543 this.board.renderer.remove(this.board.renderer.getElementById(this.label.id)); 1544 } 1545 this.board.renderer.remove(this.board.renderer.getElementById(this.id)); 1546 }, 1547 1548 // documented in GeometryElement 1549 getTextAnchor: function () { 1550 return this.coords; 1551 }, 1552 1553 // documented in GeometryElement 1554 getLabelAnchor: function () { 1555 return this.coords; 1556 }, 1557 1558 /** 1559 * Set the face of a point element. 1560 * @param {String} f String which determines the face of the point. See {@link JXG.GeometryElement#face} for a list of available faces. 1561 * @see JXG.GeometryElement#face 1562 * @deprecated Use setAttribute() 1563 */ 1564 face: function (f) { 1565 this.setAttribute({face: f}); 1566 }, 1567 1568 /** 1569 * Set the size of a point element 1570 * @param {Number} s Integer which determines the size of the point. 1571 * @see JXG.GeometryElement#size 1572 * @deprecated Use setAttribute() 1573 */ 1574 size: function (s) { 1575 this.setAttribute({size: s}); 1576 }, 1577 1578 // already documented in GeometryElement 1579 cloneToBackground: function () { 1580 var copy = {}; 1581 1582 copy.id = this.id + 'T' + this.numTraces; 1583 this.numTraces += 1; 1584 1585 copy.coords = this.coords; 1586 copy.visProp = Type.deepCopy(this.visProp, this.visProp.traceattributes, true); 1587 copy.visProp.layer = this.board.options.layer.trace; 1588 copy.elementClass = Const.OBJECT_CLASS_POINT; 1589 copy.board = this.board; 1590 Type.clearVisPropOld(copy); 1591 1592 this.board.renderer.drawPoint(copy); 1593 this.traces[copy.id] = copy.rendNode; 1594 1595 return this; 1596 }, 1597 1598 getParents: function () { 1599 var p = [this.Z(), this.X(), this.Y()]; 1600 1601 if (this.parents) { 1602 p = this.parents; 1603 } 1604 1605 if (this.type === Const.OBJECT_TYPE_GLIDER) { 1606 p = [this.X(), this.Y(), this.slideObject.id]; 1607 1608 } 1609 1610 return p; 1611 } 1612 }); 1613 1614 1615 /** 1616 * @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 1617 * 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 1618 * 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 1619 * position directly. 1620 * @pseudo 1621 * @description 1622 * @name Point 1623 * @augments JXG.Point 1624 * @constructor 1625 * @type JXG.Point 1626 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1627 * @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 1628 * constraint, or a function which takes no parameter and returns a number. Every parent element determines one coordinate. If a coordinate is 1629 * 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 1630 * 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 1631 * 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 1632 * parent elements are given they will be interpreted as homogeneous coordinates. 1633 * @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 1634 * point transformed by the given Transformation. {@see JXG.Transformation}. 1635 * @example 1636 * // Create a free point using affine euclidean coordinates 1637 * var p1 = board.create('point', [3.5, 2.0]); 1638 * </pre><div id="672f1764-7dfa-4abc-a2c6-81fbbf83e44b" style="width: 200px; height: 200px;"></div> 1639 * <script type="text/javascript"> 1640 * var board = JXG.JSXGraph.initBoard('672f1764-7dfa-4abc-a2c6-81fbbf83e44b', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false}); 1641 * var p1 = board.create('point', [3.5, 2.0]); 1642 * </script><pre> 1643 * @example 1644 * // Create a constrained point using anonymous function 1645 * var p2 = board.create('point', [3.5, function () { return p1.X(); }]); 1646 * </pre><div id="4fd4410c-3383-4e80-b1bb-961f5eeef224" style="width: 200px; height: 200px;"></div> 1647 * <script type="text/javascript"> 1648 * var fpex1_board = JXG.JSXGraph.initBoard('4fd4410c-3383-4e80-b1bb-961f5eeef224', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false}); 1649 * var fpex1_p1 = fpex1_board.create('point', [3.5, 2.0]); 1650 * var fpex1_p2 = fpex1_board.create('point', [3.5, function () { return fpex1_p1.X(); }]); 1651 * </script><pre> 1652 * @example 1653 * // Create a point using transformations 1654 * var trans = board.create('transform', [2, 0.5], {type:'scale'}); 1655 * var p3 = board.create('point', [p2, trans]); 1656 * </pre><div id="630afdf3-0a64-46e0-8a44-f51bd197bb8d" style="width: 400px; height: 400px;"></div> 1657 * <script type="text/javascript"> 1658 * var fpex2_board = JXG.JSXGraph.initBoard('630afdf3-0a64-46e0-8a44-f51bd197bb8d', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 1659 * var fpex2_trans = fpex2_board.create('transform', [2, 0.5], {type:'scale'}); 1660 * var fpex2_p2 = fpex2_board.create('point', [3.5, 2.0]); 1661 * var fpex2_p3 = fpex2_board.create('point', [fpex2_p2, fpex2_trans]); 1662 * </script><pre> 1663 */ 1664 JXG.createPoint = function (board, parents, attributes) { 1665 var el, isConstrained = false, i, attr; 1666 1667 attr = Type.copyAttributes(attributes, board.options, 'point'); 1668 1669 if (parents.length === 1 && Type.isArray(parents[0]) && parents[0].length > 1 && parents[0].length < 4) { 1670 parents = parents[0]; 1671 } 1672 1673 for (i = 0; i < parents.length; i++) { 1674 if (typeof parents[i] === 'function' || typeof parents[i] === 'string') { 1675 isConstrained = true; 1676 } 1677 } 1678 1679 if (!isConstrained) { 1680 if ((Type.isNumber(parents[0])) && (Type.isNumber(parents[1]))) { 1681 el = new JXG.Point(board, parents, attr); 1682 1683 if (Type.exists(attr.slideobject)) { 1684 el.makeGlider(attr.slideobject); 1685 } else { 1686 // Free point 1687 el.baseElement = el; 1688 } 1689 el.isDraggable = true; 1690 } else if ((typeof parents[0] === 'object') && (typeof parents[1] === 'object')) { 1691 // Transformation 1692 el = new JXG.Point(board, [0, 0], attr); 1693 el.addTransform(parents[0], parents[1]); 1694 el.isDraggable = false; 1695 1696 el.parents = [parents[0].id]; 1697 } else { 1698 throw new Error("JSXGraph: Can't create point with parent types '" + 1699 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1700 "\nPossible parent types: [x,y], [z,x,y], [point,transformation]"); 1701 } 1702 } else { 1703 el = new JXG.Point(board, [NaN, NaN], attr); 1704 el.addConstraint(parents); 1705 } 1706 1707 if (!board.isSuspendedUpdate) { 1708 el.handleSnapToGrid(); 1709 el.handleSnapToPoints(); 1710 el.handleAttractors(); 1711 } 1712 1713 return el; 1714 }; 1715 1716 /** 1717 * @class This element is used to provide a constructor for a glider point. 1718 * @pseudo 1719 * @description A glider is a point which lives on another geometric element like a line, circle, curve, turtle. 1720 * @name Glider 1721 * @augments JXG.Point 1722 * @constructor 1723 * @type JXG.Point 1724 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1725 * @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. 1726 * 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 1727 * coordinates, otherwise they will be interpreted as homogeneous coordinates. In any case the point will be projected on the glide object. 1728 * @example 1729 * // Create a glider with user defined coordinates. If the coordinates are not on 1730 * // the circle (like in this case) the point will be projected onto the circle. 1731 * var p1 = board.create('point', [2.0, 2.0]); 1732 * var c1 = board.create('circle', [p1, 2.0]); 1733 * var p2 = board.create('glider', [2.0, 1.5, c1]); 1734 * </pre><div id="4f65f32f-e50a-4b50-9b7c-f6ec41652930" style="width: 300px; height: 300px;"></div> 1735 * <script type="text/javascript"> 1736 * var gpex1_board = JXG.JSXGraph.initBoard('4f65f32f-e50a-4b50-9b7c-f6ec41652930', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false}); 1737 * var gpex1_p1 = gpex1_board.create('point', [2.0, 2.0]); 1738 * var gpex1_c1 = gpex1_board.create('circle', [gpex1_p1, 2.0]); 1739 * var gpex1_p2 = gpex1_board.create('glider', [2.0, 1.5, gpex1_c1]); 1740 * </script><pre> 1741 * @example 1742 * // Create a glider with default coordinates (1,0,0). Same premises as above. 1743 * var p1 = board.create('point', [2.0, 2.0]); 1744 * var c1 = board.create('circle', [p1, 2.0]); 1745 * var p2 = board.create('glider', [c1]); 1746 * </pre><div id="4de7f181-631a-44b1-a12f-bc4d995609e8" style="width: 200px; height: 200px;"></div> 1747 * <script type="text/javascript"> 1748 * var gpex2_board = JXG.JSXGraph.initBoard('4de7f181-631a-44b1-a12f-bc4d995609e8', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false}); 1749 * var gpex2_p1 = gpex2_board.create('point', [2.0, 2.0]); 1750 * var gpex2_c1 = gpex2_board.create('circle', [gpex2_p1, 2.0]); 1751 * var gpex2_p2 = gpex2_board.create('glider', [gpex2_c1]); 1752 * </script><pre> 1753 */ 1754 JXG.createGlider = function (board, parents, attributes) { 1755 var el, 1756 attr = Type.copyAttributes(attributes, board.options, 'glider'); 1757 1758 if (parents.length === 1) { 1759 el = board.create('point', [0, 0], attr); 1760 } else { 1761 el = board.create('point', parents.slice(0, 2), attr); 1762 } 1763 1764 // eltype is set in here 1765 el.makeGlider(parents[parents.length - 1]); 1766 1767 return el; 1768 }; 1769 1770 /** 1771 * @class This element is used to provide a constructor for an intersection point. 1772 * @pseudo 1773 * @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. 1774 * an intersection point of the two elements. 1775 * @name Intersection 1776 * @augments JXG.Point 1777 * @constructor 1778 * @type JXG.Point 1779 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1780 * @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 1781 * intersection point if two points are available: <ul> 1782 * <li>i==0: use the positive square root,</li> 1783 * <li>i==1: use the negative square root.</li></ul> 1784 * @example 1785 * // Create an intersection point of circle and line 1786 * var p1 = board.create('point', [2.0, 2.0]); 1787 * var c1 = board.create('circle', [p1, 2.0]); 1788 * 1789 * var p2 = board.create('point', [2.0, 2.0]); 1790 * var p3 = board.create('point', [2.0, 2.0]); 1791 * var l1 = board.create('line', [p2, p3]); 1792 * 1793 * var i = board.create('intersection', [c1, l1, 0]); 1794 * </pre><div id="e5b0e190-5200-4bc3-b995-b6cc53dc5dc0" style="width: 300px; height: 300px;"></div> 1795 * <script type="text/javascript"> 1796 * var ipex1_board = JXG.JSXGraph.initBoard('e5b0e190-5200-4bc3-b995-b6cc53dc5dc0', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1797 * var ipex1_p1 = ipex1_board.create('point', [4.0, 4.0]); 1798 * var ipex1_c1 = ipex1_board.create('circle', [ipex1_p1, 2.0]); 1799 * var ipex1_p2 = ipex1_board.create('point', [1.0, 1.0]); 1800 * var ipex1_p3 = ipex1_board.create('point', [5.0, 3.0]); 1801 * var ipex1_l1 = ipex1_board.create('line', [ipex1_p2, ipex1_p3]); 1802 * var ipex1_i = ipex1_board.create('intersection', [ipex1_c1, ipex1_l1, 0]); 1803 * </script><pre> 1804 */ 1805 JXG.createIntersectionPoint = function (board, parents, attributes) { 1806 var el, el1, el2, func, i, j, 1807 attr = Type.copyAttributes(attributes, board.options, 'intersection'); 1808 1809 1810 // make sure we definitely have the indices 1811 parents.push(0, 0); 1812 1813 el = board.create('point', [0, 0, 0], attr); 1814 1815 el1 = board.select(parents[0]); 1816 el2 = board.select(parents[1]); 1817 1818 i = parents[2] || 0; 1819 j = parents[3] || 0; 1820 1821 if (el1.elementClass === Const.OBJECT_CLASS_CURVE && 1822 el2.elementClass === Const.OBJECT_CLASS_CURVE) { 1823 // curve - curve 1824 /** @ignore */ 1825 func = function () { 1826 return Geometry.meetCurveCurve(el1, el2, i, j, el1.board); 1827 }; 1828 1829 //} else if ((el1.type === Const.OBJECT_TYPE_ARC && el2.elementClass === Const.OBJECT_CLASS_LINE) || 1830 // (el2.type === Const.OBJECT_TYPE_ARC && el1.elementClass === Const.OBJECT_CLASS_LINE)) { 1831 // arc - line (arcs are of class curve, but are intersected like circles) 1832 // TEMPORARY FIX!!! 1833 /** @ignore */ 1834 // func = function () { 1835 //return Geometry.meet(el1.stdform, el2.stdform, i, el1.board); 1836 //}; 1837 1838 } else if ((el1.elementClass === Const.OBJECT_CLASS_CURVE && el2.elementClass === Const.OBJECT_CLASS_LINE) || 1839 (el2.elementClass === Const.OBJECT_CLASS_CURVE && el1.elementClass === Const.OBJECT_CLASS_LINE)) { 1840 // curve - line (this includes intersections between conic sections and lines 1841 /** @ignore */ 1842 func = function () { 1843 return Geometry.meetCurveLine(el1, el2, i, el1.board, el.visProp.alwaysintersect); 1844 }; 1845 1846 } else if (el1.elementClass === Const.OBJECT_CLASS_LINE && el2.elementClass === Const.OBJECT_CLASS_LINE) { 1847 // line - line, lines may also be segments. 1848 /** @ignore */ 1849 func = function () { 1850 var res, c, 1851 first1 = el1.visProp.straightfirst, 1852 first2 = el2.visProp.straightfirst, 1853 last1 = el1.visProp.straightlast, 1854 last2 = el2.visProp.straightlast; 1855 1856 /** 1857 * If one of the lines is a segment or ray and 1858 * the the intersection point shpould disappear if outside 1859 * of the segment or ray we call 1860 * meetSegmentSegment 1861 */ 1862 if (!el.visProp.alwaysintersect && (!first1 || !last1 || !first2 || !last2)) { 1863 res = Geometry.meetSegmentSegment( 1864 el1.point1.coords.usrCoords, 1865 el1.point2.coords.usrCoords, 1866 el2.point1.coords.usrCoords, 1867 el2.point2.coords.usrCoords, 1868 el1.board 1869 ); 1870 1871 if ((!first1 && res[1] < 0) || (!last1 && res[1] > 1) || 1872 (!first2 && res[2] < 0) || (!last2 && res[2] > 1)) { 1873 // Non-existent 1874 c = [0, NaN, NaN]; 1875 } else { 1876 c = res[0]; 1877 } 1878 1879 return (new Coords(Const.COORDS_BY_USER, c, el1.board)); 1880 } 1881 1882 return Geometry.meet(el1.stdform, el2.stdform, i, el1.board); 1883 }; 1884 } else { 1885 // All other combinations of circles and lines 1886 /** @ignore */ 1887 func = function () { 1888 return Geometry.meet(el1.stdform, el2.stdform, i, el1.board); 1889 }; 1890 } 1891 1892 el.addConstraint([func]); 1893 1894 try { 1895 el1.addChild(el); 1896 el2.addChild(el); 1897 } catch (e) { 1898 throw new Error("JSXGraph: Can't create 'intersection' with parent types '" + 1899 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'."); 1900 } 1901 1902 el.type = Const.OBJECT_TYPE_INTERSECTION; 1903 el.elType = 'intersection'; 1904 el.parents = [el1.id, el2.id, i, j]; 1905 1906 el.generatePolynomial = function () { 1907 var poly1 = el1.generatePolynomial(el), 1908 poly2 = el2.generatePolynomial(el); 1909 1910 if ((poly1.length === 0) || (poly2.length === 0)) { 1911 return []; 1912 } 1913 1914 return [poly1[0], poly2[0]]; 1915 }; 1916 1917 return el; 1918 }; 1919 1920 /** 1921 * @class This element is used to provide a constructor for the "other" intersection point. 1922 * @pseudo 1923 * @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. 1924 * an intersection point of the two elements. Additionally, one intersection point is provided. The function returns the other intersection point. 1925 * @name OtherIntersection 1926 * @augments JXG.Point 1927 * @constructor 1928 * @type JXG.Point 1929 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1930 * @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 1931 * intersection point different from p: 1932 * @example 1933 * // Create an intersection point of circle and line 1934 * var p1 = board.create('point', [2.0, 2.0]); 1935 * var c1 = board.create('circle', [p1, 2.0]); 1936 * 1937 * var p2 = board.create('point', [2.0, 2.0]); 1938 * var p3 = board.create('point', [2.0, 2.0]); 1939 * var l1 = board.create('line', [p2, p3]); 1940 * 1941 * var i = board.create('intersection', [c1, l1, 0]); 1942 * var j = board.create('otherintersection', [c1, l1, i]); 1943 * </pre><div id="45e25f12-a1de-4257-a466-27a2ae73614c" style="width: 300px; height: 300px;"></div> 1944 * <script type="text/javascript"> 1945 * var ipex2_board = JXG.JSXGraph.initBoard('45e25f12-a1de-4257-a466-27a2ae73614c', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1946 * var ipex2_p1 = ipex2_board.create('point', [4.0, 4.0]); 1947 * var ipex2_c1 = ipex2_board.create('circle', [ipex2_p1, 2.0]); 1948 * var ipex2_p2 = ipex2_board.create('point', [1.0, 1.0]); 1949 * var ipex2_p3 = ipex2_board.create('point', [5.0, 3.0]); 1950 * var ipex2_l1 = ipex2_board.create('line', [ipex2_p2, ipex2_p3]); 1951 * var ipex2_i = ipex2_board.create('intersection', [ipex2_c1, ipex2_l1, 0], {name:'D'}); 1952 * var ipex2_j = ipex2_board.create('otherintersection', [ipex2_c1, ipex2_l1, ipex2_i], {name:'E'}); 1953 * </script><pre> 1954 */ 1955 JXG.createOtherIntersectionPoint = function (board, parents, attributes) { 1956 var el, el1, el2, other; 1957 1958 if (parents.length !== 3 || 1959 !Type.isPoint(parents[2]) || 1960 (parents[0].elementClass !== Const.OBJECT_CLASS_LINE && parents[0].elementClass !== Const.OBJECT_CLASS_CIRCLE) || 1961 (parents[1].elementClass !== Const.OBJECT_CLASS_LINE && parents[1].elementClass !== Const.OBJECT_CLASS_CIRCLE)) { 1962 // Failure 1963 throw new Error("JSXGraph: Can't create 'other intersection point' with parent types '" + 1964 (typeof parents[0]) + "', '" + (typeof parents[1]) + "'and '" + (typeof parents[2]) + "'." + 1965 "\nPossible parent types: [circle|line,circle|line,point]"); 1966 } 1967 1968 el1 = board.select(parents[0]); 1969 el2 = board.select(parents[1]); 1970 other = board.select(parents[2]); 1971 1972 el = board.create('point', [function () { 1973 var c = Geometry.meet(el1.stdform, el2.stdform, 0, el1.board); 1974 1975 if (Math.abs(other.X() - c.usrCoords[1]) > Mat.eps || 1976 Math.abs(other.Y() - c.usrCoords[2]) > Mat.eps || 1977 Math.abs(other.Z() - c.usrCoords[0]) > Mat.eps) { 1978 return c; 1979 } 1980 1981 return Geometry.meet(el1.stdform, el2.stdform, 1, el1.board); 1982 }], attributes); 1983 1984 el.type = Const.OBJECT_TYPE_INTERSECTION; 1985 el.elType = 'otherintersection'; 1986 el.parents = [el1.id, el2.id, other]; 1987 1988 el1.addChild(el); 1989 el2.addChild(el); 1990 1991 el.generatePolynomial = function () { 1992 var poly1 = el1.generatePolynomial(el), 1993 poly2 = el2.generatePolynomial(el); 1994 1995 if ((poly1.length === 0) || (poly2.length === 0)) { 1996 return []; 1997 } 1998 1999 return [poly1[0], poly2[0]]; 2000 }; 2001 2002 return el; 2003 }; 2004 2005 2006 JXG.registerElement('point', JXG.createPoint); 2007 JXG.registerElement('glider', JXG.createGlider); 2008 JXG.registerElement('intersection', JXG.createIntersectionPoint); 2009 JXG.registerElement('otherintersection', JXG.createOtherIntersectionPoint); 2010 2011 return { 2012 Point: JXG.Point, 2013 createPoint: JXG.createPoint, 2014 createGlider: JXG.createGlider, 2015 createIntersection: JXG.createIntersectionPoint, 2016 createOtherIntersection: JXG.createOtherIntersectionPoint 2017 }; 2018 }); 2019