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