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