1 /* 2 Copyright 2008-2015 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*/ 34 /*jslint nomen: true, plusplus: true*/ 35 36 /* depends: 37 jxg 38 math/math 39 math/geometry 40 math/numerics 41 math/statistics 42 base/constants 43 base/coords 44 base/element 45 utils/type 46 elements: 47 transform 48 point 49 ticks 50 */ 51 52 /** 53 * @fileoverview The geometry object Line is defined in this file. Line stores all 54 * style and functional properties that are required to draw and move a line on 55 * a board. 56 */ 57 58 define([ 59 'jxg', 'math/math', 'math/geometry', 'math/numerics', 'math/statistics', 'base/constants', 'base/coords', 60 'base/element', 'utils/type', 'base/transformation', 'base/point', 'base/ticks' 61 ], function (JXG, Mat, Geometry, Numerics, Statistics, Const, Coords, GeometryElement, Type, Transform, Point, Ticks) { 62 63 "use strict"; 64 65 /** 66 * The Line class is a basic class for all kind of line objects, e.g. line, arrow, and axis. It is usually defined by two points and can 67 * be intersected with some other geometry elements. 68 * @class Creates a new basic line object. Do not use this constructor to create a line. Use {@link JXG.Board#create} with 69 * type {@link Line}, {@link Arrow}, or {@link Axis} instead. 70 * @constructor 71 * @augments JXG.GeometryElement 72 * @param {String,JXG.Board} board The board the new line is drawn on. 73 * @param {Point} p1 Startpoint of the line. 74 * @param {Point} p2 Endpoint of the line. 75 * @param {String} id Unique identifier for this object. If null or an empty string is given, 76 * an unique id will be generated by Board 77 * @param {String} name Not necessarily unique name. If null or an 78 * empty string is given, an unique name will be generated. 79 * @param {Boolean} withLabel construct label, yes/no 80 * @param {Number} layer display layer [0-9] 81 * @see JXG.Board#generateName 82 */ 83 JXG.Line = function (board, p1, p2, attributes) { 84 this.constructor(board, attributes, Const.OBJECT_TYPE_LINE, Const.OBJECT_CLASS_LINE); 85 86 /** 87 * Startpoint of the line. You really should not set this field directly as it may break JSXGraph's 88 * udpate system so your construction won't be updated properly. 89 * @type JXG.Point 90 */ 91 this.point1 = this.board.select(p1); 92 93 /** 94 * Endpoint of the line. Just like {@link #point1} you shouldn't write this field directly. 95 * @type JXG.Point 96 */ 97 this.point2 = this.board.select(p2); 98 99 /** 100 * Array of ticks storing all the ticks on this line. Do not set this field directly and use 101 * {@link JXG.Line#addTicks} and {@link JXG.Line#removeTicks} to add and remove ticks to and from the line. 102 * @type Array 103 * @see JXG.Ticks 104 */ 105 this.ticks = []; 106 107 /** 108 * Reference of the ticks created automatically when constructing an axis. 109 * @type JXG.Ticks 110 * @see JXG.Ticks 111 */ 112 this.defaultTicks = null; 113 114 /** 115 * If the line is the border of a polygon, the polygon object is stored, otherwise null. 116 * @type JXG.Polygon 117 * @default null 118 * @private 119 */ 120 this.parentPolygon = null; 121 122 /* Register line at board */ 123 this.id = this.board.setId(this, 'L'); 124 this.board.renderer.drawLine(this); 125 this.board.finalizeAdding(this); 126 127 this.elType = 'line'; 128 129 /* Add arrow as child to defining points */ 130 this.point1.addChild(this); 131 this.point2.addChild(this); 132 133 134 this.updateStdform(); // This is needed in the following situation: 135 // * the line is defined by three coordinates 136 // * and it will have a glider 137 // * and board.suspendUpdate() has been called. 138 139 // create Label 140 this.createLabel(); 141 142 this.methodMap = JXG.deepCopy(this.methodMap, { 143 point1: 'point1', 144 point2: 'point2', 145 getSlope: 'getSlope', 146 getRise: 'getRise', 147 getYIntersect: 'getRise', 148 getAngle: 'getAngle', 149 L: 'L', 150 length: 'L', 151 addTicks: 'addTicks', 152 removeTicks: 'removeTicks', 153 removeAllTicks: 'removeAllTicks' 154 }); 155 }; 156 157 JXG.Line.prototype = new GeometryElement(); 158 159 160 JXG.extend(JXG.Line.prototype, /** @lends JXG.Line.prototype */ { 161 /** 162 * Checks whether (x,y) is near the line. 163 * @param {Number} x Coordinate in x direction, screen coordinates. 164 * @param {Number} y Coordinate in y direction, screen coordinates. 165 * @return {Boolean} True if (x,y) is near the line, False otherwise. 166 */ 167 hasPoint: function (x, y) { 168 // Compute the stdform of the line in screen coordinates. 169 var c = [], s, 170 v = [1, x, y], 171 vnew, 172 p1c, p2c, d, pos, i; 173 174 c[0] = this.stdform[0] - 175 this.stdform[1] * this.board.origin.scrCoords[1] / this.board.unitX + 176 this.stdform[2] * this.board.origin.scrCoords[2] / this.board.unitY; 177 c[1] = this.stdform[1] / this.board.unitX; 178 c[2] = this.stdform[2] / (-this.board.unitY); 179 180 s = Geometry.distPointLine(v, c); 181 if (isNaN(s) || s > this.board.options.precision.hasPoint) { 182 return false; 183 } 184 185 if (this.visProp.straightfirst && this.visProp.straightlast) { 186 return true; 187 } 188 189 // If the line is a ray or segment we have to check if the projected point is between P1 and P2. 190 p1c = this.point1.coords; 191 p2c = this.point2.coords; 192 193 // Project the point orthogonally onto the line 194 vnew = [0, c[1], c[2]]; 195 // Orthogonal line to c through v 196 vnew = Mat.crossProduct(vnew, v); 197 // Intersect orthogonal line with line 198 vnew = Mat.crossProduct(vnew, c); 199 200 // Normalize the projected point 201 vnew[1] /= vnew[0]; 202 vnew[2] /= vnew[0]; 203 vnew[0] = 1; 204 205 vnew = (new Coords(Const.COORDS_BY_SCREEN, vnew.slice(1), this.board)).usrCoords; 206 d = p1c.distance(Const.COORDS_BY_USER, p2c); 207 p1c = p1c.usrCoords.slice(0); 208 p2c = p2c.usrCoords.slice(0); 209 210 // The defining points are identical 211 if (d < Mat.eps) { 212 pos = 0; 213 } else { 214 /* 215 * Handle the cases, where one of the defining points is an ideal point. 216 * d is set to something close to infinity, namely 1/eps. 217 * The ideal point is (temporarily) replaced by a finite point which has 218 * distance d from the other point. 219 * This is accomplishrd by extracting the x- and y-coordinates (x,y)=:v of the ideal point. 220 * v determines the direction of the line. v is normalized, i.e. set to length 1 by deividing through its length. 221 * Finally, the new point is the sum of the other point and v*d. 222 * 223 */ 224 225 // At least one point is an ideal point 226 if (d === Number.POSITIVE_INFINITY) { 227 d = 1 / Mat.eps; 228 229 // The second point is an ideal point 230 if (Math.abs(p2c[0]) < Mat.eps) { 231 d /= Geometry.distance([0, 0, 0], p2c); 232 p2c = [1, p1c[1] + p2c[1] * d, p1c[2] + p2c[2] * d]; 233 // The first point is an ideal point 234 } else { 235 d /= Geometry.distance([0, 0, 0], p1c); 236 p1c = [1, p2c[1] + p1c[1] * d, p2c[2] + p1c[2] * d]; 237 } 238 } 239 i = 1; 240 d = p2c[i] - p1c[i]; 241 242 if (Math.abs(d) < Mat.eps) { 243 i = 2; 244 d = p2c[i] - p1c[i]; 245 } 246 pos = (vnew[i] - p1c[i]) / d; 247 } 248 249 if (!this.visProp.straightfirst && pos < 0) { 250 return false; 251 } 252 253 if (!this.visProp.straightlast && pos > 1) { 254 return false; 255 } 256 return true; 257 }, 258 259 // document in base/element 260 update: function () { 261 var funps; 262 263 if (!this.needsUpdate) { 264 return this; 265 } 266 267 if (this.constrained) { 268 if (typeof this.funps === 'function') { 269 funps = this.funps(); 270 if (funps && funps.length && funps.length === 2) { 271 this.point1 = funps[0]; 272 this.point2 = funps[1]; 273 } 274 } else { 275 if (typeof this.funp1 === 'function') { 276 funps = this.funp1(); 277 if (Type.isPoint(funps)) { 278 this.point1 = funps; 279 } else if (funps && funps.length && funps.length === 2) { 280 this.point1.setPositionDirectly(Const.COORDS_BY_USER, funps); 281 } 282 } 283 284 if (typeof this.funp2 === 'function') { 285 funps = this.funp2(); 286 if (Type.isPoint(funps)) { 287 this.point2 = funps; 288 } else if (funps && funps.length && funps.length === 2) { 289 this.point2.setPositionDirectly(Const.COORDS_BY_USER, funps); 290 } 291 } 292 } 293 } 294 295 this.updateSegmentFixedLength(); 296 this.updateStdform(); 297 298 if (this.visProp.trace) { 299 this.cloneToBackground(true); 300 } 301 302 return this; 303 }, 304 305 /** 306 * Update segments with fixed length and at least one movable point. 307 * @private 308 */ 309 updateSegmentFixedLength: function () { 310 var d, dnew, d1, d2, drag1, drag2, x, y; 311 312 if (!this.hasFixedLength) { 313 return this; 314 } 315 316 // Compute the actual length of the segment 317 d = this.point1.Dist(this.point2); 318 // Determine the length the segment ought to have 319 dnew = this.fixedLength(); 320 // Distances between the two points and their respective 321 // position before the update 322 d1 = this.fixedLengthOldCoords[0].distance(Const.COORDS_BY_USER, this.point1.coords); 323 d2 = this.fixedLengthOldCoords[1].distance(Const.COORDS_BY_USER, this.point2.coords); 324 325 // If the position of the points or the fixed length function has been changed we have to work. 326 if (d1 > Mat.eps || d2 > Mat.eps || d !== dnew) { 327 drag1 = this.point1.isDraggable && (this.point1.type !== Const.OBJECT_TYPE_GLIDER) && !this.point1.visProp.fixed; 328 drag2 = this.point2.isDraggable && (this.point2.type !== Const.OBJECT_TYPE_GLIDER) && !this.point2.visProp.fixed; 329 330 // First case: the two points are different 331 // Then we try to adapt the point that was not dragged 332 // If this point can not be moved (e.g. because it is a glider) 333 // we try move the other point 334 if (d > Mat.eps) { 335 if ((d1 > d2 && drag2) || 336 (d1 <= d2 && drag2 && !drag1)) { 337 this.point2.setPositionDirectly(Const.COORDS_BY_USER, [ 338 this.point1.X() + (this.point2.X() - this.point1.X()) * dnew / d, 339 this.point1.Y() + (this.point2.Y() - this.point1.Y()) * dnew / d 340 ]); 341 this.point2.prepareUpdate().updateRenderer(); 342 } else if ((d1 <= d2 && drag1) || 343 (d1 > d2 && drag1 && !drag2)) { 344 this.point1.setPositionDirectly(Const.COORDS_BY_USER, [ 345 this.point2.X() + (this.point1.X() - this.point2.X()) * dnew / d, 346 this.point2.Y() + (this.point1.Y() - this.point2.Y()) * dnew / d 347 ]); 348 this.point1.prepareUpdate().updateRenderer(); 349 } 350 // Second case: the two points are identical. In this situation 351 // we choose a random direction. 352 } else { 353 x = Math.random() - 0.5; 354 y = Math.random() - 0.5; 355 d = Math.sqrt(x * x + y * y); 356 357 if (drag2) { 358 this.point2.setPositionDirectly(Const.COORDS_BY_USER, [ 359 this.point1.X() + x * dnew / d, 360 this.point1.Y() + y * dnew / d 361 ]); 362 this.point2.prepareUpdate().updateRenderer(); 363 } else if (drag1) { 364 this.point1.setPositionDirectly(Const.COORDS_BY_USER, [ 365 this.point2.X() + x * dnew / d, 366 this.point2.Y() + y * dnew / d 367 ]); 368 this.point1.prepareUpdate().updateRenderer(); 369 } 370 } 371 // Finally, we save the position of the two points. 372 this.fixedLengthOldCoords[0].setCoordinates(Const.COORDS_BY_USER, this.point1.coords.usrCoords); 373 this.fixedLengthOldCoords[1].setCoordinates(Const.COORDS_BY_USER, this.point2.coords.usrCoords); 374 } 375 return this; 376 }, 377 378 /** 379 * Updates the stdform derived from the parent point positions. 380 * @private 381 */ 382 updateStdform: function () { 383 var v = Mat.crossProduct(this.point1.coords.usrCoords, this.point2.coords.usrCoords); 384 385 this.stdform[0] = v[0]; 386 this.stdform[1] = v[1]; 387 this.stdform[2] = v[2]; 388 this.stdform[3] = 0; 389 390 this.normalize(); 391 }, 392 393 /** 394 * Uses the boards renderer to update the line. 395 * @private 396 */ 397 updateRenderer: function () { 398 var wasReal; 399 400 if (this.needsUpdate && this.visProp.visible) { 401 wasReal = this.isReal; 402 this.isReal = (!isNaN(this.point1.coords.usrCoords[1] + this.point1.coords.usrCoords[2] + 403 this.point2.coords.usrCoords[1] + this.point2.coords.usrCoords[2]) && 404 (Mat.innerProduct(this.stdform, this.stdform, 3) >= Mat.eps * Mat.eps)); 405 406 if (this.isReal) { 407 if (wasReal !== this.isReal) { 408 this.board.renderer.show(this); 409 if (this.hasLabel && this.label.visProp.visible) { 410 this.board.renderer.show(this.label); 411 } 412 } 413 this.board.renderer.updateLine(this); 414 } else { 415 if (wasReal !== this.isReal) { 416 this.board.renderer.hide(this); 417 if (this.hasLabel && this.label.visProp.visible) { 418 this.board.renderer.hide(this.label); 419 } 420 } 421 } 422 423 this.needsUpdate = false; 424 } 425 426 /* Update the label if visible. */ 427 if (this.hasLabel && this.label.visProp.visible && this.isReal) { 428 this.label.update(); 429 this.board.renderer.updateText(this.label); 430 } 431 432 return this; 433 }, 434 435 /** 436 * Used to generate a polynomial for a point p that lies on this line, i.e. p is collinear to {@link #point1} 437 * and {@link #point2}. 438 * @param {JXG.Point} p The point for that the polynomial is generated. 439 * @return {Array} An array containing the generated polynomial. 440 * @private 441 */ 442 generatePolynomial: function (p) { 443 var u1 = this.point1.symbolic.x, 444 u2 = this.point1.symbolic.y, 445 v1 = this.point2.symbolic.x, 446 v2 = this.point2.symbolic.y, 447 w1 = p.symbolic.x, 448 w2 = p.symbolic.y; 449 450 /* 451 * The polynomial in this case is determined by three points being collinear: 452 * 453 * U (u1,u2) W (w1,w2) V (v1,v2) 454 * ----x--------------x------------------------x---------------- 455 * 456 * The collinearity condition is 457 * 458 * u2-w2 w2-v2 459 * ------- = ------- (1) 460 * u1-w1 w1-v1 461 * 462 * Multiplying (1) with denominators and simplifying is 463 * 464 * u2w1 - u2v1 + w2v1 - u1w2 + u1v2 - w1v2 = 0 465 */ 466 467 return [['(', u2, ')*(', w1, ')-(', u2, ')*(', v1, ')+(', w2, ')*(', v1, ')-(', u1, ')*(', w2, ')+(', u1, ')*(', v2, ')-(', w1, ')*(', v2, ')'].join('')]; 468 }, 469 470 /** 471 * Calculates the y intersect of the line. 472 * @returns {Number} The y intersect. 473 */ 474 getRise: function () { 475 if (Math.abs(this.stdform[2]) >= Mat.eps) { 476 return -this.stdform[0] / this.stdform[2]; 477 } 478 479 return Infinity; 480 }, 481 482 /** 483 * Calculates the slope of the line. 484 * @returns {Number} The slope of the line or Infinity if the line is parallel to the y-axis. 485 */ 486 getSlope: function () { 487 if (Math.abs(this.stdform[2]) >= Mat.eps) { 488 return -this.stdform[1] / this.stdform[2]; 489 } 490 491 return Infinity; 492 }, 493 494 /** 495 * Determines the angle between the positive x axis and the line. 496 * @returns {Number} 497 */ 498 getAngle: function () { 499 return Math.atan2(-this.stdform[1], this.stdform[2]); 500 }, 501 502 /** 503 * Determines whether the line is drawn beyond {@link #point1} and {@link #point2} and updates the line. 504 * @param {Boolean} straightFirst True if the Line shall be drawn beyond {@link #point1}, false otherwise. 505 * @param {Boolean} straightLast True if the Line shall be drawn beyond {@link #point2}, false otherwise. 506 * @see #straightFirst 507 * @see #straightLast 508 * @private 509 */ 510 setStraight: function (straightFirst, straightLast) { 511 this.visProp.straightfirst = straightFirst; 512 this.visProp.straightlast = straightLast; 513 514 this.board.renderer.updateLine(this); 515 return this; 516 }, 517 518 // documented in geometry element 519 getTextAnchor: function () { 520 return new Coords(Const.COORDS_BY_USER, [0.5 * (this.point2.X() + this.point1.X()), 0.5 * (this.point2.Y() + this.point1.Y())], this.board); 521 }, 522 523 /** 524 * Adjusts Label coords relative to Anchor. DESCRIPTION 525 * @private 526 */ 527 setLabelRelativeCoords: function (relCoords) { 528 if (Type.exists(this.label)) { 529 this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [relCoords[0], -relCoords[1]], this.board); 530 } 531 }, 532 533 // documented in geometry element 534 getLabelAnchor: function () { 535 var x, y, 536 fs = 0, 537 sx = 0, 538 sy = 0, 539 c1 = new Coords(Const.COORDS_BY_USER, this.point1.coords.usrCoords, this.board), 540 c2 = new Coords(Const.COORDS_BY_USER, this.point2.coords.usrCoords, this.board); 541 542 if (this.visProp.straightfirst || this.visProp.straightlast) { 543 Geometry.calcStraight(this, c1, c2, 0); 544 } 545 546 c1 = c1.scrCoords; 547 c2 = c2.scrCoords; 548 549 if (!Type.exists(this.label)) { 550 return new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], this.board); 551 } 552 553 switch (this.label.visProp.position) { 554 case 'lft': 555 case 'llft': 556 case 'ulft': 557 if (c1[1] <= c2[1]) { 558 x = c1[1]; 559 y = c1[2]; 560 } else { 561 x = c2[1]; 562 y = c2[2]; 563 } 564 break; 565 case 'rt': 566 case 'lrt': 567 case 'urt': 568 if (c1[1] > c2[1]) { 569 x = c1[1]; 570 y = c1[2]; 571 } else { 572 x = c2[1]; 573 y = c2[2]; 574 } 575 break; 576 default: 577 x = 0.5 * (c1[1] + c2[1]); 578 y = 0.5 * (c1[2] + c2[2]); 579 } 580 581 // Correct label offsets if the label seems to be outside of camvas. 582 if (this.visProp.straightfirst || this.visProp.straightlast) { 583 if (Type.exists(this.label)) { // Does not exist during createLabel 584 sx = parseFloat(this.label.visProp.offset[0]); 585 sy = parseFloat(this.label.visProp.offset[1]); 586 fs = this.label.visProp.fontsize; 587 } 588 589 if (Math.abs(x) < Mat.eps) { 590 x = sx; 591 } else if (this.board.canvasWidth + Mat.eps > x && x > this.board.canvasWidth - fs - Mat.eps) { 592 x = this.board.canvasWidth - sx - fs; 593 } 594 595 if (Mat.eps + fs > y && y > -Mat.eps) { 596 y = sy + fs; 597 } else if (this.board.canvasHeight + Mat.eps > y && y > this.board.canvasHeight - fs - Mat.eps) { 598 y = this.board.canvasHeight - sy; 599 } 600 } 601 602 return new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board); 603 }, 604 605 // documented in geometry element 606 cloneToBackground: function () { 607 var copy = {}, r, s, er; 608 609 copy.id = this.id + 'T' + this.numTraces; 610 copy.elementClass = Const.OBJECT_CLASS_LINE; 611 this.numTraces++; 612 copy.point1 = this.point1; 613 copy.point2 = this.point2; 614 615 copy.stdform = this.stdform; 616 617 copy.board = this.board; 618 619 copy.visProp = Type.deepCopy(this.visProp, this.visProp.traceattributes, true); 620 copy.visProp.layer = this.board.options.layer.trace; 621 Type.clearVisPropOld(copy); 622 623 s = this.getSlope(); 624 r = this.getRise(); 625 copy.getSlope = function () { 626 return s; 627 }; 628 copy.getRise = function () { 629 return r; 630 }; 631 632 er = this.board.renderer.enhancedRendering; 633 this.board.renderer.enhancedRendering = true; 634 this.board.renderer.drawLine(copy); 635 this.board.renderer.enhancedRendering = er; 636 this.traces[copy.id] = copy.rendNode; 637 638 return this; 639 }, 640 641 /** 642 * Add transformations to this line. 643 * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of 644 * {@link JXG.Transformation}s. 645 * @returns {JXG.Line} Reference to this line object. 646 */ 647 addTransform: function (transform) { 648 var i, 649 list = Type.isArray(transform) ? transform : [transform], 650 len = list.length; 651 652 for (i = 0; i < len; i++) { 653 this.point1.transformations.push(list[i]); 654 this.point2.transformations.push(list[i]); 655 } 656 657 return this; 658 }, 659 660 // see GeometryElement.js 661 snapToGrid: function (pos) { 662 var c1, c2, dc, t, v, ticks, 663 x, y, sX, sY; 664 665 if (this.visProp.snaptogrid) { 666 if (this.parents.length < 3) { // Line through two points 667 this.point1.handleSnapToGrid(true); 668 this.point2.handleSnapToGrid(true); 669 /* 670 if (this.point1.visProp.snaptogrid || this.point2.visProp.snaptogrid) { 671 this.point1.snapToGrid(); 672 this.point2.snapToGrid(); 673 */ 674 } else if (JXG.exists(pos)) { // Free line 675 sX = this.visProp.snapsizex; 676 sY = this.visProp.snapsizey; 677 678 c1 = new Coords(Const.COORDS_BY_SCREEN, [pos.Xprev, pos.Yprev], this.board); 679 680 x = c1.usrCoords[1]; 681 y = c1.usrCoords[2]; 682 683 if (sX <= 0 && this.board.defaultAxes && this.board.defaultAxes.x.defaultTicks) { 684 ticks = this.board.defaultAxes.x.defaultTicks; 685 sX = ticks.ticksDelta * (ticks.visProp.minorticks + 1); 686 } 687 if (sY <= 0 && this.board.defaultAxes && this.board.defaultAxes.y.defaultTicks) { 688 ticks = this.board.defaultAxes.y.defaultTicks; 689 sY = ticks.ticksDelta * (ticks.visProp.minorticks + 1); 690 } 691 692 // if no valid snap sizes are available, don't change the coords. 693 if (sX > 0 && sY > 0) { 694 // projectCoordsToLine 695 /* 696 v = [0, this.stdform[1], this.stdform[2]]; 697 v = Mat.crossProduct(v, c1.usrCoords); 698 c2 = Geometry.meetLineLine(v, this.stdform, 0, this.board); 699 */ 700 c2 = Geometry.projectPointToLine({coords: c1}, this, this.board); 701 702 dc = Statistics.subtract([1, Math.round(x / sX) * sX, Math.round(y / sY) * sY], c2.usrCoords); 703 t = this.board.create('transform', dc.slice(1), {type: 'translate'}); 704 t.applyOnce([this.point1, this.point2]); 705 } 706 } 707 } else { 708 this.point1.snapToGrid(); 709 this.point2.snapToGrid(); 710 } 711 712 return this; 713 }, 714 715 // see element.js 716 snapToPoints: function () { 717 var forceIt = this.visProp.snaptopoints; 718 719 if (this.parents.length < 3) { // Line through two points 720 this.point1.handleSnapToPoints(forceIt); 721 this.point2.handleSnapToPoints(forceIt); 722 } 723 724 return this; 725 }, 726 727 /** 728 * Treat the line as parametric curve in homogeneous coordinates, where the parameter t runs from 0 to 1. 729 * First we transform the interval [0,1] to [-1,1]. 730 * If the line has homogeneous coordinates [c,a,b] = stdform[] then the direction of the line is [b,-a]. 731 * Now, we take one finite point that defines the line, i.e. we take either point1 or point2 (in case the line is not the ideal line). 732 * Let the coordinates of that point be [z, x, y]. 733 * Then, the curve runs linearly from 734 * [0, b, -a] (t=-1) to [z, x, y] (t=0) 735 * and 736 * [z, x, y] (t=0) to [0, -b, a] (t=1) 737 * 738 * @param {Number} t Parameter running from 0 to 1. 739 * @returns {Number} X(t) x-coordinate of the line treated as parametric curve. 740 * */ 741 X: function (t) { 742 var x, 743 b = this.stdform[2]; 744 745 x = (Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps) ? 746 this.point1.coords.usrCoords[1] : 747 this.point2.coords.usrCoords[1]; 748 749 t = (t - 0.5) * 2; 750 751 return (1 - Math.abs(t)) * x - t * b; 752 }, 753 754 /** 755 * Treat the line as parametric curve in homogeneous coordinates. See {@link #X} for a detailed description. 756 * @param {Number} t Parameter running from 0 to 1. 757 * @returns {Number} Y(t) y-coordinate of the line treated as parametric curve. 758 */ 759 Y: function (t) { 760 var y, 761 a = this.stdform[1]; 762 763 y = (Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps) ? 764 this.point1.coords.usrCoords[2] : 765 this.point2.coords.usrCoords[2]; 766 767 t = (t - 0.5) * 2; 768 769 return (1 - Math.abs(t)) * y + t * a; 770 }, 771 772 /** 773 * Treat the line as parametric curve in homogeneous coordinates. See {@link #X} for a detailed description. 774 * @param {Number} t Parameter running from 0 to 1. 775 * @returns {Number} Z(t) z-coordinate of the line treated as parametric curve. 776 */ 777 Z: function (t) { 778 var z = (Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps) ? 779 this.point1.coords.usrCoords[0] : 780 this.point2.coords.usrCoords[0]; 781 782 t = (t - 0.5) * 2; 783 784 return (1 - Math.abs(t)) * z; 785 }, 786 787 788 /** 789 * The distance between the two points defining the line. 790 * @returns {Number} 791 */ 792 L: function () { 793 return this.point1.Dist(this.point2); 794 }, 795 796 /** 797 * Treat the element as a parametric curve 798 * @private 799 */ 800 minX: function () { 801 return 0.0; 802 }, 803 804 /** 805 * Treat the element as parametric curve 806 * @private 807 */ 808 maxX: function () { 809 return 1.0; 810 }, 811 812 // documented in geometry element 813 bounds: function () { 814 var p1c = this.point1.coords.usrCoords, 815 p2c = this.point2.coords.usrCoords; 816 817 return [Math.min(p1c[1], p2c[1]), Math.max(p1c[2], p2c[2]), Math.max(p1c[1], p2c[1]), Math.min(p1c[2], p2c[2])]; 818 }, 819 820 /** 821 * Adds ticks to this line. Ticks can be added to any kind of line: line, arrow, and axis. 822 * @param {JXG.Ticks} ticks Reference to a ticks object which is describing the ticks (color, distance, how many, etc.). 823 * @returns {String} Id of the ticks object. 824 */ 825 addTicks: function (ticks) { 826 if (ticks.id === '' || !Type.exists(ticks.id)) { 827 ticks.id = this.id + '_ticks_' + (this.ticks.length + 1); 828 } 829 830 this.board.renderer.drawTicks(ticks); 831 this.ticks.push(ticks); 832 833 return ticks.id; 834 }, 835 836 // documented in GeometryElement.js 837 remove: function () { 838 this.removeAllTicks(); 839 GeometryElement.prototype.remove.call(this); 840 }, 841 842 /** 843 * Removes all ticks from a line. 844 */ 845 removeAllTicks: function () { 846 var i, t; 847 848 for (t = this.ticks.length; t > 0; t--) { 849 this.removeTicks(this.ticks[t - 1]); 850 } 851 852 this.ticks = []; 853 this.board.update(); 854 }, 855 856 /** 857 * Removes ticks identified by parameter named tick from this line. 858 * @param {JXG.Ticks} tick Reference to tick object to remove. 859 */ 860 removeTicks: function (tick) { 861 var t, j; 862 863 if (Type.exists(this.defaultTicks) && this.defaultTicks === tick) { 864 this.defaultTicks = null; 865 } 866 867 for (t = this.ticks.length; t > 0; t--) { 868 if (this.ticks[t - 1] === tick) { 869 this.board.removeObject(this.ticks[t - 1]); 870 871 if (this.ticks[t - 1].ticks) { 872 for (j = 0; j < this.ticks[t - 1].ticks.length; j++) { 873 if (Type.exists(this.ticks[t - 1].labels[j])) { 874 this.board.removeObject(this.ticks[t - 1].labels[j]); 875 } 876 } 877 } 878 879 delete this.ticks[t - 1]; 880 break; 881 } 882 } 883 }, 884 885 hideElement: function () { 886 var i; 887 888 GeometryElement.prototype.hideElement.call(this); 889 890 for (i = 0; i < this.ticks.length; i++) { 891 this.ticks[i].hideElement(); 892 } 893 }, 894 895 showElement: function () { 896 var i; 897 898 GeometryElement.prototype.showElement.call(this); 899 900 for (i = 0; i < this.ticks.length; i++) { 901 this.ticks[i].showElement(); 902 } 903 } 904 }); 905 906 /** 907 * @class This element is used to provide a constructor for a general line. A general line is given by two points. By setting additional properties 908 * a line can be used as an arrow and/or axis. 909 * @pseudo 910 * @description 911 * @name Line 912 * @augments JXG.Line 913 * @constructor 914 * @type JXG.Line 915 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 916 * @param {JXG.Point,array,function_JXG.Point,array,function} point1,point2 Parent elements can be two elements either of type {@link JXG.Point} or array of 917 * numbers describing the coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 918 * It is possible to provide a function returning an array or a point, instead of providing an array or a point. 919 * @param {Number,function_Number,function_Number,function} c,a,b A line can also be created providing three numbers. The line is then described by 920 * the set of solutions of the equation <tt>a*x+b*y+c*z = 0</tt>. It is possible to provide three functions returning numbers, too. 921 * @param {function} f This function must return an array containing three numbers forming the line's homogeneous coordinates. 922 * @example 923 * // Create a line using point and coordinates/ 924 * // The second point will be fixed and invisible. 925 * var p1 = board.create('point', [4.5, 2.0]); 926 * var l1 = board.create('line', [p1, [1.0, 1.0]]); 927 * </pre><div id="c0ae3461-10c4-4d39-b9be-81d74759d122" style="width: 300px; height: 300px;"></div> 928 * <script type="text/javascript"> 929 * var glex1_board = JXG.JSXGraph.initBoard('c0ae3461-10c4-4d39-b9be-81d74759d122', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 930 * var glex1_p1 = glex1_board.create('point', [4.5, 2.0]); 931 * var glex1_l1 = glex1_board.create('line', [glex1_p1, [1.0, 1.0]]); 932 * </script><pre> 933 * @example 934 * // Create a line using three coordinates 935 * var l1 = board.create('line', [1.0, -2.0, 3.0]); 936 * </pre><div id="cf45e462-f964-4ba4-be3a-c9db94e2593f" style="width: 300px; height: 300px;"></div> 937 * <script type="text/javascript"> 938 * var glex2_board = JXG.JSXGraph.initBoard('cf45e462-f964-4ba4-be3a-c9db94e2593f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 939 * var glex2_l1 = glex2_board.create('line', [1.0, -2.0, 3.0]); 940 * </script><pre> 941 */ 942 JXG.createLine = function (board, parents, attributes) { 943 var ps, el, p1, p2, i, attr, 944 c = [], 945 constrained = false, 946 isDraggable; 947 948 /** 949 * The line is defined by two points or coordinates of two points. 950 * In the latter case, the points are created. 951 */ 952 if (parents.length === 2) { 953 // point 1 given by coordinates 954 if (Type.isArray(parents[0]) && parents[0].length > 1) { 955 attr = Type.copyAttributes(attributes, board.options, 'line', 'point1'); 956 p1 = board.create('point', parents[0], attr); 957 } else if (Type.isString(parents[0]) || Type.isPoint(parents[0])) { 958 p1 = board.select(parents[0]); 959 } else if ((typeof parents[0] === 'function') && ( Type.isPoint(parents[0]()) )) { 960 p1 = parents[0](); 961 constrained = true; 962 } else if ((typeof parents[0] === 'function') && (parents[0]().length && parents[0]().length === 2)) { 963 attr = Type.copyAttributes(attributes, board.options, 'line', 'point1'); 964 p1 = Point.createPoint(board, parents[0](), attr); 965 constrained = true; 966 } else { 967 throw new Error("JSXGraph: Can't create line with parent types '" + 968 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 969 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"); 970 } 971 972 // point 2 given by coordinates 973 if (Type.isArray(parents[1]) && parents[1].length > 1) { 974 attr = Type.copyAttributes(attributes, board.options, 'line', 'point2'); 975 p2 = board.create('point', parents[1], attr); 976 } else if (Type.isString(parents[1]) || Type.isPoint(parents[1])) { 977 p2 = board.select(parents[1]); 978 } else if ((typeof parents[1] === 'function') && ( Type.isPoint(parents[1]()) )) { 979 p2 = parents[1](); 980 constrained = true; 981 } else if ((typeof parents[1] === 'function') && (parents[1]().length && parents[1]().length === 2)) { 982 attr = Type.copyAttributes(attributes, board.options, 'line', 'point2'); 983 p2 = Point.createPoint(board, parents[1](), attr); 984 constrained = true; 985 } else { 986 throw new Error("JSXGraph: Can't create line with parent types '" + 987 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 988 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"); 989 } 990 991 attr = Type.copyAttributes(attributes, board.options, 'line'); 992 993 el = new JXG.Line(board, p1, p2, attr); 994 if (constrained) { 995 el.constrained = true; 996 el.funp1 = parents[0]; 997 el.funp2 = parents[1]; 998 } else { 999 el.isDraggable = true; 1000 } 1001 1002 if (!el.constrained) { 1003 el.parents = [p1.id, p2.id]; 1004 } 1005 // Line is defined by three homogeneous coordinates. 1006 // Also in this case points are created. 1007 } else if (parents.length === 3) { 1008 // free line 1009 isDraggable = true; 1010 for (i = 0; i < 3; i++) { 1011 if (typeof parents[i] === 'number') { 1012 // createFunction will just wrap a function around our constant number 1013 // that does nothing else but to return that number. 1014 c[i] = Type.createFunction(parents[i]); 1015 } else if (typeof parents[i] === 'function') { 1016 c[i] = parents[i]; 1017 isDraggable = false; 1018 } else { 1019 throw new Error("JSXGraph: Can't create line with parent types '" + 1020 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." + 1021 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"); 1022 } 1023 } 1024 1025 // point 1 is the midpoint between (0,c,-b) and point 2. => point1 is finite. 1026 attr = Type.copyAttributes(attributes, board.options, 'line', 'point1'); 1027 if (isDraggable) { 1028 p1 = board.create('point', [ 1029 c[2]() * c[2]() + c[1]() * c[1](), 1030 c[2]() - c[1]() * c[0]() + c[2](), 1031 -c[1]() - c[2]() * c[0]() - c[1]() 1032 ], attr); 1033 } else { 1034 p1 = board.create('point', [ 1035 function () { 1036 return (c[2]() * c[2]() + c[1]() * c[1]()) * 0.5; 1037 }, 1038 function () { 1039 return (c[2]() - c[1]() * c[0]() + c[2]()) * 0.5; 1040 }, 1041 function () { 1042 return (-c[1]() - c[2]() * c[0]() - c[1]()) * 0.5; 1043 }], attr); 1044 } 1045 1046 // point 2: (b^2+c^2,-ba+c,-ca-b) 1047 attr = Type.copyAttributes(attributes, board.options, 'line', 'point2'); 1048 if (isDraggable) { 1049 p2 = board.create('point', [ 1050 c[2]() * c[2]() + c[1]() * c[1](), 1051 -c[1]() * c[0]() + c[2](), 1052 -c[2]() * c[0]() - c[1]() 1053 ], attr); 1054 } else { 1055 p2 = board.create('point', [ 1056 function () { 1057 return c[2]() * c[2]() + c[1]() * c[1](); 1058 }, 1059 function () { 1060 return -c[1]() * c[0]() + c[2](); 1061 }, 1062 function () { 1063 return -c[2]() * c[0]() - c[1](); 1064 }], attr); 1065 } 1066 1067 // If the line will have a glider and board.suspendUpdate() has been called, we 1068 // need to compute the initial position of the two points p1 and p2. 1069 p1.prepareUpdate().update(); 1070 p2.prepareUpdate().update(); 1071 attr = Type.copyAttributes(attributes, board.options, 'line'); 1072 el = new JXG.Line(board, p1, p2, attr); 1073 // Not yet working, because the points are not draggable. 1074 el.isDraggable = isDraggable; 1075 1076 if (isDraggable) { 1077 el.parents = [c[0](), c[1](), c[2]()]; 1078 } 1079 // The parent array contains a function which returns two points. 1080 } else if ((parents.length === 1) && (typeof parents[0] === 'function') && (parents[0]().length === 2) && 1081 (Type.isPoint(parents[0]()[0])) && 1082 (Type.isPoint(parents[0]()[1]))) { 1083 ps = parents[0](); 1084 attr = Type.copyAttributes(attributes, board.options, 'line'); 1085 el = new JXG.Line(board, ps[0], ps[1], attr); 1086 el.constrained = true; 1087 el.funps = parents[0]; 1088 } else if ((parents.length === 1) && (typeof parents[0] === 'function') && (parents[0]().length === 3) && 1089 (typeof parents[0]()[0] === 'number') && 1090 (typeof parents[0]()[1] === 'number') && 1091 (typeof parents[0]()[2] === 'number')) { 1092 ps = parents[0]; 1093 1094 attr = Type.copyAttributes(attributes, board.options, 'line', 'point1'); 1095 p1 = board.create('point', [ 1096 function () { 1097 var c = ps(); 1098 1099 return [ 1100 (c[2] * c[2] + c[1] * c[1]) * 0.5, 1101 (c[2] - c[1] * c[0] + c[2]) * 0.5, 1102 (-c[1] - c[2] * c[0] - c[1]) * 0.5 1103 ]; 1104 }], attr); 1105 1106 attr = Type.copyAttributes(attributes, board.options, 'line', 'point2'); 1107 p2 = board.create('point', [ 1108 function () { 1109 var c = ps(); 1110 1111 return [ 1112 c[2] * c[2] + c[1] * c[1], 1113 -c[1] * c[0] + c[2], 1114 -c[2] * c[0] - c[1] 1115 ]; 1116 }], attr); 1117 1118 attr = Type.copyAttributes(attributes, board.options, 'line'); 1119 el = new JXG.Line(board, p1, p2, attr); 1120 1121 el.constrained = true; 1122 el.funps = parents[0]; 1123 } else { 1124 throw new Error("JSXGraph: Can't create line with parent types '" + 1125 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1126 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"); 1127 } 1128 1129 return el; 1130 }; 1131 1132 JXG.registerElement('line', JXG.createLine); 1133 1134 /** 1135 * @class This element is used to provide a constructor for a segment. 1136 * It's strictly spoken just a wrapper for element {@link Line} with {@link JXG.Line#straightFirst} 1137 * and {@link JXG.Line#straightLast} properties set to false. If there is a third variable then the 1138 * segment has a fixed length (which may be a function, too). 1139 * @pseudo 1140 * @description 1141 * @name Segment 1142 * @augments JXG.Line 1143 * @constructor 1144 * @type JXG.Line 1145 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1146 * @param {JXG.Point,array_JXG.Point,array} point1, point2 Parent elements can be two elements either of type {@link JXG.Point} 1147 * or array of numbers describing the 1148 * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 1149 * @param {number,function} length (optional) The points are adapted - if possible - such that their distance 1150 * has a this value. 1151 * @see Line 1152 * @example 1153 * // Create a segment providing two points. 1154 * var p1 = board.create('point', [4.5, 2.0]); 1155 * var p2 = board.create('point', [1.0, 1.0]); 1156 * var l1 = board.create('segment', [p1, p2]); 1157 * </pre><div id="d70e6aac-7c93-4525-a94c-a1820fa38e2f" style="width: 300px; height: 300px;"></div> 1158 * <script type="text/javascript"> 1159 * var slex1_board = JXG.JSXGraph.initBoard('d70e6aac-7c93-4525-a94c-a1820fa38e2f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1160 * var slex1_p1 = slex1_board.create('point', [4.5, 2.0]); 1161 * var slex1_p2 = slex1_board.create('point', [1.0, 1.0]); 1162 * var slex1_l1 = slex1_board.create('segment', [slex1_p1, slex1_p2]); 1163 * </script><pre> 1164 * 1165 * @example 1166 * // Create a segment providing two points. 1167 * var p1 = board.create('point', [4.0, 1.0]); 1168 * var p2 = board.create('point', [1.0, 1.0]); 1169 * var l1 = board.create('segment', [p1, p2]); 1170 * var p3 = board.create('point', [4.0, 2.0]); 1171 * var p4 = board.create('point', [1.0, 2.0]); 1172 * var l2 = board.create('segment', [p3, p4, 3]); 1173 * var p5 = board.create('point', [4.0, 3.0]); 1174 * var p6 = board.create('point', [1.0, 4.0]); 1175 * var l3 = board.create('segment', [p5, p6, function(){ return l1.L();} ]); 1176 * </pre><div id="617336ba-0705-4b2b-a236-c87c28ef25be" style="width: 300px; height: 300px;"></div> 1177 * <script type="text/javascript"> 1178 * var slex2_board = JXG.JSXGraph.initBoard('617336ba-0705-4b2b-a236-c87c28ef25be', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1179 * var slex2_p1 = slex2_board.create('point', [4.0, 1.0]); 1180 * var slex2_p2 = slex2_board.create('point', [1.0, 1.0]); 1181 * var slex2_l1 = slex2_board.create('segment', [slex2_p1, slex2_p2]); 1182 * var slex2_p3 = slex2_board.create('point', [4.0, 2.0]); 1183 * var slex2_p4 = slex2_board.create('point', [1.0, 2.0]); 1184 * var slex2_l2 = slex2_board.create('segment', [slex2_p3, slex2_p4, 3]); 1185 * var slex2_p5 = slex2_board.create('point', [4.0, 2.0]); 1186 * var slex2_p6 = slex2_board.create('point', [1.0, 2.0]); 1187 * var slex2_l3 = slex2_board.create('segment', [slex2_p5, slex2_p6, function(){ return slex2_l1.L();}]); 1188 * </script><pre> 1189 * 1190 */ 1191 JXG.createSegment = function (board, parents, attributes) { 1192 var el, i, attr; 1193 1194 attributes.straightFirst = false; 1195 attributes.straightLast = false; 1196 attr = Type.copyAttributes(attributes, board.options, 'segment'); 1197 1198 el = board.create('line', parents.slice(0, 2), attr); 1199 1200 if (parents.length === 3) { 1201 el.hasFixedLength = true; 1202 1203 if (Type.isNumber(parents[2])) { 1204 el.fixedLength = function () { 1205 return parents[2]; 1206 }; 1207 } else if (Type.isFunction(parents[2])) { 1208 el.fixedLength = parents[2]; 1209 } else { 1210 throw new Error("JSXGraph: Can't create segment with third parent type '" + 1211 (typeof parents[2]) + "'." + 1212 "\nPossible third parent types: number or function"); 1213 } 1214 1215 el.fixedLengthOldCoords = []; 1216 el.fixedLengthOldCoords[0] = new Coords(Const.COORDS_BY_USER, el.point1.coords.usrCoords.slice(1, 3), board); 1217 el.fixedLengthOldCoords[1] = new Coords(Const.COORDS_BY_USER, el.point2.coords.usrCoords.slice(1, 3), board); 1218 } 1219 1220 el.elType = 'segment'; 1221 1222 return el; 1223 }; 1224 1225 JXG.registerElement('segment', JXG.createSegment); 1226 1227 /** 1228 * @class This element is used to provide a constructor for arrow, which is just a wrapper for element {@link Line} with {@link JXG.Line#straightFirst} 1229 * and {@link JXG.Line#straightLast} properties set to false and {@link JXG.Line#lastArrow} set to true. 1230 * @pseudo 1231 * @description 1232 * @name Arrow 1233 * @augments JXG.Line 1234 * @constructor 1235 * @type JXG.Line 1236 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1237 * @param {JXG.Point,array_JXG.Point,array} point1,point2 Parent elements can be two elements either of type {@link JXG.Point} or array of numbers describing the 1238 * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 1239 * @param {Number_Number_Number} a,b,c A line can also be created providing three numbers. The line is then described by the set of solutions 1240 * of the equation <tt>a*x+b*y+c*z = 0</tt>. 1241 * @see Line 1242 * @example 1243 * // Create an arrow providing two points. 1244 * var p1 = board.create('point', [4.5, 2.0]); 1245 * var p2 = board.create('point', [1.0, 1.0]); 1246 * var l1 = board.create('arrow', [p1, p2]); 1247 * </pre><div id="1d26bd22-7d6d-4018-b164-4c8bc8d22ccf" style="width: 300px; height: 300px;"></div> 1248 * <script type="text/javascript"> 1249 * var alex1_board = JXG.JSXGraph.initBoard('1d26bd22-7d6d-4018-b164-4c8bc8d22ccf', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1250 * var alex1_p1 = alex1_board.create('point', [4.5, 2.0]); 1251 * var alex1_p2 = alex1_board.create('point', [1.0, 1.0]); 1252 * var alex1_l1 = alex1_board.create('arrow', [alex1_p1, alex1_p2]); 1253 * </script><pre> 1254 */ 1255 JXG.createArrow = function (board, parents, attributes) { 1256 var el; 1257 1258 attributes.firstArrow = false; 1259 attributes.lastArrow = true; 1260 el = board.create('line', parents, attributes).setStraight(false, false); 1261 //el.setArrow(false, true); 1262 el.type = Const.OBJECT_TYPE_VECTOR; 1263 el.elType = 'arrow'; 1264 1265 return el; 1266 }; 1267 1268 JXG.registerElement('arrow', JXG.createArrow); 1269 1270 /** 1271 * @class This element is used to provide a constructor for an axis. It's strictly spoken just a wrapper for element {@link Line} with {@link JXG.Line#straightFirst} 1272 * and {@link JXG.Line#straightLast} properties set to true. Additionally {@link JXG.Line#lastArrow} is set to true and default {@link Ticks} will be created. 1273 * @pseudo 1274 * @description 1275 * @name Axis 1276 * @augments JXG.Line 1277 * @constructor 1278 * @type JXG.Line 1279 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1280 * @param {JXG.Point,array_JXG.Point,array} point1,point2 Parent elements can be two elements either of type {@link JXG.Point} or array of numbers describing the 1281 * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 1282 * @param {Number_Number_Number} a,b,c A line can also be created providing three numbers. The line is then described by the set of solutions 1283 * of the equation <tt>a*x+b*y+c*z = 0</tt>. 1284 * @example 1285 * // Create an axis providing two coord pairs. 1286 * var l1 = board.create('axis', [[0.0, 1.0], [1.0, 1.3]]); 1287 * </pre><div id="4f414733-624c-42e4-855c-11f5530383ae" style="width: 300px; height: 300px;"></div> 1288 * <script type="text/javascript"> 1289 * var axex1_board = JXG.JSXGraph.initBoard('4f414733-624c-42e4-855c-11f5530383ae', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1290 * var axex1_l1 = axex1_board.create('axis', [[0.0, 1.0], [1.0, 1.3]]); 1291 * </script><pre> 1292 */ 1293 JXG.createAxis = function (board, parents, attributes) { 1294 var attr, el, els, dist; 1295 1296 // Arrays oder Punkte, mehr brauchen wir nicht. 1297 if ((Type.isArray(parents[0]) || Type.isPoint(parents[0])) && (Type.isArray(parents[1]) || Type.isPoint(parents[1]))) { 1298 attr = Type.copyAttributes(attributes, board.options, 'axis'); 1299 el = board.create('line', parents, attr); 1300 el.type = Const.OBJECT_TYPE_AXIS; 1301 el.isDraggable = false; 1302 el.point1.isDraggable = false; 1303 el.point2.isDraggable = false; 1304 1305 for (els in el.ancestors) { 1306 if (el.ancestors.hasOwnProperty(els)) { 1307 el.ancestors[els].type = Const.OBJECT_TYPE_AXISPOINT; 1308 } 1309 } 1310 1311 attr = Type.copyAttributes(attributes, board.options, 'axis', 'ticks'); 1312 if (Type.exists(attr.ticksdistance)) { 1313 dist = attr.ticksdistance; 1314 } else if (Type.isArray(attr.ticks)) { 1315 dist = attr.ticks; 1316 } else { 1317 dist = 1.0; 1318 } 1319 1320 /** 1321 * The ticks attached to the axis. 1322 * @memberOf Axis.prototype 1323 * @name defaultTicks 1324 * @type JXG.Ticks 1325 */ 1326 el.defaultTicks = board.create('ticks', [el, dist], attr); 1327 1328 el.defaultTicks.dump = false; 1329 1330 el.elType = 'axis'; 1331 el.subs = { 1332 ticks: el.defaultTicks 1333 }; 1334 } else { 1335 throw new Error("JSXGraph: Can't create axis with parent types '" + 1336 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1337 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]"); 1338 } 1339 1340 return el; 1341 }; 1342 1343 JXG.registerElement('axis', JXG.createAxis); 1344 1345 /** 1346 * @class With the element tangent the slope of a line, circle, or curve in a certain point can be visualized. A tangent is always constructed 1347 * by a glider on a line, circle, or curve and describes the tangent in the glider point on that line, circle, or curve. 1348 * @pseudo 1349 * @description 1350 * @name Tangent 1351 * @augments JXG.Line 1352 * @constructor 1353 * @type JXG.Line 1354 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1355 * @param {Glider} g A glider on a line, circle, or curve. 1356 * @example 1357 * // Create a tangent providing a glider on a function graph 1358 * var c1 = board.create('curve', [function(t){return t},function(t){return t*t*t;}]); 1359 * var g1 = board.create('glider', [0.6, 1.2, c1]); 1360 * var t1 = board.create('tangent', [g1]); 1361 * </pre><div id="7b7233a0-f363-47dd-9df5-4018d0d17a98" style="width: 400px; height: 400px;"></div> 1362 * <script type="text/javascript"> 1363 * var tlex1_board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-4018d0d17a98', {boundingbox: [-6, 6, 6, -6], axis: true, showcopyright: false, shownavigation: false}); 1364 * var tlex1_c1 = tlex1_board.create('curve', [function(t){return t},function(t){return t*t*t;}]); 1365 * var tlex1_g1 = tlex1_board.create('glider', [0.6, 1.2, tlex1_c1]); 1366 * var tlex1_t1 = tlex1_board.create('tangent', [tlex1_g1]); 1367 * </script><pre> 1368 */ 1369 JXG.createTangent = function (board, parents, attributes) { 1370 var p, c, g, f, i, j, el, tangent; 1371 1372 // One arguments: glider on line, circle or curve 1373 if (parents.length === 1) { 1374 p = parents[0]; 1375 c = p.slideObject; 1376 // Two arguments: (point,F"|conic) or (line|curve|circle|conic,point). // Not yet: curve! 1377 } else if (parents.length === 2) { 1378 // In fact, for circles and conics it is the polar 1379 if (Type.isPoint(parents[0])) { 1380 p = parents[0]; 1381 c = parents[1]; 1382 } else if (Type.isPoint(parents[1])) { 1383 c = parents[0]; 1384 p = parents[1]; 1385 } else { 1386 throw new Error("JSXGraph: Can't create tangent with parent types '" + 1387 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1388 "\nPossible parent types: [glider], [point,line|curve|circle|conic]"); 1389 } 1390 } else { 1391 throw new Error("JSXGraph: Can't create tangent with parent types '" + 1392 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1393 "\nPossible parent types: [glider], [point,line|curve|circle|conic]"); 1394 } 1395 1396 if (c.elementClass === Const.OBJECT_CLASS_LINE) { 1397 tangent = board.create('line', [c.point1, c.point2], attributes); 1398 tangent.glider = p; 1399 } else if (c.elementClass === Const.OBJECT_CLASS_CURVE && c.type !== Const.OBJECT_TYPE_CONIC) { 1400 if (c.visProp.curvetype !== 'plot') { 1401 g = c.X; 1402 f = c.Y; 1403 tangent = board.create('line', [ 1404 function () { 1405 return -p.X() * Numerics.D(f)(p.position) + p.Y() * Numerics.D(g)(p.position); 1406 }, 1407 function () { 1408 return Numerics.D(f)(p.position); 1409 }, 1410 function () { 1411 return -Numerics.D(g)(p.position); 1412 } 1413 ], attributes); 1414 p.addChild(tangent); 1415 1416 // this is required for the geogebra reader to display a slope 1417 tangent.glider = p; 1418 } else { // curveType 'plot' 1419 // equation of the line segment: 0 = y*(x1-x2) + x*(y2-y1) + y1*x2-x1*y2 1420 tangent = board.create('line', [ 1421 function () { 1422 var i = Math.floor(p.position); 1423 1424 if (i === c.numberPoints - 1) { 1425 i--; 1426 } 1427 1428 if (i < 0) { 1429 return 1; 1430 } 1431 1432 return c.Y(i) * c.X(i + 1) - c.X(i) * c.Y(i + 1); 1433 }, 1434 function () { 1435 var i = Math.floor(p.position); 1436 1437 if (i === c.numberPoints - 1) { 1438 i--; 1439 } 1440 1441 if (i < 0) { 1442 return 0; 1443 } 1444 1445 return c.Y(i + 1) - c.Y(i); 1446 }, 1447 function () { 1448 var i = Math.floor(p.position); 1449 1450 if (i === c.numberPoints - 1) { 1451 i--; 1452 } 1453 1454 if (i < 0) { 1455 return 0.0; 1456 } 1457 1458 return c.X(i) - c.X(i + 1); 1459 }], attributes); 1460 1461 p.addChild(tangent); 1462 1463 // this is required for the geogebra reader to display a slope 1464 tangent.glider = p; 1465 } 1466 } else if (c.type === Const.OBJECT_TYPE_TURTLE) { 1467 tangent = board.create('line', [ 1468 function () { 1469 var i = Math.floor(p.position); 1470 1471 // run through all curves of this turtle 1472 for (j = 0; j < c.objects.length; j++) { 1473 el = c.objects[j]; 1474 1475 if (el.type === Const.OBJECT_TYPE_CURVE) { 1476 if (i < el.numberPoints) { 1477 break; 1478 } 1479 1480 i -= el.numberPoints; 1481 } 1482 } 1483 1484 if (i === el.numberPoints - 1) { 1485 i--; 1486 } 1487 1488 if (i < 0) { 1489 return 1; 1490 } 1491 1492 return el.Y(i) * el.X(i + 1) - el.X(i) * el.Y(i + 1); 1493 }, 1494 function () { 1495 var i = Math.floor(p.position); 1496 1497 // run through all curves of this turtle 1498 for (j = 0; j < c.objects.length; j++) { 1499 el = c.objects[j]; 1500 1501 if (el.type === Const.OBJECT_TYPE_CURVE) { 1502 if (i < el.numberPoints) { 1503 break; 1504 } 1505 1506 i -= el.numberPoints; 1507 } 1508 } 1509 1510 if (i === el.numberPoints - 1) { 1511 i--; 1512 } 1513 if (i < 0) { 1514 return 0; 1515 } 1516 1517 return el.Y(i + 1) - el.Y(i); 1518 }, 1519 function () { 1520 var i = Math.floor(p.position); 1521 1522 // run through all curves of this turtle 1523 for (j = 0; j < c.objects.length; j++) { 1524 el = c.objects[j]; 1525 if (el.type === Const.OBJECT_TYPE_CURVE) { 1526 if (i < el.numberPoints) { 1527 break; 1528 } 1529 i -= el.numberPoints; 1530 } 1531 } 1532 if (i === el.numberPoints - 1) { 1533 i--; 1534 } 1535 1536 if (i < 0) { 1537 return 0; 1538 } 1539 1540 return el.X(i) - el.X(i + 1); 1541 }], attributes); 1542 p.addChild(tangent); 1543 1544 // this is required for the geogebra reader to display a slope 1545 tangent.glider = p; 1546 } else if (c.elementClass === Const.OBJECT_CLASS_CIRCLE || c.type === Const.OBJECT_TYPE_CONIC) { 1547 // If p is not on c, the tangent is the polar. 1548 // This construction should work on conics, too. p has to lie on c. 1549 tangent = board.create('line', [ 1550 function () { 1551 return Mat.matVecMult(c.quadraticform, p.coords.usrCoords)[0]; 1552 }, 1553 function () { 1554 return Mat.matVecMult(c.quadraticform, p.coords.usrCoords)[1]; 1555 }, 1556 function () { 1557 return Mat.matVecMult(c.quadraticform, p.coords.usrCoords)[2]; 1558 }], attributes); 1559 1560 p.addChild(tangent); 1561 // this is required for the geogebra reader to display a slope 1562 tangent.glider = p; 1563 } 1564 1565 if (!Type.exists(tangent)) { 1566 throw new Error('JSXGraph: Couldn\'t create tangent with the given parents.'); 1567 } 1568 1569 tangent.elType = 'tangent'; 1570 tangent.type = Const.OBJECT_TYPE_TANGENT; 1571 tangent.parents = []; 1572 for (i = 0; i < parents.length; i++) { 1573 tangent.parents.push(parents[i].id); 1574 } 1575 1576 return tangent; 1577 }; 1578 1579 /** 1580 * @class This element is used to provide a constructor for the radical axis with respect to two circles with distinct centers. 1581 * The angular bisector of the polar lines of the circle centers with respect to the other circle is always the radical axis. 1582 * The radical axis passes through the intersection points when the circles intersect. 1583 * When a circle about the midpoint of circle centers, passing through the circle centers, intersects the circles, the polar lines pass through those intersection points. 1584 * @pseudo 1585 * @description 1586 * @name RadicalAxis 1587 * @augments JXG.Line 1588 * @constructor 1589 * @type JXG.Line 1590 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1591 * @param {JXG.Circle} circle Circle one of the two respective circles. 1592 * @param {JXG.Circle} circle Circle the other of the two respective circles. 1593 * @example 1594 * // Create the radical axis line with respect to two circles 1595 * var board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-5018d0d17a98', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 1596 * var p1 = board.create('point', [2, 3]); 1597 * var p2 = board.create('point', [1, 4]); 1598 * var c1 = board.create('circle', [p1, p2]); 1599 * var p3 = board.create('point', [6, 5]); 1600 * var p4 = board.create('point', [8, 6]); 1601 * var c2 = board.create('circle', [p3, p4]); 1602 * var r1 = board.create('radicalaxis', [c1, c2]); 1603 * </pre><div id='7b7233a0-f363-47dd-9df5-5018d0d17a98' class='jxgbox' style='width:400px; height:400px;'></div> 1604 * <script type='text/javascript'> 1605 * var rlex1_board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-5018d0d17a98', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 1606 * var rlex1_p1 = rlex1_board.create('point', [2, 3]); 1607 * var rlex1_p2 = rlex1_board.create('point', [1, 4]); 1608 * var rlex1_c1 = rlex1_board.create('circle', [rlex1_p1, rlex1_p2]); 1609 * var rlex1_p3 = rlex1_board.create('point', [6, 5]); 1610 * var rlex1_p4 = rlex1_board.create('point', [8, 6]); 1611 * var rlex1_c2 = rlex1_board.create('circle', [rlex1_p3, rlex1_p4]); 1612 * var rlex1_r1 = rlex1_board.create('radicalaxis', [rlex1_c1, rlex1_c2]); 1613 * </script><pre> 1614 */ 1615 JXG.createRadicalAxis = function (board, parents, attributes) { 1616 var el, el1, el2; 1617 1618 if (parents.length !== 2 || 1619 parents[0].elementClass !== Const.OBJECT_CLASS_CIRCLE || 1620 parents[1].elementClass !== Const.OBJECT_CLASS_CIRCLE) { 1621 // Failure 1622 throw new Error("JSXGraph: Can't create 'radical axis' with parent types '" + 1623 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1624 "\nPossible parent type: [circle,circle]"); 1625 } 1626 1627 el1 = board.select(parents[0]); 1628 el2 = board.select(parents[1]); 1629 1630 el = board.create('line', [function () { 1631 var a = el1.stdform, 1632 b = el2.stdform; 1633 1634 return Mat.matVecMult(Mat.transpose([a.slice(0, 3), b.slice(0, 3)]), [b[3], -a[3]]); 1635 }], attributes); 1636 1637 el.elType = 'radicalaxis'; 1638 el.parents = [el1.id, el2.id]; 1639 1640 el1.addChild(el); 1641 el2.addChild(el); 1642 1643 return el; 1644 }; 1645 1646 /** 1647 * @class This element is used to provide a constructor for the polar line of a point with respect to a conic or a circle. 1648 * @pseudo 1649 * @description The polar line is the unique reciprocal relationship of a point with respect to a conic. 1650 * The lines through the intersections of a conic and the polar line of a point with respect to that conic and through that point are tangent to the conic. 1651 * A point on a conic has the polar line of that point with respect to that conic as the tangent line to that conic at that point. 1652 * See {@link http://en.wikipedia.org/wiki/Pole_and_polar} for more information on pole and polar. 1653 * @name PolarLine 1654 * @augments JXG.Line 1655 * @constructor 1656 * @type JXG.Line 1657 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1658 * @param {JXG.Conic,JXG.Circle_JXG.Point} el1,el2 or 1659 * @param {JXG.Point_JXG.Conic,JXG.Circle} el1,el2 The result will be the polar line of the point with respect to the conic or the circle. 1660 * @example 1661 * // Create the polar line of a point with respect to a conic 1662 * var p1 = board.create('point', [-1, 2]); 1663 * var p2 = board.create('point', [ 1, 4]); 1664 * var p3 = board.create('point', [-1,-2]); 1665 * var p4 = board.create('point', [ 0, 0]); 1666 * var p5 = board.create('point', [ 4,-2]); 1667 * var c1 = board.create('conic',[p1,p2,p3,p4,p5]); 1668 * var p6 = board.create('point', [-1, 1]); 1669 * var l1 = board.create('polarline', [c1, p6]); 1670 * </pre><div id='7b7233a0-f363-47dd-9df5-6018d0d17a98' class='jxgbox' style='width:400px; height:400px;'></div> 1671 * <script type='text/javascript'> 1672 * var plex1_board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-6018d0d17a98', {boundingbox: [-3, 5, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 1673 * var plex1_p1 = plex1_board.create('point', [-1, 2]); 1674 * var plex1_p2 = plex1_board.create('point', [ 1, 4]); 1675 * var plex1_p3 = plex1_board.create('point', [-1,-2]); 1676 * var plex1_p4 = plex1_board.create('point', [ 0, 0]); 1677 * var plex1_p5 = plex1_board.create('point', [ 4,-2]); 1678 * var plex1_c1 = plex1_board.create('conic',[plex1_p1,plex1_p2,plex1_p3,plex1_p4,plex1_p5]); 1679 * var plex1_p6 = plex1_board.create('point', [-1, 1]); 1680 * var plex1_l1 = plex1_board.create('polarline', [plex1_c1, plex1_p6]); 1681 * </script><pre> 1682 * @example 1683 * // Create the polar line of a point with respect to a circle. 1684 * var p1 = board.create('point', [ 1, 1]); 1685 * var p2 = board.create('point', [ 2, 3]); 1686 * var c1 = board.create('circle',[p1,p2]); 1687 * var p3 = board.create('point', [ 6, 6]); 1688 * var l1 = board.create('polarline', [c1, p3]); 1689 * </pre><div id='7b7233a0-f363-47dd-9df5-7018d0d17a98' class='jxgbox' style='width:400px; height:400px;'></div> 1690 * <script type='text/javascript'> 1691 * var plex2_board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-7018d0d17a98', {boundingbox: [-3, 7, 7, -3], axis: true, showcopyright: false, shownavigation: false}); 1692 * var plex2_p1 = plex2_board.create('point', [ 1, 1]); 1693 * var plex2_p2 = plex2_board.create('point', [ 2, 3]); 1694 * var plex2_c1 = plex2_board.create('circle',[plex2_p1,plex2_p2]); 1695 * var plex2_p3 = plex2_board.create('point', [ 6, 6]); 1696 * var plex2_l1 = plex2_board.create('polarline', [plex2_c1, plex2_p3]); 1697 * </script><pre> 1698 */ 1699 JXG.createPolarLine = function (board, parents, attributes) { 1700 var el, el1, el2, 1701 firstParentIsConic, secondParentIsConic, 1702 firstParentIsPoint, secondParentIsPoint; 1703 1704 if (parents.length > 1) { 1705 firstParentIsConic = (parents[0].type === Const.OBJECT_TYPE_CONIC || 1706 parents[0].elementClass === Const.OBJECT_CLASS_CIRCLE); 1707 secondParentIsConic = (parents[1].type === Const.OBJECT_TYPE_CONIC || 1708 parents[1].elementClass === Const.OBJECT_CLASS_CIRCLE); 1709 1710 firstParentIsPoint = (Type.isPoint(parents[0])); 1711 secondParentIsPoint = (Type.isPoint(parents[1])); 1712 } 1713 1714 if (parents.length !== 2 || 1715 !((firstParentIsConic && secondParentIsPoint) || 1716 (firstParentIsPoint && secondParentIsConic))) { 1717 // Failure 1718 throw new Error("JSXGraph: Can't create 'polar line' with parent types '" + 1719 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1720 "\nPossible parent type: [conic|circle,point], [point,conic|circle]"); 1721 } 1722 1723 if (secondParentIsPoint) { 1724 el1 = board.select(parents[0]); 1725 el2 = board.select(parents[1]); 1726 } else { 1727 el1 = board.select(parents[1]); 1728 el2 = board.select(parents[0]); 1729 } 1730 1731 // Polar lines have been already provided in the tangent element. 1732 el = board.create('tangent', [el1, el2], attributes); 1733 1734 el.elType = 'polarline'; 1735 return el; 1736 }; 1737 1738 /** 1739 * Register the element type tangent at JSXGraph 1740 * @private 1741 */ 1742 JXG.registerElement('tangent', JXG.createTangent); 1743 JXG.registerElement('polar', JXG.createTangent); 1744 JXG.registerElement('radicalaxis', JXG.createRadicalAxis); 1745 JXG.registerElement('polarline', JXG.createPolarLine); 1746 1747 return { 1748 Line: JXG.Line, 1749 createLine: JXG.createLine, 1750 createTangent: JXG.createTangent, 1751 createPolar: JXG.createTangent, 1752 createSegment: JXG.createSegment, 1753 createAxis: JXG.createAxis, 1754 createArrow: JXG.createArrow, 1755 createRadicalAxis: JXG.createRadicalAxis, 1756 createPolarLine: JXG.createPolarLine 1757 }; 1758 }); 1759