1 /* 2 Copyright 2008-2013 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/> 29 and <http://opensource.org/licenses/MIT/>. 30 */ 31 32 33 /*global JXG: true, define: true*/ 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 /** 661 * Apply a translation by <tt>tv = (x, y)</tt> to the line. 662 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 663 * @param {Array} tv (x, y) 664 * @returns {JXG.Line} Reference to this line object. 665 */ 666 setPosition: function (method, tv) { 667 var t; 668 669 tv = new Coords(method, tv, this.board); 670 t = this.board.create('transform', tv.usrCoords.slice(1), {type: 'translate'}); 671 672 if (this.point1.transformations.length > 0 && this.point1.transformations[this.point1.transformations.length - 1].isNumericMatrix) { 673 this.point1.transformations[this.point1.transformations.length - 1].melt(t); 674 } else { 675 this.point1.addTransform(this.point1, t); 676 } 677 if (this.point2.transformations.length > 0 && this.point2.transformations[this.point2.transformations.length - 1].isNumericMatrix) { 678 this.point2.transformations[this.point2.transformations.length - 1].melt(t); 679 } else { 680 this.point2.addTransform(this.point2, t); 681 } 682 683 return this; 684 }, 685 686 /** 687 * Moves the line by the difference of two coordinates. 688 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 689 * @param {Array} coords coordinates in screen/user units 690 * @param {Array} oldcoords previous coordinates in screen/user units 691 * @returns {JXG.Line} this element 692 */ 693 setPositionDirectly: function (method, coords, oldcoords) { 694 var dc, t, 695 c = new Coords(method, coords, this.board), 696 oldc = new Coords(method, oldcoords, this.board); 697 698 if (!this.point1.draggable() || !this.point2.draggable()) { 699 return this; 700 } 701 702 dc = Statistics.subtract(c.usrCoords, oldc.usrCoords); 703 t = this.board.create('transform', dc.slice(1), {type: 'translate'}); 704 t.applyOnce([this.point1, this.point2]); 705 706 return this; 707 }, 708 709 // see GeometryElement.js 710 snapToGrid: function (pos) { 711 var c1, c2, dc, t, v, 712 x, y, sX, sY; 713 714 if (this.visProp.snaptogrid) { 715 if (this.point1.visProp.snaptogrid || this.point2.visProp.snaptogrid) { 716 this.point1.snapToGrid(); 717 this.point2.snapToGrid(); 718 } else { 719 sX = this.visProp.snapsizex; 720 sY = this.visProp.snapsizey; 721 722 c1 = new Coords(Const.COORDS_BY_SCREEN, [pos.Xprev, pos.Yprev], this.board); 723 724 x = c1.usrCoords[1]; 725 y = c1.usrCoords[2]; 726 727 if (sX <= 0 && this.board.defaultAxes && this.board.defaultAxes.x.defaultTicks) { 728 sX = this.board.defaultAxes.x.defaultTicks.ticksDelta * (this.board.defaultAxes.x.defaultTicks.visProp.minorticks + 1); 729 } 730 if (sY <= 0 && this.board.defaultAxes && this.board.defaultAxes.y.defaultTicks) { 731 sY = this.board.defaultAxes.y.defaultTicks.ticksDelta * (this.board.defaultAxes.y.defaultTicks.visProp.minorticks + 1); 732 } 733 734 // if no valid snap sizes are available, don't change the coords. 735 if (sX > 0 && sY > 0) { 736 // projectCoordsToLine 737 v = [0, this.stdform[1], this.stdform[2]]; 738 v = Mat.crossProduct(v, c1.usrCoords); 739 c2 = Geometry.meetLineLine(v, this.stdform, 0, this.board); 740 741 dc = Statistics.subtract([1, Math.round(x / sX) * sX, Math.round(y / sY) * sY], c2.usrCoords); 742 t = this.board.create('transform', dc.slice(1), {type: 'translate'}); 743 t.applyOnce([this.point1, this.point2]); 744 } 745 } 746 } 747 748 return this; 749 }, 750 751 /** 752 * Treat the line as parametric curve in homogeneous coordinates, where the parameter t runs from 0 to 1. 753 * First we transform the interval [0,1] to [-1,1]. 754 * If the line has homogeneous coordinates [c,a,b] = stdform[] then the direction of the line is [b,-a]. 755 * 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). 756 * Let the coordinates of that point be [z, x, y]. 757 * Then, the curve runs linearly from 758 * [0, b, -a] (t=-1) to [z, x, y] (t=0) 759 * and 760 * [z, x, y] (t=0) to [0, -b, a] (t=1) 761 * 762 * @param {Number} t Parameter running from 0 to 1. 763 * @returns {Number} X(t) x-coordinate of the line treated as parametric curve. 764 * */ 765 X: function (t) { 766 var x, 767 b = this.stdform[2]; 768 769 x = (Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps) ? 770 this.point1.coords.usrCoords[1] : 771 this.point2.coords.usrCoords[1]; 772 773 t = (t - 0.5) * 2; 774 775 return (1 - Math.abs(t)) * x - t * b; 776 }, 777 778 /** 779 * Treat the line as parametric curve in homogeneous coordinates. See {@link #X} for a detailed description. 780 * @param {Number} t Parameter running from 0 to 1. 781 * @returns {Number} Y(t) y-coordinate of the line treated as parametric curve. 782 */ 783 Y: function (t) { 784 var y, 785 a = this.stdform[1]; 786 787 y = (Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps) ? 788 this.point1.coords.usrCoords[2] : 789 this.point2.coords.usrCoords[2]; 790 791 t = (t - 0.5) * 2; 792 793 return (1 - Math.abs(t)) * y + t * a; 794 }, 795 796 /** 797 * Treat the line as parametric curve in homogeneous coordinates. See {@link #X} for a detailed description. 798 * @param {Number} t Parameter running from 0 to 1. 799 * @returns {Number} Z(t) z-coordinate of the line treated as parametric curve. 800 */ 801 Z: function (t) { 802 var z = (Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps) ? 803 this.point1.coords.usrCoords[0] : 804 this.point2.coords.usrCoords[0]; 805 806 t = (t - 0.5) * 2; 807 808 return (1 - Math.abs(t)) * z; 809 }, 810 811 812 /** 813 * The distance between the two points defining the line. 814 * @returns {Number} 815 */ 816 L: function () { 817 return this.point1.Dist(this.point2); 818 }, 819 820 /** 821 * Treat the element as a parametric curve 822 * @private 823 */ 824 minX: function () { 825 return 0.0; 826 }, 827 828 /** 829 * Treat the element as parametric curve 830 * @private 831 */ 832 maxX: function () { 833 return 1.0; 834 }, 835 836 // documented in geometry element 837 bounds: function () { 838 var p1c = this.point1.coords.usrCoords, 839 p2c = this.point2.coords.usrCoords; 840 841 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])]; 842 }, 843 844 /** 845 * Adds ticks to this line. Ticks can be added to any kind of line: line, arrow, and axis. 846 * @param {JXG.Ticks} ticks Reference to a ticks object which is describing the ticks (color, distance, how many, etc.). 847 * @returns {String} Id of the ticks object. 848 */ 849 addTicks: function (ticks) { 850 if (ticks.id === '' || !Type.exists(ticks.id)) { 851 ticks.id = this.id + '_ticks_' + (this.ticks.length + 1); 852 } 853 854 this.board.renderer.drawTicks(ticks); 855 this.ticks.push(ticks); 856 857 return ticks.id; 858 }, 859 860 // documented in GeometryElement.js 861 remove: function () { 862 this.removeAllTicks(); 863 GeometryElement.prototype.remove.call(this); 864 }, 865 866 /** 867 * Removes all ticks from a line. 868 */ 869 removeAllTicks: function () { 870 var i, t; 871 872 for (t = this.ticks.length; t > 0; t--) { 873 this.removeTicks(this.ticks[t - 1]); 874 } 875 876 this.ticks = []; 877 this.board.update(); 878 }, 879 880 /** 881 * Removes ticks identified by parameter named tick from this line. 882 * @param {JXG.Ticks} tick Reference to tick object to remove. 883 */ 884 removeTicks: function (tick) { 885 var t, j; 886 887 if (Type.exists(this.defaultTicks) && this.defaultTicks === tick) { 888 this.defaultTicks = null; 889 } 890 891 for (t = this.ticks.length; t > 0; t--) { 892 if (this.ticks[t - 1] === tick) { 893 this.board.removeObject(this.ticks[t - 1]); 894 895 if (this.ticks[t - 1].ticks) { 896 for (j = 0; j < this.ticks[t - 1].ticks.length; j++) { 897 if (Type.exists(this.ticks[t - 1].labels[j])) { 898 this.board.removeObject(this.ticks[t - 1].labels[j]); 899 } 900 } 901 } 902 903 delete this.ticks[t - 1]; 904 break; 905 } 906 } 907 }, 908 909 hideElement: function () { 910 var i; 911 912 GeometryElement.prototype.hideElement.call(this); 913 914 for (i = 0; i < this.ticks.length; i++) { 915 this.ticks[i].hideElement(); 916 } 917 }, 918 919 showElement: function () { 920 var i; 921 922 GeometryElement.prototype.showElement.call(this); 923 924 for (i = 0; i < this.ticks.length; i++) { 925 this.ticks[i].showElement(); 926 } 927 } 928 }); 929 930 /** 931 * @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 932 * a line can be used as an arrow and/or axis. 933 * @pseudo 934 * @description 935 * @name Line 936 * @augments JXG.Line 937 * @constructor 938 * @type JXG.Line 939 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 940 * @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 941 * numbers describing the coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 942 * It is possible to provide a function returning an array or a point, instead of providing an array or a point. 943 * @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 944 * 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. 945 * @param {function} f This function must return an array containing three numbers forming the line's homogeneous coordinates. 946 * @example 947 * // Create a line using point and coordinates/ 948 * // The second point will be fixed and invisible. 949 * var p1 = board.create('point', [4.5, 2.0]); 950 * var l1 = board.create('line', [p1, [1.0, 1.0]]); 951 * </pre><div id="c0ae3461-10c4-4d39-b9be-81d74759d122" style="width: 300px; height: 300px;"></div> 952 * <script type="text/javascript"> 953 * var glex1_board = JXG.JSXGraph.initBoard('c0ae3461-10c4-4d39-b9be-81d74759d122', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 954 * var glex1_p1 = glex1_board.create('point', [4.5, 2.0]); 955 * var glex1_l1 = glex1_board.create('line', [glex1_p1, [1.0, 1.0]]); 956 * </script><pre> 957 * @example 958 * // Create a line using three coordinates 959 * var l1 = board.create('line', [1.0, -2.0, 3.0]); 960 * </pre><div id="cf45e462-f964-4ba4-be3a-c9db94e2593f" style="width: 300px; height: 300px;"></div> 961 * <script type="text/javascript"> 962 * var glex2_board = JXG.JSXGraph.initBoard('cf45e462-f964-4ba4-be3a-c9db94e2593f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 963 * var glex2_l1 = glex2_board.create('line', [1.0, -2.0, 3.0]); 964 * </script><pre> 965 */ 966 JXG.createLine = function (board, parents, attributes) { 967 var ps, el, p1, p2, i, attr, 968 c = [], 969 constrained = false, 970 isDraggable; 971 972 /** 973 * The line is defined by two points or coordinates of two points. 974 * In the latter case, the points are created. 975 */ 976 if (parents.length === 2) { 977 // point 1 given by coordinates 978 if (Type.isArray(parents[0]) && parents[0].length > 1) { 979 attr = Type.copyAttributes(attributes, board.options, 'line', 'point1'); 980 p1 = board.create('point', parents[0], attr); 981 } else if (Type.isString(parents[0]) || parents[0].elementClass === Const.OBJECT_CLASS_POINT) { 982 p1 = board.select(parents[0]); 983 } else if ((typeof parents[0] === 'function') && (parents[0]().elementClass === Const.OBJECT_CLASS_POINT)) { 984 p1 = parents[0](); 985 constrained = true; 986 } else if ((typeof parents[0] === 'function') && (parents[0]().length && parents[0]().length === 2)) { 987 attr = Type.copyAttributes(attributes, board.options, 'line', 'point1'); 988 p1 = Point.createPoint(board, parents[0](), attr); 989 constrained = true; 990 } else { 991 throw new Error("JSXGraph: Can't create line with parent types '" + 992 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 993 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"); 994 } 995 996 // point 2 given by coordinates 997 if (Type.isArray(parents[1]) && parents[1].length > 1) { 998 attr = Type.copyAttributes(attributes, board.options, 'line', 'point2'); 999 p2 = board.create('point', parents[1], attr); 1000 } else if (Type.isString(parents[1]) || parents[1].elementClass === Const.OBJECT_CLASS_POINT) { 1001 p2 = board.select(parents[1]); 1002 } else if ((typeof parents[1] === 'function') && (parents[1]().elementClass === Const.OBJECT_CLASS_POINT)) { 1003 p2 = parents[1](); 1004 constrained = true; 1005 } else if ((typeof parents[1] === 'function') && (parents[1]().length && parents[1]().length === 2)) { 1006 attr = Type.copyAttributes(attributes, board.options, 'line', 'point2'); 1007 p2 = Point.createPoint(board, parents[1](), attr); 1008 constrained = true; 1009 } else { 1010 throw new Error("JSXGraph: Can't create line with parent types '" + 1011 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1012 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"); 1013 } 1014 1015 attr = Type.copyAttributes(attributes, board.options, 'line'); 1016 1017 el = new JXG.Line(board, p1, p2, attr); 1018 if (constrained) { 1019 el.constrained = true; 1020 el.funp1 = parents[0]; 1021 el.funp2 = parents[1]; 1022 } else { 1023 el.isDraggable = true; 1024 } 1025 1026 if (!el.constrained) { 1027 el.parents = [p1.id, p2.id]; 1028 } 1029 // Line is defined by three homogeneous coordinates. 1030 // Also in this case points are created. 1031 } else if (parents.length === 3) { 1032 // free line 1033 isDraggable = true; 1034 for (i = 0; i < 3; i++) { 1035 if (typeof parents[i] === 'number') { 1036 // createFunction will just wrap a function around our constant number 1037 // that does nothing else but to return that number. 1038 c[i] = Type.createFunction(parents[i]); 1039 } else if (typeof parents[i] === 'function') { 1040 c[i] = parents[i]; 1041 isDraggable = false; 1042 } else { 1043 throw new Error("JSXGraph: Can't create line with parent types '" + 1044 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." + 1045 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"); 1046 } 1047 } 1048 1049 // point 1 is the midpoint between (0,c,-b) and point 2. => point1 is finite. 1050 attr = Type.copyAttributes(attributes, board.options, 'line', 'point1'); 1051 if (isDraggable) { 1052 p1 = board.create('point', [ 1053 c[2]() * c[2]() + c[1]() * c[1](), 1054 c[2]() - c[1]() * c[0]() + c[2](), 1055 -c[1]() - c[2]() * c[0]() - c[1]() 1056 ], attr); 1057 } else { 1058 p1 = board.create('point', [ 1059 function () { 1060 return (c[2]() * c[2]() + c[1]() * c[1]()) * 0.5; 1061 }, 1062 function () { 1063 return (c[2]() - c[1]() * c[0]() + c[2]()) * 0.5; 1064 }, 1065 function () { 1066 return (-c[1]() - c[2]() * c[0]() - c[1]()) * 0.5; 1067 }], attr); 1068 } 1069 1070 // point 2: (b^2+c^2,-ba+c,-ca-b) 1071 attr = Type.copyAttributes(attributes, board.options, 'line', 'point2'); 1072 if (isDraggable) { 1073 p2 = board.create('point', [ 1074 c[2]() * c[2]() + c[1]() * c[1](), 1075 -c[1]() * c[0]() + c[2](), 1076 -c[2]() * c[0]() - c[1]() 1077 ], attr); 1078 } else { 1079 p2 = board.create('point', [ 1080 function () { 1081 return c[2]() * c[2]() + c[1]() * c[1](); 1082 }, 1083 function () { 1084 return -c[1]() * c[0]() + c[2](); 1085 }, 1086 function () { 1087 return -c[2]() * c[0]() - c[1](); 1088 }], attr); 1089 } 1090 1091 // If the line will have a glider and board.suspendUpdate() has been called, we 1092 // need to compute the initial position of the two points p1 and p2. 1093 p1.prepareUpdate().update(); 1094 p2.prepareUpdate().update(); 1095 attr = Type.copyAttributes(attributes, board.options, 'line'); 1096 el = new JXG.Line(board, p1, p2, attr); 1097 // Not yet working, because the points are not draggable. 1098 el.isDraggable = isDraggable; 1099 1100 if (isDraggable) { 1101 el.parents = [c[0](), c[1](), c[2]()]; 1102 } 1103 // The parent array contains a function which returns two points. 1104 } else if ((parents.length === 1) && (typeof parents[0] === 'function') && (parents[0]().length === 2) && 1105 (parents[0]()[0].elementClass === Const.OBJECT_CLASS_POINT) && 1106 (parents[0]()[1].elementClass === Const.OBJECT_CLASS_POINT)) { 1107 ps = parents[0](); 1108 attr = Type.copyAttributes(attributes, board.options, 'line'); 1109 el = new JXG.Line(board, ps[0], ps[1], attr); 1110 el.constrained = true; 1111 el.funps = parents[0]; 1112 } else if ((parents.length === 1) && (typeof parents[0] === 'function') && (parents[0]().length === 3) && 1113 (typeof parents[0]()[0] === 'number') && 1114 (typeof parents[0]()[1] === 'number') && 1115 (typeof parents[0]()[2] === 'number')) { 1116 ps = parents[0]; 1117 1118 attr = Type.copyAttributes(attributes, board.options, 'line', 'point1'); 1119 p1 = board.create('point', [ 1120 function () { 1121 var c = ps(); 1122 1123 return [ 1124 (c[2] * c[2] + c[1] * c[1]) * 0.5, 1125 (c[2] - c[1] * c[0] + c[2]) * 0.5, 1126 (-c[1] - c[2] * c[0] - c[1]) * 0.5 1127 ]; 1128 }], attr); 1129 1130 attr = Type.copyAttributes(attributes, board.options, 'line', 'point2'); 1131 p2 = board.create('point', [ 1132 function () { 1133 var c = ps(); 1134 1135 return [ 1136 c[2] * c[2] + c[1] * c[1], 1137 -c[1] * c[0] + c[2], 1138 -c[2] * c[0] - c[1] 1139 ]; 1140 }], attr); 1141 1142 attr = Type.copyAttributes(attributes, board.options, 'line'); 1143 el = new JXG.Line(board, p1, p2, attr); 1144 1145 el.constrained = true; 1146 el.funps = parents[0]; 1147 } else { 1148 throw new Error("JSXGraph: Can't create line with parent types '" + 1149 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1150 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"); 1151 } 1152 1153 return el; 1154 }; 1155 1156 JXG.registerElement('line', JXG.createLine); 1157 1158 /** 1159 * @class This element is used to provide a constructor for a segment. 1160 * It's strictly spoken just a wrapper for element {@link Line} with {@link JXG.Line#straightFirst} 1161 * and {@link JXG.Line#straightLast} properties set to false. If there is a third variable then the 1162 * segment has a fixed length (which may be a function, too). 1163 * @pseudo 1164 * @description 1165 * @name Segment 1166 * @augments JXG.Line 1167 * @constructor 1168 * @type JXG.Line 1169 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1170 * @param {JXG.Point,array_JXG.Point,array} point1, point2 Parent elements can be two elements either of type {@link JXG.Point} 1171 * or array of numbers describing the 1172 * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 1173 * @param {number,function} length (optional) The points are adapted - if possible - such that their distance 1174 * has a this value. 1175 * @see Line 1176 * @example 1177 * // Create a segment providing two points. 1178 * var p1 = board.create('point', [4.5, 2.0]); 1179 * var p2 = board.create('point', [1.0, 1.0]); 1180 * var l1 = board.create('segment', [p1, p2]); 1181 * </pre><div id="d70e6aac-7c93-4525-a94c-a1820fa38e2f" style="width: 300px; height: 300px;"></div> 1182 * <script type="text/javascript"> 1183 * var slex1_board = JXG.JSXGraph.initBoard('d70e6aac-7c93-4525-a94c-a1820fa38e2f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1184 * var slex1_p1 = slex1_board.create('point', [4.5, 2.0]); 1185 * var slex1_p2 = slex1_board.create('point', [1.0, 1.0]); 1186 * var slex1_l1 = slex1_board.create('segment', [slex1_p1, slex1_p2]); 1187 * </script><pre> 1188 * 1189 * @example 1190 * // Create a segment providing two points. 1191 * var p1 = board.create('point', [4.0, 1.0]); 1192 * var p2 = board.create('point', [1.0, 1.0]); 1193 * var l1 = board.create('segment', [p1, p2]); 1194 * var p3 = board.create('point', [4.0, 2.0]); 1195 * var p4 = board.create('point', [1.0, 2.0]); 1196 * var l2 = board.create('segment', [p3, p4, 3]); 1197 * var p5 = board.create('point', [4.0, 3.0]); 1198 * var p6 = board.create('point', [1.0, 4.0]); 1199 * var l3 = board.create('segment', [p5, p6, function(){ return l1.L();} ]); 1200 * </pre><div id="617336ba-0705-4b2b-a236-c87c28ef25be" style="width: 300px; height: 300px;"></div> 1201 * <script type="text/javascript"> 1202 * var slex2_board = JXG.JSXGraph.initBoard('617336ba-0705-4b2b-a236-c87c28ef25be', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1203 * var slex2_p1 = slex1_board.create('point', [4.0, 1.0]); 1204 * var slex2_p2 = slex1_board.create('point', [1.0, 1.0]); 1205 * var slex2_l1 = slex1_board.create('segment', [slex1_p1, slex1_p2]); 1206 * var slex2_p3 = slex1_board.create('point', [4.0, 2.0]); 1207 * var slex2_p4 = slex1_board.create('point', [1.0, 2.0]); 1208 * var slex2_l2 = slex1_board.create('segment', [slex1_p3, slex1_p4, 3]); 1209 * var slex2_p5 = slex1_board.create('point', [4.0, 2.0]); 1210 * var slex2_p6 = slex1_board.create('point', [1.0, 2.0]); 1211 * var slex2_l3 = slex1_board.create('segment', [slex1_p5, slex1_p6, function(){ return slex2_l1.L();}]); 1212 * </script><pre> 1213 * 1214 */ 1215 JXG.createSegment = function (board, parents, attributes) { 1216 var el, i, attr; 1217 1218 attributes.straightFirst = false; 1219 attributes.straightLast = false; 1220 attr = Type.copyAttributes(attributes, board.options, 'segment'); 1221 1222 el = board.create('line', parents.slice(0, 2), attr); 1223 1224 if (parents.length === 3) { 1225 el.hasFixedLength = true; 1226 1227 if (Type.isNumber(parents[2])) { 1228 el.fixedLength = function () { 1229 return parents[2]; 1230 }; 1231 } else if (Type.isFunction(parents[2])) { 1232 el.fixedLength = parents[2]; 1233 } else { 1234 throw new Error("JSXGraph: Can't create segment with third parent type '" + 1235 (typeof parents[2]) + "'." + 1236 "\nPossible third parent types: number or function"); 1237 } 1238 1239 el.fixedLengthOldCoords = []; 1240 el.fixedLengthOldCoords[0] = new Coords(Const.COORDS_BY_USER, el.point1.coords.usrCoords.slice(1, 3), board); 1241 el.fixedLengthOldCoords[1] = new Coords(Const.COORDS_BY_USER, el.point2.coords.usrCoords.slice(1, 3), board); 1242 } 1243 1244 el.elType = 'segment'; 1245 1246 return el; 1247 }; 1248 1249 JXG.registerElement('segment', JXG.createSegment); 1250 1251 /** 1252 * @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} 1253 * and {@link JXG.Line#straightLast} properties set to false and {@link JXG.Line#lastArrow} set to true. 1254 * @pseudo 1255 * @description 1256 * @name Arrow 1257 * @augments JXG.Line 1258 * @constructor 1259 * @type JXG.Line 1260 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1261 * @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 1262 * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 1263 * @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 1264 * of the equation <tt>a*x+b*y+c*z = 0</tt>. 1265 * @see Line 1266 * @example 1267 * // Create an arrow providing two points. 1268 * var p1 = board.create('point', [4.5, 2.0]); 1269 * var p2 = board.create('point', [1.0, 1.0]); 1270 * var l1 = board.create('arrow', [p1, p2]); 1271 * </pre><div id="1d26bd22-7d6d-4018-b164-4c8bc8d22ccf" style="width: 300px; height: 300px;"></div> 1272 * <script type="text/javascript"> 1273 * var alex1_board = JXG.JSXGraph.initBoard('1d26bd22-7d6d-4018-b164-4c8bc8d22ccf', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1274 * var alex1_p1 = alex1_board.create('point', [4.5, 2.0]); 1275 * var alex1_p2 = alex1_board.create('point', [1.0, 1.0]); 1276 * var alex1_l1 = alex1_board.create('arrow', [alex1_p1, alex1_p2]); 1277 * </script><pre> 1278 */ 1279 JXG.createArrow = function (board, parents, attributes) { 1280 var el; 1281 1282 attributes.firstArrow = false; 1283 attributes.lastArrow = true; 1284 el = board.create('line', parents, attributes).setStraight(false, false); 1285 //el.setArrow(false, true); 1286 el.type = Const.OBJECT_TYPE_VECTOR; 1287 el.elType = 'arrow'; 1288 1289 return el; 1290 }; 1291 1292 JXG.registerElement('arrow', JXG.createArrow); 1293 1294 /** 1295 * @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} 1296 * 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. 1297 * @pseudo 1298 * @description 1299 * @name Axis 1300 * @augments JXG.Line 1301 * @constructor 1302 * @type JXG.Line 1303 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1304 * @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 1305 * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 1306 * @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 1307 * of the equation <tt>a*x+b*y+c*z = 0</tt>. 1308 * @example 1309 * // Create an axis providing two coord pairs. 1310 * var l1 = board.create('axis', [[0.0, 1.0], [1.0, 1.3]]); 1311 * </pre><div id="4f414733-624c-42e4-855c-11f5530383ae" style="width: 300px; height: 300px;"></div> 1312 * <script type="text/javascript"> 1313 * var axex1_board = JXG.JSXGraph.initBoard('4f414733-624c-42e4-855c-11f5530383ae', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1314 * var axex1_l1 = axex1_board.create('axis', [[0.0, 1.0], [1.0, 1.3]]); 1315 * </script><pre> 1316 */ 1317 JXG.createAxis = function (board, parents, attributes) { 1318 var attr, el, els, dist; 1319 1320 // Arrays oder Punkte, mehr brauchen wir nicht. 1321 if ((Type.isArray(parents[0]) || Type.isPoint(parents[0])) && (Type.isArray(parents[1]) || Type.isPoint(parents[1]))) { 1322 attr = Type.copyAttributes(attributes, board.options, 'axis'); 1323 el = board.create('line', parents, attr); 1324 el.type = Const.OBJECT_TYPE_AXIS; 1325 el.isDraggable = false; 1326 el.point1.isDraggable = false; 1327 el.point2.isDraggable = false; 1328 1329 for (els in el.ancestors) { 1330 if (el.ancestors.hasOwnProperty(els)) { 1331 el.ancestors[els].type = Const.OBJECT_TYPE_AXISPOINT; 1332 } 1333 } 1334 1335 attr = Type.copyAttributes(attributes, board.options, 'axis', 'ticks'); 1336 if (Type.exists(attr.ticksdistance)) { 1337 dist = attr.ticksdistance; 1338 } else if (Type.isArray(attr.ticks)) { 1339 dist = attr.ticks; 1340 } else { 1341 dist = 1.0; 1342 } 1343 1344 /** 1345 * The ticks attached to the axis. 1346 * @memberOf Axis.prototype 1347 * @name defaultTicks 1348 * @type JXG.Ticks 1349 */ 1350 el.defaultTicks = board.create('ticks', [el, dist], attr); 1351 1352 el.defaultTicks.dump = false; 1353 1354 el.elType = 'axis'; 1355 el.subs = { 1356 ticks: el.defaultTicks 1357 }; 1358 } else { 1359 throw new Error("JSXGraph: Can't create axis with parent types '" + 1360 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1361 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]"); 1362 } 1363 1364 return el; 1365 }; 1366 1367 JXG.registerElement('axis', JXG.createAxis); 1368 1369 /** 1370 * @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 1371 * by a glider on a line, circle, or curve and describes the tangent in the glider point on that line, circle, or curve. 1372 * @pseudo 1373 * @description 1374 * @name Tangent 1375 * @augments JXG.Line 1376 * @constructor 1377 * @type JXG.Line 1378 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1379 * @param {Glider} g A glider on a line, circle, or curve. 1380 * @example 1381 * // Create a tangent providing a glider on a function graph 1382 * var c1 = board.create('curve', [function(t){return t},function(t){return t*t*t;}]); 1383 * var g1 = board.create('glider', [0.6, 1.2, c1]); 1384 * var t1 = board.create('tangent', [g1]); 1385 * </pre><div id="7b7233a0-f363-47dd-9df5-4018d0d17a98" style="width: 400px; height: 400px;"></div> 1386 * <script type="text/javascript"> 1387 * var tlex1_board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-4018d0d17a98', {boundingbox: [-6, 6, 6, -6], axis: true, showcopyright: false, shownavigation: false}); 1388 * var tlex1_c1 = tlex1_board.create('curve', [function(t){return t},function(t){return t*t*t;}]); 1389 * var tlex1_g1 = tlex1_board.create('glider', [0.6, 1.2, tlex1_c1]); 1390 * var tlex1_t1 = tlex1_board.create('tangent', [tlex1_g1]); 1391 * </script><pre> 1392 */ 1393 JXG.createTangent = function (board, parents, attributes) { 1394 var p, c, g, f, i, j, el, tangent; 1395 1396 // One arguments: glider on line, circle or curve 1397 if (parents.length === 1) { 1398 p = parents[0]; 1399 c = p.slideObject; 1400 // Two arguments: (point,F"|conic) or (line|curve|circle|conic,point). // Not yet: curve! 1401 } else if (parents.length === 2) { 1402 // In fact, for circles and conics it is the polar 1403 if (Type.isPoint(parents[0])) { 1404 p = parents[0]; 1405 c = parents[1]; 1406 } else if (Type.isPoint(parents[1])) { 1407 c = parents[0]; 1408 p = parents[1]; 1409 } else { 1410 throw new Error("JSXGraph: Can't create tangent with parent types '" + 1411 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1412 "\nPossible parent types: [glider], [point,line|curve|circle|conic]"); 1413 } 1414 } else { 1415 throw new Error("JSXGraph: Can't create tangent with parent types '" + 1416 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1417 "\nPossible parent types: [glider], [point,line|curve|circle|conic]"); 1418 } 1419 1420 if (c.elementClass === Const.OBJECT_CLASS_LINE) { 1421 tangent = board.create('line', [c.point1, c.point2], attributes); 1422 tangent.glider = p; 1423 } else if (c.elementClass === Const.OBJECT_CLASS_CURVE && c.type !== Const.OBJECT_TYPE_CONIC) { 1424 if (c.visProp.curvetype !== 'plot') { 1425 g = c.X; 1426 f = c.Y; 1427 tangent = board.create('line', [ 1428 function () { 1429 return -p.X() * Numerics.D(f)(p.position) + p.Y() * Numerics.D(g)(p.position); 1430 }, 1431 function () { 1432 return Numerics.D(f)(p.position); 1433 }, 1434 function () { 1435 return -Numerics.D(g)(p.position); 1436 } 1437 ], attributes); 1438 p.addChild(tangent); 1439 1440 // this is required for the geogebra reader to display a slope 1441 tangent.glider = p; 1442 } else { // curveType 'plot' 1443 // equation of the line segment: 0 = y*(x1-x2) + x*(y2-y1) + y1*x2-x1*y2 1444 tangent = board.create('line', [ 1445 function () { 1446 var i = Math.floor(p.position); 1447 1448 if (i === c.numberPoints - 1) { 1449 i--; 1450 } 1451 1452 if (i < 0) { 1453 return 1; 1454 } 1455 1456 return c.Y(i) * c.X(i + 1) - c.X(i) * c.Y(i + 1); 1457 }, 1458 function () { 1459 var i = Math.floor(p.position); 1460 1461 if (i === c.numberPoints - 1) { 1462 i--; 1463 } 1464 1465 if (i < 0) { 1466 return 0; 1467 } 1468 1469 return c.Y(i + 1) - c.Y(i); 1470 }, 1471 function () { 1472 var i = Math.floor(p.position); 1473 1474 if (i === c.numberPoints - 1) { 1475 i--; 1476 } 1477 1478 if (i < 0) { 1479 return 0.0; 1480 } 1481 1482 return c.X(i) - c.X(i + 1); 1483 }], attributes); 1484 1485 p.addChild(tangent); 1486 1487 // this is required for the geogebra reader to display a slope 1488 tangent.glider = p; 1489 } 1490 } else if (c.type === Const.OBJECT_TYPE_TURTLE) { 1491 tangent = board.create('line', [ 1492 function () { 1493 var i = Math.floor(p.position); 1494 1495 // run through all curves of this turtle 1496 for (j = 0; j < c.objects.length; j++) { 1497 el = c.objects[j]; 1498 1499 if (el.type === Const.OBJECT_TYPE_CURVE) { 1500 if (i < el.numberPoints) { 1501 break; 1502 } 1503 1504 i -= el.numberPoints; 1505 } 1506 } 1507 1508 if (i === el.numberPoints - 1) { 1509 i--; 1510 } 1511 1512 if (i < 0) { 1513 return 1; 1514 } 1515 1516 return el.Y(i) * el.X(i + 1) - el.X(i) * el.Y(i + 1); 1517 }, 1518 function () { 1519 var i = Math.floor(p.position); 1520 1521 // run through all curves of this turtle 1522 for (j = 0; j < c.objects.length; j++) { 1523 el = c.objects[j]; 1524 1525 if (el.type === Const.OBJECT_TYPE_CURVE) { 1526 if (i < el.numberPoints) { 1527 break; 1528 } 1529 1530 i -= el.numberPoints; 1531 } 1532 } 1533 1534 if (i === el.numberPoints - 1) { 1535 i--; 1536 } 1537 if (i < 0) { 1538 return 0; 1539 } 1540 1541 return el.Y(i + 1) - el.Y(i); 1542 }, 1543 function () { 1544 var i = Math.floor(p.position); 1545 1546 // run through all curves of this turtle 1547 for (j = 0; j < c.objects.length; j++) { 1548 el = c.objects[j]; 1549 if (el.type === Const.OBJECT_TYPE_CURVE) { 1550 if (i < el.numberPoints) { 1551 break; 1552 } 1553 i -= el.numberPoints; 1554 } 1555 } 1556 if (i === el.numberPoints - 1) { 1557 i--; 1558 } 1559 1560 if (i < 0) { 1561 return 0; 1562 } 1563 1564 return el.X(i) - el.X(i + 1); 1565 }], attributes); 1566 p.addChild(tangent); 1567 1568 // this is required for the geogebra reader to display a slope 1569 tangent.glider = p; 1570 } else if (c.elementClass === Const.OBJECT_CLASS_CIRCLE || c.type === Const.OBJECT_TYPE_CONIC) { 1571 // If p is not on c, the tangent is the polar. 1572 // This construction should work on conics, too. p has to lie on c. 1573 tangent = board.create('line', [ 1574 function () { 1575 return Mat.matVecMult(c.quadraticform, p.coords.usrCoords)[0]; 1576 }, 1577 function () { 1578 return Mat.matVecMult(c.quadraticform, p.coords.usrCoords)[1]; 1579 }, 1580 function () { 1581 return Mat.matVecMult(c.quadraticform, p.coords.usrCoords)[2]; 1582 }], attributes); 1583 1584 p.addChild(tangent); 1585 // this is required for the geogebra reader to display a slope 1586 tangent.glider = p; 1587 } 1588 1589 if (!Type.exists(tangent)) { 1590 throw new Error('JSXGraph: Couldn\'t create tangent with the given parents.'); 1591 } 1592 1593 tangent.elType = 'tangent'; 1594 tangent.type = Const.OBJECT_TYPE_TANGENT; 1595 tangent.parents = []; 1596 for (i = 0; i < parents.length; i++) { 1597 tangent.parents.push(parents[i].id); 1598 } 1599 1600 return tangent; 1601 }; 1602 1603 /** 1604 * Register the element type tangent at JSXGraph 1605 * @private 1606 */ 1607 JXG.registerElement('tangent', JXG.createTangent); 1608 JXG.registerElement('polar', JXG.createTangent); 1609 1610 return { 1611 Line: JXG.Line, 1612 createLine: JXG.createLine, 1613 createTangent: JXG.createTangent, 1614 createPolar: JXG.createTangent, 1615 createSegment: JXG.createSegment, 1616 createAxis: JXG.createAxis, 1617 createArrow: JXG.createArrow 1618 }; 1619 }); 1620