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