1 /* 2 Copyright 2008-2023 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 <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true*/ 33 /*jslint nomen: true, plusplus: true*/ 34 35 /** 36 * @fileoverview The geometry object Line is defined in this file. Line stores all 37 * style and functional properties that are required to draw and move a line on 38 * a board. 39 */ 40 41 import JXG from "../jxg"; 42 import Mat from "../math/math"; 43 import Geometry from "../math/geometry"; 44 import Numerics from "../math/numerics"; 45 import Statistics from "../math/statistics"; 46 import Const from "./constants"; 47 import Coords from "./coords"; 48 import GeometryElement from "./element"; 49 import Type from "../utils/type"; 50 51 /** 52 * 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 53 * be intersected with some other geometry elements. 54 * @class Creates a new basic line object. Do not use this constructor to create a line. 55 * Use {@link JXG.Board#create} with 56 * type {@link Line}, {@link Arrow}, or {@link Axis} instead. 57 * @constructor 58 * @augments JXG.GeometryElement 59 * @param {String|JXG.Board} board The board the new line is drawn on. 60 * @param {Point} p1 Startpoint of the line. 61 * @param {Point} p2 Endpoint of the line. 62 * @param {Object} attributes Javascript object containing attributes like name, id and colors. 63 */ 64 JXG.Line = function (board, p1, p2, attributes) { 65 this.constructor(board, attributes, Const.OBJECT_TYPE_LINE, Const.OBJECT_CLASS_LINE); 66 67 /** 68 * Startpoint of the line. You really should not set this field directly as it may break JSXGraph's 69 * update system so your construction won't be updated properly. 70 * @type JXG.Point 71 */ 72 this.point1 = this.board.select(p1); 73 74 /** 75 * Endpoint of the line. Just like {@link JXG.Line.point1} you shouldn't write this field directly. 76 * @type JXG.Point 77 */ 78 this.point2 = this.board.select(p2); 79 80 /** 81 * Array of ticks storing all the ticks on this line. Do not set this field directly and use 82 * {@link JXG.Line#addTicks} and {@link JXG.Line#removeTicks} to add and remove ticks to and from the line. 83 * @type Array 84 * @see JXG.Ticks 85 */ 86 this.ticks = []; 87 88 /** 89 * Reference of the ticks created automatically when constructing an axis. 90 * @type JXG.Ticks 91 * @see JXG.Ticks 92 */ 93 this.defaultTicks = null; 94 95 /** 96 * If the line is the border of a polygon, the polygon object is stored, otherwise null. 97 * @type JXG.Polygon 98 * @default null 99 * @private 100 */ 101 this.parentPolygon = null; 102 103 /* Register line at board */ 104 this.id = this.board.setId(this, "L"); 105 this.board.renderer.drawLine(this); 106 this.board.finalizeAdding(this); 107 108 this.elType = "line"; 109 110 /* Add line as child to defining points */ 111 if (this.point1._is_new) { 112 this.addChild(this.point1); 113 delete this.point1._is_new; 114 } else { 115 this.point1.addChild(this); 116 } 117 if (this.point2._is_new) { 118 this.addChild(this.point2); 119 delete this.point2._is_new; 120 } else { 121 this.point2.addChild(this); 122 } 123 124 this.inherits.push(this.point1, this.point2); 125 126 this.updateStdform(); // This is needed in the following situation: 127 // * the line is defined by three coordinates 128 // * and it will have a glider 129 // * and board.suspendUpdate() has been called. 130 131 // create Label 132 this.createLabel(); 133 134 this.methodMap = JXG.deepCopy(this.methodMap, { 135 point1: "point1", 136 point2: "point2", 137 getSlope: "Slope", 138 Slope: "Slope", 139 Direction: "Direction", 140 getRise: "getRise", 141 Rise: "getRise", 142 getYIntersect: "getRise", 143 YIntersect: "getRise", 144 getAngle: "getAngle", 145 Angle: "getAngle", 146 L: "L", 147 length: "L", 148 setFixedLength: "setFixedLength", 149 setStraight: "setStraight" 150 }); 151 }; 152 153 JXG.Line.prototype = new GeometryElement(); 154 155 JXG.extend( 156 JXG.Line.prototype, 157 /** @lends JXG.Line.prototype */ { 158 /** 159 * Checks whether (x,y) is near the line. 160 * @param {Number} x Coordinate in x direction, screen coordinates. 161 * @param {Number} y Coordinate in y direction, screen coordinates. 162 * @returns {Boolean} True if (x,y) is near the line, False otherwise. 163 */ 164 hasPoint: function (x, y) { 165 // Compute the stdform of the line in screen coordinates. 166 var c = [], 167 v = [1, x, y], 168 s, vnew, p1c, p2c, d, pos, i, prec, type, 169 sw = Type.evaluate(this.visProp.strokewidth); 170 171 if (Type.isObject(Type.evaluate(this.visProp.precision))) { 172 type = this.board._inputDevice; 173 prec = Type.evaluate(this.visProp.precision[type]); 174 } else { 175 // 'inherit' 176 prec = this.board.options.precision.hasPoint; 177 } 178 prec += sw * 0.5; 179 180 c[0] = 181 this.stdform[0] - 182 (this.stdform[1] * this.board.origin.scrCoords[1]) / this.board.unitX + 183 (this.stdform[2] * this.board.origin.scrCoords[2]) / this.board.unitY; 184 c[1] = this.stdform[1] / this.board.unitX; 185 c[2] = this.stdform[2] / -this.board.unitY; 186 187 s = Geometry.distPointLine(v, c); 188 if (isNaN(s) || s > prec) { 189 return false; 190 } 191 192 if ( 193 Type.evaluate(this.visProp.straightfirst) && 194 Type.evaluate(this.visProp.straightlast) 195 ) { 196 return true; 197 } 198 199 // If the line is a ray or segment we have to check if the projected point is between P1 and P2. 200 p1c = this.point1.coords; 201 p2c = this.point2.coords; 202 203 // Project the point orthogonally onto the line 204 vnew = [0, c[1], c[2]]; 205 // Orthogonal line to c through v 206 vnew = Mat.crossProduct(vnew, v); 207 // Intersect orthogonal line with line 208 vnew = Mat.crossProduct(vnew, c); 209 210 // Normalize the projected point 211 vnew[1] /= vnew[0]; 212 vnew[2] /= vnew[0]; 213 vnew[0] = 1; 214 215 vnew = new Coords(Const.COORDS_BY_SCREEN, vnew.slice(1), this.board).usrCoords; 216 d = p1c.distance(Const.COORDS_BY_USER, p2c); 217 p1c = p1c.usrCoords.slice(0); 218 p2c = p2c.usrCoords.slice(0); 219 220 // The defining points are identical 221 if (d < Mat.eps) { 222 pos = 0; 223 } else { 224 /* 225 * Handle the cases, where one of the defining points is an ideal point. 226 * d is set to something close to infinity, namely 1/eps. 227 * The ideal point is (temporarily) replaced by a finite point which has 228 * distance d from the other point. 229 * This is accomplished by extracting the x- and y-coordinates (x,y)=:v of the ideal point. 230 * v determines the direction of the line. v is normalized, i.e. set to length 1 by dividing through its length. 231 * Finally, the new point is the sum of the other point and v*d. 232 * 233 */ 234 235 // At least one point is an ideal point 236 if (d === Number.POSITIVE_INFINITY) { 237 d = 1 / Mat.eps; 238 239 // The second point is an ideal point 240 if (Math.abs(p2c[0]) < Mat.eps) { 241 d /= Geometry.distance([0, 0, 0], p2c); 242 p2c = [1, p1c[1] + p2c[1] * d, p1c[2] + p2c[2] * d]; 243 // The first point is an ideal point 244 } else { 245 d /= Geometry.distance([0, 0, 0], p1c); 246 p1c = [1, p2c[1] + p1c[1] * d, p2c[2] + p1c[2] * d]; 247 } 248 } 249 i = 1; 250 d = p2c[i] - p1c[i]; 251 252 if (Math.abs(d) < Mat.eps) { 253 i = 2; 254 d = p2c[i] - p1c[i]; 255 } 256 pos = (vnew[i] - p1c[i]) / d; 257 } 258 259 if (!Type.evaluate(this.visProp.straightfirst) && pos < 0) { 260 return false; 261 } 262 263 return !(!Type.evaluate(this.visProp.straightlast) && pos > 1); 264 }, 265 266 // documented in base/element 267 update: function () { 268 var funps; 269 270 if (!this.needsUpdate) { 271 return this; 272 } 273 274 if (this.constrained) { 275 if (Type.isFunction(this.funps)) { 276 funps = this.funps(); 277 if (funps && funps.length && funps.length === 2) { 278 this.point1 = funps[0]; 279 this.point2 = funps[1]; 280 } 281 } else { 282 if (Type.isFunction(this.funp1)) { 283 funps = this.funp1(); 284 if (Type.isPoint(funps)) { 285 this.point1 = funps; 286 } else if (funps && funps.length && funps.length === 2) { 287 this.point1.setPositionDirectly(Const.COORDS_BY_USER, funps); 288 } 289 } 290 291 if (Type.isFunction(this.funp2)) { 292 funps = this.funp2(); 293 if (Type.isPoint(funps)) { 294 this.point2 = funps; 295 } else if (funps && funps.length && funps.length === 2) { 296 this.point2.setPositionDirectly(Const.COORDS_BY_USER, funps); 297 } 298 } 299 } 300 } 301 302 this.updateSegmentFixedLength(); 303 this.updateStdform(); 304 305 if (Type.evaluate(this.visProp.trace)) { 306 this.cloneToBackground(true); 307 } 308 309 return this; 310 }, 311 312 /** 313 * Update segments with fixed length and at least one movable point. 314 * @private 315 */ 316 updateSegmentFixedLength: function () { 317 var d, d_new, d1, d2, drag1, drag2, x, y; 318 319 if (!this.hasFixedLength) { 320 return this; 321 } 322 323 // Compute the actual length of the segment 324 d = this.point1.Dist(this.point2); 325 // Determine the length the segment ought to have 326 d_new = Math.abs(this.fixedLength()); 327 328 // Distances between the two points and their respective 329 // position before the update 330 d1 = this.fixedLengthOldCoords[0].distance( 331 Const.COORDS_BY_USER, 332 this.point1.coords 333 ); 334 d2 = this.fixedLengthOldCoords[1].distance( 335 Const.COORDS_BY_USER, 336 this.point2.coords 337 ); 338 339 // If the position of the points or the fixed length function has been changed we have to work. 340 if (d1 > Mat.eps || d2 > Mat.eps || d !== d_new) { 341 drag1 = 342 this.point1.isDraggable && 343 this.point1.type !== Const.OBJECT_TYPE_GLIDER && 344 !Type.evaluate(this.point1.visProp.fixed); 345 drag2 = 346 this.point2.isDraggable && 347 this.point2.type !== Const.OBJECT_TYPE_GLIDER && 348 !Type.evaluate(this.point2.visProp.fixed); 349 350 // First case: the two points are different 351 // Then we try to adapt the point that was not dragged 352 // If this point can not be moved (e.g. because it is a glider) 353 // we try move the other point 354 if (d > Mat.eps) { 355 if ((d1 > d2 && drag2) || (d1 <= d2 && drag2 && !drag1)) { 356 this.point2.setPositionDirectly(Const.COORDS_BY_USER, [ 357 this.point1.X() + ((this.point2.X() - this.point1.X()) * d_new) / d, 358 this.point1.Y() + ((this.point2.Y() - this.point1.Y()) * d_new) / d 359 ]); 360 this.point2.fullUpdate(); 361 } else if ((d1 <= d2 && drag1) || (d1 > d2 && drag1 && !drag2)) { 362 this.point1.setPositionDirectly(Const.COORDS_BY_USER, [ 363 this.point2.X() + ((this.point1.X() - this.point2.X()) * d_new) / d, 364 this.point2.Y() + ((this.point1.Y() - this.point2.Y()) * d_new) / d 365 ]); 366 this.point1.fullUpdate(); 367 } 368 // Second case: the two points are identical. In this situation 369 // we choose a random direction. 370 } else { 371 x = Math.random() - 0.5; 372 y = Math.random() - 0.5; 373 d = Mat.hypot(x, y); 374 375 if (drag2) { 376 this.point2.setPositionDirectly(Const.COORDS_BY_USER, [ 377 this.point1.X() + (x * d_new) / d, 378 this.point1.Y() + (y * d_new) / d 379 ]); 380 this.point2.fullUpdate(); 381 } else if (drag1) { 382 this.point1.setPositionDirectly(Const.COORDS_BY_USER, [ 383 this.point2.X() + (x * d_new) / d, 384 this.point2.Y() + (y * d_new) / d 385 ]); 386 this.point1.fullUpdate(); 387 } 388 } 389 // Finally, we save the position of the two points. 390 this.fixedLengthOldCoords[0].setCoordinates( 391 Const.COORDS_BY_USER, 392 this.point1.coords.usrCoords 393 ); 394 this.fixedLengthOldCoords[1].setCoordinates( 395 Const.COORDS_BY_USER, 396 this.point2.coords.usrCoords 397 ); 398 } 399 400 return this; 401 }, 402 403 /** 404 * Updates the stdform derived from the parent point positions. 405 * @private 406 */ 407 updateStdform: function () { 408 var v = Mat.crossProduct( 409 this.point1.coords.usrCoords, 410 this.point2.coords.usrCoords 411 ); 412 413 this.stdform[0] = v[0]; 414 this.stdform[1] = v[1]; 415 this.stdform[2] = v[2]; 416 this.stdform[3] = 0; 417 418 this.normalize(); 419 }, 420 421 /** 422 * Uses the boards renderer to update the line. 423 * @private 424 */ 425 updateRenderer: function () { 426 //var wasReal; 427 428 if (!this.needsUpdate) { 429 return this; 430 } 431 432 if (this.visPropCalc.visible) { 433 // wasReal = this.isReal; 434 this.isReal = 435 !isNaN( 436 this.point1.coords.usrCoords[1] + 437 this.point1.coords.usrCoords[2] + 438 this.point2.coords.usrCoords[1] + 439 this.point2.coords.usrCoords[2] 440 ) && Mat.innerProduct(this.stdform, this.stdform, 3) >= Mat.eps * Mat.eps; 441 442 if ( 443 //wasReal && 444 !this.isReal 445 ) { 446 this.updateVisibility(false); 447 } 448 } 449 450 if (this.visPropCalc.visible) { 451 this.board.renderer.updateLine(this); 452 } 453 454 /* Update the label if visible. */ 455 if ( 456 this.hasLabel && 457 this.visPropCalc.visible && 458 this.label && 459 this.label.visPropCalc.visible && 460 this.isReal 461 ) { 462 this.label.update(); 463 this.board.renderer.updateText(this.label); 464 } 465 466 // Update rendNode display 467 this.setDisplayRendNode(); 468 // if (this.visPropCalc.visible !== this.visPropOld.visible) { 469 // this.setDisplayRendNode(this.visPropCalc.visible); 470 // if (this.hasLabel) { 471 // this.board.renderer.display(this.label, this.label.visPropCalc.visible); 472 // } 473 // } 474 475 this.needsUpdate = false; 476 return this; 477 }, 478 479 // /** 480 // * Used to generate a polynomial for a point p that lies on this line, i.e. p is collinear to 481 // * {@link JXG.Line#point1} and {@link JXG.Line#point2}. 482 // * 483 // * @param {JXG.Point} p The point for that the polynomial is generated. 484 // * @returns {Array} An array containing the generated polynomial. 485 // * @private 486 // */ 487 generatePolynomial: function (p) { 488 var u1 = this.point1.symbolic.x, 489 u2 = this.point1.symbolic.y, 490 v1 = this.point2.symbolic.x, 491 v2 = this.point2.symbolic.y, 492 w1 = p.symbolic.x, 493 w2 = p.symbolic.y; 494 495 /* 496 * The polynomial in this case is determined by three points being collinear: 497 * 498 * U (u1,u2) W (w1,w2) V (v1,v2) 499 * ----x--------------x------------------------x---------------- 500 * 501 * The collinearity condition is 502 * 503 * u2-w2 w2-v2 504 * ------- = ------- (1) 505 * u1-w1 w1-v1 506 * 507 * Multiplying (1) with denominators and simplifying is 508 * 509 * u2w1 - u2v1 + w2v1 - u1w2 + u1v2 - w1v2 = 0 510 */ 511 512 return [ 513 [ 514 "(", u2, ")*(", w1, ")-(", u2, ")*(", v1, ")+(", w2, ")*(", v1, ")-(", u1, ")*(", w2, ")+(", u1, ")*(", v2, ")-(", w1, ")*(", v2, ")" 515 ].join("") 516 ]; 517 }, 518 519 /** 520 * Calculates the y intersect of the line. 521 * @returns {Number} The y intersect. 522 */ 523 getRise: function () { 524 if (Math.abs(this.stdform[2]) >= Mat.eps) { 525 return -this.stdform[0] / this.stdform[2]; 526 } 527 528 return Infinity; 529 }, 530 531 /** 532 * Calculates the slope of the line. 533 * @returns {Number} The slope of the line or Infinity if the line is parallel to the y-axis. 534 */ 535 Slope: function () { 536 if (Math.abs(this.stdform[2]) >= Mat.eps) { 537 return -this.stdform[1] / this.stdform[2]; 538 } 539 540 return Infinity; 541 }, 542 543 /** 544 * Alias for line.Slope 545 * @returns {Number} The slope of the line or Infinity if the line is parallel to the y-axis. 546 * @deprecated 547 * @see #Slope 548 */ 549 getSlope: function () { 550 return this.Slope(); 551 }, 552 553 /** 554 * Determines the angle between the positive x axis and the line. 555 * @returns {Number} 556 */ 557 getAngle: function () { 558 return Math.atan2(-this.stdform[1], this.stdform[2]); 559 }, 560 561 /** 562 * Returns the direction vector of the line. This is an array of length two 563 * containing the direction vector as [x, y]. It is defined as 564 * <li> the difference of the x- and y-coordinate of the second and first point, in case both points are finite or both points are infinite. 565 * <li> [x, y] coordinates of point2, in case only point2 is infinite. 566 * <li> [-x, -y] coordinates of point1, in case only point1 is infinite. 567 * @function 568 * @returns {Array} of length 2. 569 */ 570 Direction: function () { 571 var coords1 = this.point1.coords.usrCoords, 572 coords2 = this.point2.coords.usrCoords; 573 574 if (coords2[0] === 0 && coords1[0] !== 0) { 575 return coords2.slice(1); 576 } 577 578 if (coords1[0] === 0 && coords2[0] !== 0) { 579 return [-coords1[1], -coords1[2]]; 580 } 581 582 return [ 583 coords2[1] - coords1[1], 584 coords2[2] - coords1[2] 585 ]; 586 }, 587 588 /** 589 * Returns true, if the line is vertical (if the x coordinate of the direction vector is 0). 590 * @function 591 * @returns {Boolean} 592 */ 593 isVertical: function () { 594 var dir = this.Direction(); 595 return dir[0] === 0 && dir[1] !== 0; 596 }, 597 598 /** 599 * Returns true, if the line is horizontal (if the y coordinate of the direction vector is 0). 600 * @function 601 * @returns {Boolean} 602 */ 603 isHorizontal: function () { 604 var dir = this.Direction(); 605 return dir[1] === 0 && dir[0] !== 0; 606 }, 607 608 /** 609 * Determines whether the line is drawn beyond {@link JXG.Line#point1} and 610 * {@link JXG.Line#point2} and updates the line. 611 * @param {Boolean} straightFirst True if the Line shall be drawn beyond 612 * {@link JXG.Line#point1}, false otherwise. 613 * @param {Boolean} straightLast True if the Line shall be drawn beyond 614 * {@link JXG.Line#point2}, false otherwise. 615 * @see #straightFirst 616 * @see #straightLast 617 * @private 618 */ 619 setStraight: function (straightFirst, straightLast) { 620 this.visProp.straightfirst = straightFirst; 621 this.visProp.straightlast = straightLast; 622 623 this.board.renderer.updateLine(this); 624 return this; 625 }, 626 627 // documented in geometry element 628 getTextAnchor: function () { 629 return new Coords( 630 Const.COORDS_BY_USER, 631 [ 632 0.5 * (this.point2.X() + this.point1.X()), 633 0.5 * (this.point2.Y() + this.point1.Y()) 634 ], 635 this.board 636 ); 637 }, 638 639 /** 640 * Adjusts Label coords relative to Anchor. DESCRIPTION 641 * @private 642 */ 643 setLabelRelativeCoords: function (relCoords) { 644 if (Type.exists(this.label)) { 645 this.label.relativeCoords = new Coords( 646 Const.COORDS_BY_SCREEN, 647 [relCoords[0], -relCoords[1]], 648 this.board 649 ); 650 } 651 }, 652 653 // documented in geometry element 654 getLabelAnchor: function () { 655 var x, y, 656 fs = 0, 657 c1 = new Coords(Const.COORDS_BY_USER, this.point1.coords.usrCoords, this.board), 658 c2 = new Coords(Const.COORDS_BY_USER, this.point2.coords.usrCoords, this.board), 659 ev_sf = Type.evaluate(this.visProp.straightfirst), 660 ev_sl = Type.evaluate(this.visProp.straightlast); 661 662 if (ev_sf || ev_sl) { 663 Geometry.calcStraight(this, c1, c2, 0); 664 } 665 666 c1 = c1.scrCoords; 667 c2 = c2.scrCoords; 668 669 if (!Type.exists(this.label)) { 670 return new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], this.board); 671 } 672 673 switch (Type.evaluate(this.label.visProp.position)) { 674 case 'last': 675 x = c2[1]; 676 y = c2[2]; 677 break; 678 case 'first': 679 x = c1[1]; 680 y = c1[2]; 681 break; 682 case "lft": 683 case "llft": 684 case "ulft": 685 if (c1[1] <= c2[1]) { 686 x = c1[1]; 687 y = c1[2]; 688 } else { 689 x = c2[1]; 690 y = c2[2]; 691 } 692 break; 693 case "rt": 694 case "lrt": 695 case "urt": 696 if (c1[1] > c2[1]) { 697 x = c1[1]; 698 y = c1[2]; 699 } else { 700 x = c2[1]; 701 y = c2[2]; 702 } 703 break; 704 default: 705 x = 0.5 * (c1[1] + c2[1]); 706 y = 0.5 * (c1[2] + c2[2]); 707 } 708 709 // Correct coordinates if the label seems to be outside of canvas. 710 if (ev_sf || ev_sl) { 711 if (Type.exists(this.label)) { 712 // Does not exist during createLabel 713 fs = Type.evaluate(this.label.visProp.fontsize); 714 } 715 716 if (Math.abs(x) < Mat.eps) { 717 x = fs; 718 } else if ( 719 this.board.canvasWidth + Mat.eps > x && 720 x > this.board.canvasWidth - fs - Mat.eps 721 ) { 722 x = this.board.canvasWidth - fs; 723 } 724 725 if (Mat.eps + fs > y && y > -Mat.eps) { 726 y = fs; 727 } else if ( 728 this.board.canvasHeight + Mat.eps > y && 729 y > this.board.canvasHeight - fs - Mat.eps 730 ) { 731 y = this.board.canvasHeight - fs; 732 } 733 } 734 735 return new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board); 736 }, 737 738 // documented in geometry element 739 cloneToBackground: function () { 740 var copy = {}, 741 r, 742 s, 743 er; 744 745 copy.id = this.id + "T" + this.numTraces; 746 copy.elementClass = Const.OBJECT_CLASS_LINE; 747 this.numTraces++; 748 copy.point1 = this.point1; 749 copy.point2 = this.point2; 750 751 copy.stdform = this.stdform; 752 753 copy.board = this.board; 754 755 copy.visProp = Type.deepCopy(this.visProp, this.visProp.traceattributes, true); 756 copy.visProp.layer = this.board.options.layer.trace; 757 Type.clearVisPropOld(copy); 758 copy.visPropCalc = { 759 visible: Type.evaluate(copy.visProp.visible) 760 }; 761 762 s = this.getSlope(); 763 r = this.getRise(); 764 copy.getSlope = function () { 765 return s; 766 }; 767 copy.getRise = function () { 768 return r; 769 }; 770 771 er = this.board.renderer.enhancedRendering; 772 this.board.renderer.enhancedRendering = true; 773 this.board.renderer.drawLine(copy); 774 this.board.renderer.enhancedRendering = er; 775 this.traces[copy.id] = copy.rendNode; 776 777 return this; 778 }, 779 780 /** 781 * Add transformations to this line. 782 * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of 783 * {@link JXG.Transformation}s. 784 * @returns {JXG.Line} Reference to this line object. 785 */ 786 addTransform: function (transform) { 787 var i, 788 list = Type.isArray(transform) ? transform : [transform], 789 len = list.length; 790 791 for (i = 0; i < len; i++) { 792 this.point1.transformations.push(list[i]); 793 this.point2.transformations.push(list[i]); 794 } 795 796 return this; 797 }, 798 799 // see GeometryElement.js 800 snapToGrid: function (pos) { 801 var c1, c2, dc, t, ticks, x, y, sX, sY; 802 803 if (Type.evaluate(this.visProp.snaptogrid)) { 804 if (this.parents.length < 3) { 805 // Line through two points 806 this.point1.handleSnapToGrid(true, true); 807 this.point2.handleSnapToGrid(true, true); 808 } else if (Type.exists(pos)) { 809 // Free line 810 sX = Type.evaluate(this.visProp.snapsizex); 811 sY = Type.evaluate(this.visProp.snapsizey); 812 813 c1 = new Coords(Const.COORDS_BY_SCREEN, [pos.Xprev, pos.Yprev], this.board); 814 815 x = c1.usrCoords[1]; 816 y = c1.usrCoords[2]; 817 818 if ( 819 sX <= 0 && 820 this.board.defaultAxes && 821 this.board.defaultAxes.x.defaultTicks 822 ) { 823 ticks = this.board.defaultAxes.x.defaultTicks; 824 sX = ticks.ticksDelta * (Type.evaluate(ticks.visProp.minorticks) + 1); 825 } 826 if ( 827 sY <= 0 && 828 this.board.defaultAxes && 829 this.board.defaultAxes.y.defaultTicks 830 ) { 831 ticks = this.board.defaultAxes.y.defaultTicks; 832 sY = ticks.ticksDelta * (Type.evaluate(ticks.visProp.minorticks) + 1); 833 } 834 835 // if no valid snap sizes are available, don't change the coords. 836 if (sX > 0 && sY > 0) { 837 // projectCoordsToLine 838 /* 839 v = [0, this.stdform[1], this.stdform[2]]; 840 v = Mat.crossProduct(v, c1.usrCoords); 841 c2 = Geometry.meetLineLine(v, this.stdform, 0, this.board); 842 */ 843 c2 = Geometry.projectPointToLine({ coords: c1 }, this, this.board); 844 845 dc = Statistics.subtract( 846 [1, Math.round(x / sX) * sX, Math.round(y / sY) * sY], 847 c2.usrCoords 848 ); 849 t = this.board.create("transform", dc.slice(1), { 850 type: "translate" 851 }); 852 t.applyOnce([this.point1, this.point2]); 853 } 854 } 855 } else { 856 this.point1.handleSnapToGrid(false, true); 857 this.point2.handleSnapToGrid(false, true); 858 } 859 860 return this; 861 }, 862 863 // see element.js 864 snapToPoints: function () { 865 var forceIt = Type.evaluate(this.visProp.snaptopoints); 866 867 if (this.parents.length < 3) { 868 // Line through two points 869 this.point1.handleSnapToPoints(forceIt); 870 this.point2.handleSnapToPoints(forceIt); 871 } 872 873 return this; 874 }, 875 876 /** 877 * Treat the line as parametric curve in homogeneous coordinates, where the parameter t runs from 0 to 1. 878 * First we transform the interval [0,1] to [-1,1]. 879 * If the line has homogeneous coordinates [c, a, b] = stdform[] then the direction of the line is [b, -a]. 880 * Now, we take one finite point that defines the line, i.e. we take either point1 or point2 881 * (in case the line is not the ideal line). 882 * Let the coordinates of that point be [z, x, y]. 883 * Then, the curve runs linearly from 884 * [0, b, -a] (t=-1) to [z, x, y] (t=0) 885 * and 886 * [z, x, y] (t=0) to [0, -b, a] (t=1) 887 * 888 * @param {Number} t Parameter running from 0 to 1. 889 * @returns {Number} X(t) x-coordinate of the line treated as parametric curve. 890 * */ 891 X: function (t) { 892 var x, 893 b = this.stdform[2]; 894 895 x = 896 Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps 897 ? this.point1.coords.usrCoords[1] 898 : this.point2.coords.usrCoords[1]; 899 900 t = (t - 0.5) * 2; 901 902 return (1 - Math.abs(t)) * x - t * b; 903 }, 904 905 /** 906 * Treat the line as parametric curve in homogeneous coordinates. 907 * See {@link JXG.Line#X} for a detailed description. 908 * @param {Number} t Parameter running from 0 to 1. 909 * @returns {Number} Y(t) y-coordinate of the line treated as parametric curve. 910 */ 911 Y: function (t) { 912 var y, 913 a = this.stdform[1]; 914 915 y = 916 Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps 917 ? this.point1.coords.usrCoords[2] 918 : this.point2.coords.usrCoords[2]; 919 920 t = (t - 0.5) * 2; 921 922 return (1 - Math.abs(t)) * y + t * a; 923 }, 924 925 /** 926 * Treat the line as parametric curve in homogeneous coordinates. 927 * See {@link JXG.Line#X} for a detailed description. 928 * 929 * @param {Number} t Parameter running from 0 to 1. 930 * @returns {Number} Z(t) z-coordinate of the line treated as parametric curve. 931 */ 932 Z: function (t) { 933 var z = 934 Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps 935 ? this.point1.coords.usrCoords[0] 936 : this.point2.coords.usrCoords[0]; 937 938 t = (t - 0.5) * 2; 939 940 return (1 - Math.abs(t)) * z; 941 }, 942 943 /** 944 * The distance between the two points defining the line. 945 * @returns {Number} 946 */ 947 L: function () { 948 return this.point1.Dist(this.point2); 949 }, 950 951 /** 952 * Set a new fixed length, then update the board. 953 * @param {String|Number|function} l A string, function or number describing the new length. 954 * @returns {JXG.Line} Reference to this line 955 */ 956 setFixedLength: function (l) { 957 if (!this.hasFixedLength) { 958 return this; 959 } 960 961 this.fixedLength = Type.createFunction(l, this.board); 962 this.board.update(); 963 964 return this; 965 }, 966 967 /** 968 * Treat the element as a parametric curve 969 * @private 970 */ 971 minX: function () { 972 return 0.0; 973 }, 974 975 /** 976 * Treat the element as parametric curve 977 * @private 978 */ 979 maxX: function () { 980 return 1.0; 981 }, 982 983 // documented in geometry element 984 bounds: function () { 985 var p1c = this.point1.coords.usrCoords, 986 p2c = this.point2.coords.usrCoords; 987 988 return [ 989 Math.min(p1c[1], p2c[1]), 990 Math.max(p1c[2], p2c[2]), 991 Math.max(p1c[1], p2c[1]), 992 Math.min(p1c[2], p2c[2]) 993 ]; 994 }, 995 996 // documented in GeometryElement.js 997 remove: function () { 998 this.removeAllTicks(); 999 GeometryElement.prototype.remove.call(this); 1000 } 1001 1002 // hideElement: function () { 1003 // var i; 1004 // 1005 // GeometryElement.prototype.hideElement.call(this); 1006 // 1007 // for (i = 0; i < this.ticks.length; i++) { 1008 // this.ticks[i].hideElement(); 1009 // } 1010 // }, 1011 // 1012 // showElement: function () { 1013 // var i; 1014 // GeometryElement.prototype.showElement.call(this); 1015 // 1016 // for (i = 0; i < this.ticks.length; i++) { 1017 // this.ticks[i].showElement(); 1018 // } 1019 // } 1020 1021 } 1022 ); 1023 1024 /** 1025 * @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 1026 * a line can be used as an arrow and/or axis. 1027 * @pseudo 1028 * @name Line 1029 * @augments JXG.Line 1030 * @constructor 1031 * @type JXG.Line 1032 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1033 * @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 1034 * numbers describing the coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 1035 * It is possible to provide a function returning an array or a point, instead of providing an array or a point. 1036 * @param {Number,function_Number,function_Number,function} a,b,c A line can also be created providing three numbers. The line is then described by 1037 * the set of solutions of the equation <tt>a*z+b*x+c*y = 0</tt>. For all finite points, z is normalized to the value 1. 1038 * It is possible to provide three functions returning numbers, too. 1039 * @param {function} f This function must return an array containing three numbers forming the line's homogeneous coordinates. 1040 * <p> 1041 * Additionally, a line can be created by providing a line and a transformation (or an array of transformations). 1042 * Then, the result is a line which is the transformation of the supplied line. 1043 * @example 1044 * // Create a line using point and coordinates/ 1045 * // The second point will be fixed and invisible. 1046 * var p1 = board.create('point', [4.5, 2.0]); 1047 * var l1 = board.create('line', [p1, [1.0, 1.0]]); 1048 * </pre><div class="jxgbox" id="JXGc0ae3461-10c4-4d39-b9be-81d74759d122" style="width: 300px; height: 300px;"></div> 1049 * <script type="text/javascript"> 1050 * var glex1_board = JXG.JSXGraph.initBoard('JXGc0ae3461-10c4-4d39-b9be-81d74759d122', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1051 * var glex1_p1 = glex1_board.create('point', [4.5, 2.0]); 1052 * var glex1_l1 = glex1_board.create('line', [glex1_p1, [1.0, 1.0]]); 1053 * </script><pre> 1054 * @example 1055 * // Create a line using three coordinates 1056 * var l1 = board.create('line', [1.0, -2.0, 3.0]); 1057 * </pre><div class="jxgbox" id="JXGcf45e462-f964-4ba4-be3a-c9db94e2593f" style="width: 300px; height: 300px;"></div> 1058 * <script type="text/javascript"> 1059 * var glex2_board = JXG.JSXGraph.initBoard('JXGcf45e462-f964-4ba4-be3a-c9db94e2593f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1060 * var glex2_l1 = glex2_board.create('line', [1.0, -2.0, 3.0]); 1061 * </script><pre> 1062 * @example 1063 * // Create a line (l2) as reflection of another line (l1) 1064 * // reflection line 1065 * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'}); 1066 * var reflect = board.create('transform', [li], {type: 'reflect'}); 1067 * 1068 * var l1 = board.create('line', [1,-5,1]); 1069 * var l2 = board.create('line', [l1, reflect]); 1070 * 1071 * </pre><div id="JXGJXGa00d7dd6-d38c-11e7-93b3-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 1072 * <script type="text/javascript"> 1073 * (function() { 1074 * var board = JXG.JSXGraph.initBoard('JXGJXGa00d7dd6-d38c-11e7-93b3-901b0e1b8723', 1075 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1076 * // reflection line 1077 * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'}); 1078 * var reflect = board.create('transform', [li], {type: 'reflect'}); 1079 * 1080 * var l1 = board.create('line', [1,-5,1]); 1081 * var l2 = board.create('line', [l1, reflect]); 1082 * })(); 1083 * 1084 * </script><pre> 1085 * 1086 * @example 1087 * var t = board.create('transform', [2, 1.5], {type: 'scale'}); 1088 * var l1 = board.create('line', [1, -5, 1]); 1089 * var l2 = board.create('line', [l1, t]); 1090 * 1091 * </pre><div id="d16d5b58-6338-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 1092 * <script type="text/javascript"> 1093 * (function() { 1094 * var board = JXG.JSXGraph.initBoard('d16d5b58-6338-11e8-9fb9-901b0e1b8723', 1095 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1096 * var t = board.create('transform', [2, 1.5], {type: 'scale'}); 1097 * var l1 = board.create('line', [1, -5, 1]); 1098 * var l2 = board.create('line', [l1, t]); 1099 * 1100 * })(); 1101 * 1102 * </script><pre> 1103 * 1104 * @example 1105 * //create line between two points 1106 * var p1 = board.create('point', [0,0]); 1107 * var p2 = board.create('point', [2,2]); 1108 * var l1 = board.create('line', [p1,p2], {straightFirst:false, straightLast:false}); 1109 * </pre><div id="d21d5b58-6338-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 1110 * <script type="text/javascript"> 1111 * (function() { 1112 * var board = JXG.JSXGraph.initBoard('d21d5b58-6338-11e8-9fb9-901b0e1b8723', 1113 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1114 * var ex5p1 = board.create('point', [0,0]); 1115 * var ex5p2 = board.create('point', [2,2]); 1116 * var ex5l1 = board.create('line', [ex5p1,ex5p2], {straightFirst:false, straightLast:false}); 1117 * })(); 1118 * 1119 * </script><pre> 1120 */ 1121 JXG.createLine = function (board, parents, attributes) { 1122 var ps, el, p1, p2, i, attr, 1123 c = [], 1124 doTransform = false, 1125 constrained = false, 1126 isDraggable; 1127 1128 if (parents.length === 2) { 1129 // The line is defined by two points or coordinates of two points. 1130 // In the latter case, the points are created. 1131 attr = Type.copyAttributes(attributes, board.options, "line", "point1"); 1132 if (Type.isArray(parents[0]) && parents[0].length > 1) { 1133 p1 = board.create("point", parents[0], attr); 1134 } else if (Type.isString(parents[0]) || Type.isPoint(parents[0])) { 1135 p1 = board.select(parents[0]); 1136 } else if (Type.isFunction(parents[0]) && Type.isPoint(parents[0]())) { 1137 p1 = parents[0](); 1138 constrained = true; 1139 } else if ( 1140 Type.isFunction(parents[0]) && 1141 parents[0]().length && 1142 parents[0]().length >= 2 1143 ) { 1144 p1 = JXG.createPoint(board, parents[0](), attr); 1145 constrained = true; 1146 } else if (Type.isObject(parents[0]) && Type.isTransformationOrArray(parents[1])) { 1147 doTransform = true; 1148 p1 = board.create("point", [parents[0].point1, parents[1]], attr); 1149 } else { 1150 throw new Error( 1151 "JSXGraph: Can't create line with parent types '" + 1152 typeof parents[0] + 1153 "' and '" + 1154 typeof parents[1] + 1155 "'." + 1156 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]" 1157 ); 1158 } 1159 1160 // point 2 given by coordinates 1161 attr = Type.copyAttributes(attributes, board.options, "line", "point2"); 1162 if (doTransform) { 1163 p2 = board.create("point", [parents[0].point2, parents[1]], attr); 1164 } else if (Type.isArray(parents[1]) && parents[1].length > 1) { 1165 p2 = board.create("point", parents[1], attr); 1166 } else if (Type.isString(parents[1]) || Type.isPoint(parents[1])) { 1167 p2 = board.select(parents[1]); 1168 } else if (Type.isFunction(parents[1]) && Type.isPoint(parents[1]())) { 1169 p2 = parents[1](); 1170 constrained = true; 1171 } else if ( 1172 Type.isFunction(parents[1]) && 1173 parents[1]().length && 1174 parents[1]().length >= 2 1175 ) { 1176 p2 = JXG.createPoint(board, parents[1](), attr); 1177 constrained = true; 1178 } else { 1179 throw new Error( 1180 "JSXGraph: Can't create line with parent types '" + 1181 typeof parents[0] + 1182 "' and '" + 1183 typeof parents[1] + 1184 "'." + 1185 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]" 1186 ); 1187 } 1188 1189 attr = Type.copyAttributes(attributes, board.options, "line"); 1190 el = new JXG.Line(board, p1, p2, attr); 1191 1192 if (constrained) { 1193 el.constrained = true; 1194 el.funp1 = parents[0]; 1195 el.funp2 = parents[1]; 1196 } else if (!doTransform) { 1197 el.isDraggable = true; 1198 } 1199 1200 //if (!el.constrained) { 1201 el.setParents([p1.id, p2.id]); 1202 //} 1203 1204 } else if (parents.length === 3) { 1205 // Free line: 1206 // Line is defined by three homogeneous coordinates. 1207 // Also in this case points are created. 1208 isDraggable = true; 1209 for (i = 0; i < 3; i++) { 1210 if (Type.isNumber(parents[i])) { 1211 // createFunction will just wrap a function around our constant number 1212 // that does nothing else but to return that number. 1213 c[i] = Type.createFunction(parents[i]); 1214 } else if (Type.isFunction(parents[i])) { 1215 c[i] = parents[i]; 1216 isDraggable = false; 1217 } else { 1218 throw new Error( 1219 "JSXGraph: Can't create line with parent types '" + 1220 typeof parents[0] + 1221 "' and '" + 1222 typeof parents[1] + 1223 "' and '" + 1224 typeof parents[2] + 1225 "'." + 1226 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]" 1227 ); 1228 } 1229 } 1230 1231 // point 1 is the midpoint between (0, c, -b) and point 2. => point1 is finite. 1232 attr = Type.copyAttributes(attributes, board.options, "line", "point1"); 1233 if (isDraggable) { 1234 p1 = board.create("point", [ 1235 c[2]() * c[2]() + c[1]() * c[1](), 1236 c[2]() - c[1]() * c[0]() + c[2](), 1237 -c[1]() - c[2]() * c[0]() - c[1]() 1238 ], attr); 1239 } else { 1240 p1 = board.create("point", [ 1241 function () { 1242 return (c[2]() * c[2]() + c[1]() * c[1]()) * 0.5; 1243 }, 1244 function () { 1245 return (c[2]() - c[1]() * c[0]() + c[2]()) * 0.5; 1246 }, 1247 function () { 1248 return (-c[1]() - c[2]() * c[0]() - c[1]()) * 0.5; 1249 } 1250 ], attr); 1251 } 1252 1253 // point 2: (b^2+c^2,-ba+c,-ca-b) 1254 attr = Type.copyAttributes(attributes, board.options, "line", "point2"); 1255 if (isDraggable) { 1256 p2 = board.create("point", [ 1257 c[2]() * c[2]() + c[1]() * c[1](), 1258 -c[1]() * c[0]() + c[2](), 1259 -c[2]() * c[0]() - c[1]() 1260 ], attr); 1261 } else { 1262 p2 = board.create("point", [ 1263 function () { 1264 return c[2]() * c[2]() + c[1]() * c[1](); 1265 }, 1266 function () { 1267 return -c[1]() * c[0]() + c[2](); 1268 }, 1269 function () { 1270 return -c[2]() * c[0]() - c[1](); 1271 } 1272 ], attr); 1273 } 1274 1275 // If the line will have a glider and board.suspendUpdate() has been called, we 1276 // need to compute the initial position of the two points p1 and p2. 1277 p1.prepareUpdate().update(); 1278 p2.prepareUpdate().update(); 1279 attr = Type.copyAttributes(attributes, board.options, "line"); 1280 el = new JXG.Line(board, p1, p2, attr); 1281 // Not yet working, because the points are not draggable. 1282 el.isDraggable = isDraggable; 1283 el.setParents([p1, p2]); 1284 1285 } else if ( 1286 // The parent array contains a function which returns two points. 1287 parents.length === 1 && 1288 Type.isFunction(parents[0]) && 1289 parents[0]().length === 2 && 1290 Type.isPoint(parents[0]()[0]) && 1291 Type.isPoint(parents[0]()[1]) 1292 ) { 1293 ps = parents[0](); 1294 attr = Type.copyAttributes(attributes, board.options, "line"); 1295 el = new JXG.Line(board, ps[0], ps[1], attr); 1296 el.constrained = true; 1297 el.funps = parents[0]; 1298 el.setParents(ps); 1299 } else if ( 1300 parents.length === 1 && 1301 Type.isFunction(parents[0]) && 1302 parents[0]().length === 3 && 1303 Type.isNumber(parents[0]()[0]) && 1304 Type.isNumber(parents[0]()[1]) && 1305 Type.isNumber(parents[0]()[2]) 1306 ) { 1307 ps = parents[0]; 1308 1309 attr = Type.copyAttributes(attributes, board.options, "line", "point1"); 1310 p1 = board.create("point", [ 1311 function () { 1312 var c = ps(); 1313 1314 return [ 1315 (c[2] * c[2] + c[1] * c[1]) * 0.5, 1316 (c[2] - c[1] * c[0] + c[2]) * 0.5, 1317 (-c[1] - c[2] * c[0] - c[1]) * 0.5 1318 ]; 1319 } 1320 ], attr); 1321 1322 attr = Type.copyAttributes(attributes, board.options, "line", "point2"); 1323 p2 = board.create("point", [ 1324 function () { 1325 var c = ps(); 1326 1327 return [ 1328 c[2] * c[2] + c[1] * c[1], 1329 -c[1] * c[0] + c[2], 1330 -c[2] * c[0] - c[1] 1331 ]; 1332 } 1333 ], attr); 1334 1335 attr = Type.copyAttributes(attributes, board.options, "line"); 1336 el = new JXG.Line(board, p1, p2, attr); 1337 1338 el.constrained = true; 1339 el.funps = parents[0]; 1340 el.setParents([p1, p2]); 1341 } else { 1342 throw new Error( 1343 "JSXGraph: Can't create line with parent types '" + 1344 typeof parents[0] + 1345 "' and '" + 1346 typeof parents[1] + 1347 "'." + 1348 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]" 1349 ); 1350 } 1351 1352 return el; 1353 }; 1354 1355 JXG.registerElement("line", JXG.createLine); 1356 1357 /** 1358 * @class This element is used to provide a constructor for a segment. 1359 * It's strictly spoken just a wrapper for element {@link Line} with {@link Line#straightFirst} 1360 * and {@link Line#straightLast} properties set to false. If there is a third variable then the 1361 * segment has a fixed length (which may be a function, too) determined by the absolute value of 1362 * that number. 1363 * @pseudo 1364 * @name Segment 1365 * @augments JXG.Line 1366 * @constructor 1367 * @type JXG.Line 1368 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1369 * @param {JXG.Point,array_JXG.Point,array} point1,point2 Parent elements can be two elements either of type {@link JXG.Point} 1370 * or array of numbers describing the 1371 * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 1372 * @param {number,function} [length] The points are adapted - if possible - such that their distance 1373 * is equal to the absolute value of this number. 1374 * @see Line 1375 * @example 1376 * // Create a segment providing two points. 1377 * var p1 = board.create('point', [4.5, 2.0]); 1378 * var p2 = board.create('point', [1.0, 1.0]); 1379 * var l1 = board.create('segment', [p1, p2]); 1380 * </pre><div class="jxgbox" id="JXGd70e6aac-7c93-4525-a94c-a1820fa38e2f" style="width: 300px; height: 300px;"></div> 1381 * <script type="text/javascript"> 1382 * var slex1_board = JXG.JSXGraph.initBoard('JXGd70e6aac-7c93-4525-a94c-a1820fa38e2f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1383 * var slex1_p1 = slex1_board.create('point', [4.5, 2.0]); 1384 * var slex1_p2 = slex1_board.create('point', [1.0, 1.0]); 1385 * var slex1_l1 = slex1_board.create('segment', [slex1_p1, slex1_p2]); 1386 * </script><pre> 1387 * 1388 * @example 1389 * // Create a segment providing two points. 1390 * var p1 = board.create('point', [4.0, 1.0]); 1391 * var p2 = board.create('point', [1.0, 1.0]); 1392 * // AB 1393 * var l1 = board.create('segment', [p1, p2]); 1394 * var p3 = board.create('point', [4.0, 2.0]); 1395 * var p4 = board.create('point', [1.0, 2.0]); 1396 * // CD 1397 * var l2 = board.create('segment', [p3, p4, 3]); // Fixed length 1398 * var p5 = board.create('point', [4.0, 3.0]); 1399 * var p6 = board.create('point', [1.0, 4.0]); 1400 * // EF 1401 * var l3 = board.create('segment', [p5, p6, function(){ return l1.L();} ]); // Fixed, but dependent length 1402 * </pre><div class="jxgbox" id="JXG617336ba-0705-4b2b-a236-c87c28ef25be" style="width: 300px; height: 300px;"></div> 1403 * <script type="text/javascript"> 1404 * var slex2_board = JXG.JSXGraph.initBoard('JXG617336ba-0705-4b2b-a236-c87c28ef25be', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1405 * var slex2_p1 = slex2_board.create('point', [4.0, 1.0]); 1406 * var slex2_p2 = slex2_board.create('point', [1.0, 1.0]); 1407 * var slex2_l1 = slex2_board.create('segment', [slex2_p1, slex2_p2]); 1408 * var slex2_p3 = slex2_board.create('point', [4.0, 2.0]); 1409 * var slex2_p4 = slex2_board.create('point', [1.0, 2.0]); 1410 * var slex2_l2 = slex2_board.create('segment', [slex2_p3, slex2_p4, 3]); 1411 * var slex2_p5 = slex2_board.create('point', [4.0, 2.0]); 1412 * var slex2_p6 = slex2_board.create('point', [1.0, 2.0]); 1413 * var slex2_l3 = slex2_board.create('segment', [slex2_p5, slex2_p6, function(){ return slex2_l1.L();}]); 1414 * </script><pre> 1415 * 1416 */ 1417 JXG.createSegment = function (board, parents, attributes) { 1418 var el, attr; 1419 1420 attributes.straightFirst = false; 1421 attributes.straightLast = false; 1422 attr = Type.copyAttributes(attributes, board.options, "segment"); 1423 1424 el = board.create("line", parents.slice(0, 2), attr); 1425 1426 if (parents.length === 3) { 1427 el.hasFixedLength = true; 1428 1429 if (Type.isNumber(parents[2])) { 1430 el.fixedLength = function () { 1431 return parents[2]; 1432 }; 1433 } else if (Type.isFunction(parents[2])) { 1434 el.fixedLength = Type.createFunction(parents[2], this.board); 1435 } else { 1436 throw new Error( 1437 "JSXGraph: Can't create segment with third parent type '" + 1438 typeof parents[2] + 1439 "'." + 1440 "\nPossible third parent types: number or function" 1441 ); 1442 } 1443 1444 el.getParents = function () { 1445 return this.parents.concat(this.fixedLength()); 1446 }; 1447 1448 el.fixedLengthOldCoords = []; 1449 el.fixedLengthOldCoords[0] = new Coords( 1450 Const.COORDS_BY_USER, 1451 el.point1.coords.usrCoords.slice(1, 3), 1452 board 1453 ); 1454 el.fixedLengthOldCoords[1] = new Coords( 1455 Const.COORDS_BY_USER, 1456 el.point2.coords.usrCoords.slice(1, 3), 1457 board 1458 ); 1459 } 1460 1461 el.elType = "segment"; 1462 1463 return el; 1464 }; 1465 1466 JXG.registerElement("segment", JXG.createSegment); 1467 1468 /** 1469 * @class This element is used to provide a constructor for arrow, which is just a wrapper for element 1470 * {@link Line} with {@link Line#straightFirst} 1471 * and {@link Line#straightLast} properties set to false and {@link Line#lastArrow} set to true. 1472 * @pseudo 1473 * @name Arrow 1474 * @augments JXG.Line 1475 * @constructor 1476 * @type JXG.Line 1477 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1478 * @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 1479 * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 1480 * @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 1481 * of the equation <tt>a*x+b*y+c*z = 0</tt>. 1482 * @see Line 1483 * @example 1484 * // Create an arrow providing two points. 1485 * var p1 = board.create('point', [4.5, 2.0]); 1486 * var p2 = board.create('point', [1.0, 1.0]); 1487 * var l1 = board.create('arrow', [p1, p2]); 1488 * </pre><div class="jxgbox" id="JXG1d26bd22-7d6d-4018-b164-4c8bc8d22ccf" style="width: 300px; height: 300px;"></div> 1489 * <script type="text/javascript"> 1490 * var alex1_board = JXG.JSXGraph.initBoard('JXG1d26bd22-7d6d-4018-b164-4c8bc8d22ccf', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1491 * var alex1_p1 = alex1_board.create('point', [4.5, 2.0]); 1492 * var alex1_p2 = alex1_board.create('point', [1.0, 1.0]); 1493 * var alex1_l1 = alex1_board.create('arrow', [alex1_p1, alex1_p2]); 1494 * </script><pre> 1495 */ 1496 JXG.createArrow = function (board, parents, attributes) { 1497 var el, attr; 1498 1499 attributes.straightFirst = false; 1500 attributes.straightLast = false; 1501 attr = Type.copyAttributes(attributes, board.options, "arrow"); 1502 el = board.create("line", parents, attr); 1503 //el.setArrow(false, true); 1504 el.type = Const.OBJECT_TYPE_VECTOR; 1505 el.elType = "arrow"; 1506 1507 return el; 1508 }; 1509 1510 JXG.registerElement("arrow", JXG.createArrow); 1511 1512 /** 1513 * @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 Line#straightFirst} 1514 * and {@link Line#straightLast} properties set to true. Additionally {@link Line#lastArrow} is set to true and default {@link Ticks} will be created. 1515 * @pseudo 1516 * @name Axis 1517 * @augments JXG.Line 1518 * @constructor 1519 * @type JXG.Line 1520 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1521 * @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 1522 * coordinates of a point. In the latter case, the point will be constructed automatically as a fixed invisible point. 1523 * @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 1524 * of the equation <tt>a*x+b*y+c*z = 0</tt>. 1525 * @example 1526 * // Create an axis providing two coords pairs. 1527 * var l1 = board.create('axis', [[0.0, 1.0], [1.0, 1.3]]); 1528 * </pre><div class="jxgbox" id="JXG4f414733-624c-42e4-855c-11f5530383ae" style="width: 300px; height: 300px;"></div> 1529 * <script type="text/javascript"> 1530 * var axex1_board = JXG.JSXGraph.initBoard('JXG4f414733-624c-42e4-855c-11f5530383ae', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1531 * var axex1_l1 = axex1_board.create('axis', [[0.0, 1.0], [1.0, 1.3]]); 1532 * </script><pre> 1533 * @example 1534 * // Create ticks labels as fractions 1535 * board.create('axis', [[0,1], [1,1]], { 1536 * ticks: { 1537 * label: { 1538 * toFraction: true, 1539 * useMathjax: false, 1540 * anchorX: 'middle', 1541 * offset: [0, -10] 1542 * } 1543 * } 1544 * }); 1545 * 1546 * 1547 * </pre><div id="JXG34174cc4-0050-4ab4-af69-e91365d0666f" class="jxgbox" style="width: 300px; height: 300px;"></div> 1548 * <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" id="MathJax-script"></script> 1549 * <script type="text/javascript"> 1550 * (function() { 1551 * var board = JXG.JSXGraph.initBoard('JXG34174cc4-0050-4ab4-af69-e91365d0666f', 1552 * {boundingbox: [-1.2, 2.3, 1.2, -2.3], axis: true, showcopyright: false, shownavigation: false}); 1553 * board.create('axis', [[0,1], [1,1]], { 1554 * ticks: { 1555 * label: { 1556 * toFraction: true, 1557 * useMathjax: false, 1558 * anchorX: 'middle', 1559 * offset: [0, -10] 1560 * } 1561 * } 1562 * }); 1563 * 1564 * 1565 * })(); 1566 * 1567 * </script><pre> 1568 * 1569 */ 1570 JXG.createAxis = function (board, parents, attributes) { 1571 var axis, attr, 1572 ancestor, ticksDist; 1573 1574 // Create line 1575 attr = Type.copyAttributes(attributes, board.options, "axis"); 1576 try { 1577 axis = board.create("line", parents, attr); 1578 } catch (err) { 1579 throw new Error( 1580 "JSXGraph: Can't create axis with parent types '" + 1581 typeof parents[0] + 1582 "' and '" + 1583 typeof parents[1] + 1584 "'." + 1585 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]" 1586 ); 1587 } 1588 1589 axis.type = Const.OBJECT_TYPE_AXIS; 1590 axis.isDraggable = false; 1591 axis.point1.isDraggable = false; 1592 axis.point2.isDraggable = false; 1593 1594 // Save usrCoords of points 1595 axis._point1UsrCoordsOrg = axis.point1.coords.usrCoords.slice(); 1596 axis._point2UsrCoordsOrg = axis.point2.coords.usrCoords.slice(); 1597 1598 for (ancestor in axis.ancestors) { 1599 if (axis.ancestors.hasOwnProperty(ancestor)) { 1600 axis.ancestors[ancestor].type = Const.OBJECT_TYPE_AXISPOINT; 1601 } 1602 } 1603 1604 // Create ticks 1605 // attrTicks = attr.ticks; 1606 if (Type.exists(attr.ticks.ticksdistance)) { 1607 ticksDist = attr.ticks.ticksdistance; 1608 } else if (Type.isArray(attr.ticks.ticks)) { 1609 ticksDist = attr.ticks.ticks; 1610 } else { 1611 ticksDist = 1.0; 1612 } 1613 1614 /** 1615 * The ticks attached to the axis. 1616 * @memberOf Axis.prototype 1617 * @name defaultTicks 1618 * @type JXG.Ticks 1619 */ 1620 axis.defaultTicks = board.create("ticks", [axis, ticksDist], attr.ticks); 1621 axis.defaultTicks.dump = false; 1622 axis.elType = "axis"; 1623 axis.subs = { 1624 ticks: axis.defaultTicks 1625 }; 1626 axis.inherits.push(axis.defaultTicks); 1627 1628 axis.update = function () { 1629 var bbox, 1630 position, i, 1631 direction, horizontal, vertical, 1632 ticksAutoPos, ticksAutoPosThres, dist, 1633 anchor, left, right, 1634 distUsr, 1635 newPosP1, newPosP2, 1636 locationOrg, 1637 visLabel, anchr, off; 1638 1639 bbox = this.board.getBoundingBox(); 1640 position = Type.evaluate(this.visProp.position); 1641 direction = this.Direction(); 1642 horizontal = this.isHorizontal(); 1643 vertical = this.isVertical(); 1644 ticksAutoPos = Type.evaluate(this.visProp.ticksautopos); 1645 ticksAutoPosThres = Type.evaluate(this.visProp.ticksautoposthreshold); 1646 1647 if (horizontal) { 1648 ticksAutoPosThres = Type.parseNumber(ticksAutoPosThres, Math.abs(bbox[1] - bbox[3]), 1 / this.board.unitX) * this.board.unitX; 1649 } else if (vertical) { 1650 ticksAutoPosThres = Type.parseNumber(ticksAutoPosThres, Math.abs(bbox[1] - bbox[3]), 1 / this.board.unitY) * this.board.unitY; 1651 } else { 1652 ticksAutoPosThres = Type.parseNumber(ticksAutoPosThres, 1, 1); 1653 } 1654 1655 anchor = Type.evaluate(this.visProp.anchor); 1656 left = anchor.indexOf('left') > -1; 1657 right = anchor.indexOf('right') > -1; 1658 1659 distUsr = Type.evaluate(this.visProp.anchordist); 1660 if (horizontal) { 1661 distUsr = Type.parseNumber(distUsr, Math.abs(bbox[1] - bbox[3]), 1 / this.board.unitX); 1662 } else if (vertical) { 1663 distUsr = Type.parseNumber(distUsr, Math.abs(bbox[0] - bbox[2]), 1 / this.board.unitY); 1664 } else { 1665 distUsr = 0; 1666 } 1667 1668 locationOrg = this.board.getPointLoc(this._point1UsrCoordsOrg, distUsr); 1669 1670 // Set position of axis 1671 newPosP1 = this.point1.coords.usrCoords.slice(); 1672 newPosP2 = this.point2.coords.usrCoords.slice(); 1673 1674 if (position === 'static' || (!vertical && !horizontal)) { 1675 // Do nothing 1676 1677 } else if (position === 'fixed') { 1678 if (horizontal) { // direction[1] === 0 1679 if ((direction[0] > 0 && right) || (direction[0] < 0 && left)) { 1680 newPosP1[2] = bbox[3] + distUsr; 1681 newPosP2[2] = bbox[3] + distUsr; 1682 } else if ((direction[0] > 0 && left) || (direction[0] < 0 && right)) { 1683 newPosP1[2] = bbox[1] - distUsr; 1684 newPosP2[2] = bbox[1] - distUsr; 1685 1686 } else { 1687 newPosP1 = this._point1UsrCoordsOrg.slice(); 1688 newPosP2 = this._point2UsrCoordsOrg.slice(); 1689 } 1690 } 1691 if (vertical) { // direction[0] === 0 1692 if ((direction[1] > 0 && left) || (direction[1] < 0 && right)) { 1693 newPosP1[1] = bbox[0] + distUsr; 1694 newPosP2[1] = bbox[0] + distUsr; 1695 1696 } else if ((direction[1] > 0 && right) || (direction[1] < 0 && left)) { 1697 newPosP1[1] = bbox[2] - distUsr; 1698 newPosP2[1] = bbox[2] - distUsr; 1699 1700 } else { 1701 newPosP1 = this._point1UsrCoordsOrg.slice(); 1702 newPosP2 = this._point2UsrCoordsOrg.slice(); 1703 } 1704 } 1705 1706 } else if (position === 'sticky') { 1707 if (horizontal) { // direction[1] === 0 1708 if (locationOrg[1] < 0 && ((direction[0] > 0 && right) || (direction[0] < 0 && left))) { 1709 newPosP1[2] = bbox[3] + distUsr; 1710 newPosP2[2] = bbox[3] + distUsr; 1711 1712 } else if (locationOrg[1] > 0 && ((direction[0] > 0 && left) || (direction[0] < 0 && right))) { 1713 newPosP1[2] = bbox[1] - distUsr; 1714 newPosP2[2] = bbox[1] - distUsr; 1715 1716 } else { 1717 newPosP1 = this._point1UsrCoordsOrg.slice(); 1718 newPosP2 = this._point2UsrCoordsOrg.slice(); 1719 } 1720 } 1721 if (vertical) { // direction[0] === 0 1722 if (locationOrg[0] < 0 && ((direction[1] > 0 && left) || (direction[1] < 0 && right))) { 1723 newPosP1[1] = bbox[0] + distUsr; 1724 newPosP2[1] = bbox[0] + distUsr; 1725 1726 } else if (locationOrg[0] > 0 && ((direction[1] > 0 && right) || (direction[1] < 0 && left))) { 1727 newPosP1[1] = bbox[2] - distUsr; 1728 newPosP2[1] = bbox[2] - distUsr; 1729 1730 } else { 1731 newPosP1 = this._point1UsrCoordsOrg.slice(); 1732 newPosP2 = this._point2UsrCoordsOrg.slice(); 1733 } 1734 } 1735 } 1736 1737 this.point1.setPositionDirectly(JXG.COORDS_BY_USER, newPosP1); 1738 this.point2.setPositionDirectly(JXG.COORDS_BY_USER, newPosP2); 1739 1740 // Set position of tick labels 1741 visLabel = this.defaultTicks.visProp.label; 1742 if (ticksAutoPos && (horizontal || vertical)) { 1743 1744 if (!Type.exists(visLabel._anchorx_org)) { 1745 visLabel._anchorx_org = Type.def(visLabel.anchorx, this.board.options.text.anchorX); 1746 } 1747 if (!Type.exists(visLabel._anchory_org)) { 1748 visLabel._anchory_org = Type.def(visLabel.anchory, this.board.options.text.anchorY); 1749 } 1750 if (!Type.exists(visLabel._offset_org)) { 1751 visLabel._offset_org = visLabel.offset.slice(); 1752 } 1753 1754 off = visLabel.offset; 1755 if (horizontal) { 1756 dist = axis.point1.coords.scrCoords[2] - (this.board.canvasHeight * 0.5); 1757 1758 anchr = visLabel.anchory; 1759 1760 // The last position of the labels is stored in visLabel._side 1761 if (dist < 0 && Math.abs(dist) > ticksAutoPosThres) { 1762 // Put labels on top of the line 1763 if (visLabel._side === 'bottom') { 1764 // Switch position 1765 if (visLabel.anchory === 'top') { 1766 anchr = 'bottom'; 1767 } 1768 off[1] *= -1; 1769 visLabel._side = 'top'; 1770 } 1771 1772 } else if (dist > 0 && Math.abs(dist) > ticksAutoPosThres) { 1773 // Put labels below the line 1774 if (visLabel._side === 'top') { 1775 // Switch position 1776 if (visLabel.anchory === 'bottom') { 1777 anchr = 'top'; 1778 } 1779 off[1] *= -1; 1780 visLabel._side = 'bottom'; 1781 } 1782 1783 } else { 1784 // Put to original position 1785 anchr = visLabel._anchory_org; 1786 off = visLabel._offset_org.slice(); 1787 1788 if (anchr === 'top') { 1789 visLabel._side = 'bottom'; 1790 } else if (anchr === 'bottom') { 1791 visLabel._side = 'top'; 1792 } else if (off[1] < 0) { 1793 visLabel._side = 'bottom'; 1794 } else { 1795 visLabel._side = 'top'; 1796 } 1797 } 1798 1799 for (i = 0; i < axis.defaultTicks.labels.length; i++) { 1800 this.defaultTicks.labels[i].visProp.anchory = anchr; 1801 } 1802 visLabel.anchory = anchr; 1803 1804 } else if (vertical) { 1805 dist = axis.point1.coords.scrCoords[1] - (this.board.canvasWidth * 0.5); 1806 1807 if (dist < 0 && Math.abs(dist) > ticksAutoPosThres) { 1808 // Put labels to the left of the line 1809 if (visLabel._side === 'right') { 1810 // Switch position 1811 if (visLabel.anchorx === 'left') { 1812 anchr = 'right'; 1813 } 1814 off[0] *= -1; 1815 visLabel._side = 'left'; 1816 } 1817 1818 } else if (dist > 0 && Math.abs(dist) > ticksAutoPosThres) { 1819 // Put labels to the right of the line 1820 if (visLabel._side === 'left') { 1821 // Switch position 1822 if (visLabel.anchorx === 'right') { 1823 anchr = 'left'; 1824 } 1825 off[0] *= -1; 1826 visLabel._side = 'right'; 1827 } 1828 1829 } else { 1830 // Put to original position 1831 anchr = visLabel._anchorx_org; 1832 off = visLabel._offset_org.slice(); 1833 1834 if (anchr === 'left') { 1835 visLabel._side = 'right'; 1836 } else if (anchr === 'right') { 1837 visLabel._side = 'left'; 1838 } else if (off[0] < 0) { 1839 visLabel._side = 'left'; 1840 } else { 1841 visLabel._side = 'right'; 1842 } 1843 } 1844 1845 for (i = 0; i < axis.defaultTicks.labels.length; i++) { 1846 this.defaultTicks.labels[i].visProp.anchorx = anchr; 1847 } 1848 visLabel.anchorx = anchr; 1849 } 1850 visLabel.offset = off; 1851 1852 } else { 1853 delete visLabel._anchorx_org; 1854 delete visLabel._anchory_org; 1855 delete visLabel._offset_org; 1856 } 1857 1858 JXG.Line.prototype.update.call(this); 1859 this.defaultTicks.needsUpdate = true; 1860 1861 return this; 1862 }; 1863 1864 return axis; 1865 }; 1866 1867 JXG.registerElement("axis", JXG.createAxis); 1868 1869 /** 1870 * @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 1871 * by a glider on a line, circle, or curve and describes the tangent in the glider point on that line, circle, or curve. 1872 * @pseudo 1873 * @name Tangent 1874 * @augments JXG.Line 1875 * @constructor 1876 * @type JXG.Line 1877 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1878 * @param {Glider} g A glider on a line, circle, or curve. 1879 * @example 1880 * // Create a tangent providing a glider on a function graph 1881 * var c1 = board.create('curve', [function(t){return t},function(t){return t*t*t;}]); 1882 * var g1 = board.create('glider', [0.6, 1.2, c1]); 1883 * var t1 = board.create('tangent', [g1]); 1884 * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-4018d0d17a98" style="width: 400px; height: 400px;"></div> 1885 * <script type="text/javascript"> 1886 * var tlex1_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-4018d0d17a98', {boundingbox: [-6, 6, 6, -6], axis: true, showcopyright: false, shownavigation: false}); 1887 * var tlex1_c1 = tlex1_board.create('curve', [function(t){return t},function(t){return t*t*t;}]); 1888 * var tlex1_g1 = tlex1_board.create('glider', [0.6, 1.2, tlex1_c1]); 1889 * var tlex1_t1 = tlex1_board.create('tangent', [tlex1_g1]); 1890 * </script><pre> 1891 */ 1892 JXG.createTangent = function (board, parents, attributes) { 1893 var p, c, j, el, tangent, attr, 1894 getCurveTangentDir; 1895 1896 // One argument: glider on line, circle or curve 1897 if (parents.length === 1) { 1898 p = parents[0]; 1899 c = p.slideObject; 1900 // Two arguments: (point,F"|conic) or (line|curve|circle|conic,point). // Not yet: curve! 1901 } else if (parents.length === 2) { 1902 // In fact, for circles and conics it is the polar 1903 if (Type.isPoint(parents[0])) { 1904 p = parents[0]; 1905 c = parents[1]; 1906 } else if (Type.isPoint(parents[1])) { 1907 c = parents[0]; 1908 p = parents[1]; 1909 } else { 1910 throw new Error( 1911 "JSXGraph: Can't create tangent with parent types '" + 1912 typeof parents[0] + 1913 "' and '" + 1914 typeof parents[1] + 1915 "'." + 1916 "\nPossible parent types: [glider], [point,line|curve|circle|conic]" 1917 ); 1918 } 1919 } else { 1920 throw new Error( 1921 "JSXGraph: Can't create tangent with parent types '" + 1922 typeof parents[0] + 1923 "' and '" + 1924 typeof parents[1] + 1925 "'." + 1926 "\nPossible parent types: [glider], [point,line|curve|circle|conic]" 1927 ); 1928 } 1929 1930 attr = Type.copyAttributes(attributes, board.options, 'tangent'); 1931 if (c.elementClass === Const.OBJECT_CLASS_LINE) { 1932 tangent = board.create("line", [c.point1, c.point2], attr); 1933 tangent.glider = p; 1934 } else if ( 1935 c.elementClass === Const.OBJECT_CLASS_CURVE && 1936 c.type !== Const.OBJECT_TYPE_CONIC 1937 ) { 1938 if (Type.evaluate(c.visProp.curvetype) !== "plot") { 1939 tangent = board.create( 1940 "line", 1941 [ 1942 function () { 1943 var g = c.X, 1944 f = c.Y; 1945 return ( 1946 -p.X() * Numerics.D(f)(p.position) + 1947 p.Y() * Numerics.D(g)(p.position) 1948 ); 1949 }, 1950 function () { 1951 return Numerics.D(c.Y)(p.position); 1952 }, 1953 function () { 1954 return -Numerics.D(c.X)(p.position); 1955 } 1956 ], 1957 attr 1958 ); 1959 1960 p.addChild(tangent); 1961 // this is required for the geogebra reader to display a slope 1962 tangent.glider = p; 1963 } else { 1964 // curveType 'plot' 1965 /** 1966 * @ignore 1967 * 1968 * In case of bezierDegree == 1: 1969 * Find two points p1, p2 enclosing the glider. 1970 * Then the equation of the line segment is: 0 = y*(x1-x2) + x*(y2-y1) + y1*x2-x1*y2, 1971 * which is the cross product of p1 and p2. 1972 * 1973 * In case of bezierDegree === 3: 1974 * The slope dy / dx of the tangent is determined. Then the 1975 * tangent is computed as cross product between 1976 * the glider p and [1, p.X() + dx, p.Y() + dy] 1977 * 1978 */ 1979 getCurveTangentDir = function (position, curve, num) { 1980 var i = Math.floor(position), 1981 p1, p2, t, A, B, C, D, dx, dy, d, 1982 points, le; 1983 1984 if (curve.bezierDegree === 1) { 1985 if (i === curve.numberPoints - 1) { 1986 i--; 1987 } 1988 } else if (curve.bezierDegree === 3) { 1989 // i is start of the Bezier segment 1990 // t is the position in the Bezier segment 1991 if (curve.elType === 'sector') { 1992 points = curve.points.slice(3, curve.numberPoints - 3); 1993 le = points.length; 1994 } else { 1995 points = curve.points; 1996 le = points.length; 1997 } 1998 i = Math.floor((position * (le - 1)) / 3) * 3; 1999 t = (position * (le - 1) - i) / 3; 2000 if (i >= le - 1) { 2001 i = le - 4; 2002 t = 1; 2003 } 2004 } else { 2005 return 0; 2006 } 2007 2008 if (i < 0) { 2009 return 1; 2010 } 2011 2012 // The curve points are transformed (if there is a transformation) 2013 // c.X(i) is not transformed. 2014 if (curve.bezierDegree === 1) { 2015 p1 = curve.points[i].usrCoords; 2016 p2 = curve.points[i + 1].usrCoords; 2017 } else { 2018 A = points[i].usrCoords; 2019 B = points[i + 1].usrCoords; 2020 C = points[i + 2].usrCoords; 2021 D = points[i + 3].usrCoords; 2022 dx = 2023 (1 - t) * (1 - t) * (B[1] - A[1]) + 2024 2 * (1 - t) * t * (C[1] - B[1]) + 2025 t * t * (D[1] - C[1]); 2026 dy = 2027 (1 - t) * (1 - t) * (B[2] - A[2]) + 2028 2 * (1 - t) * t * (C[2] - B[2]) + 2029 t * t * (D[2] - C[2]); 2030 d = Mat.hypot(dx, dy); 2031 dx /= d; 2032 dy /= d; 2033 p1 = p.coords.usrCoords; 2034 p2 = [1, p1[1] + dx, p1[2] + dy]; 2035 } 2036 2037 switch (num) { 2038 case 0: 2039 return p1[2] * p2[1] - p1[1] * p2[2]; 2040 case 1: 2041 return p2[2] - p1[2]; 2042 case 2: 2043 return p1[1] - p2[1]; 2044 } 2045 return 0; 2046 }; 2047 2048 tangent = board.create( 2049 "line", 2050 [ 2051 function () { 2052 return getCurveTangentDir(p.position, c, 0); 2053 }, 2054 function () { 2055 return getCurveTangentDir(p.position, c, 1); 2056 }, 2057 function () { 2058 return getCurveTangentDir(p.position, c, 2); 2059 } 2060 ], 2061 attr 2062 ); 2063 2064 p.addChild(tangent); 2065 // this is required for the geogebra reader to display a slope 2066 tangent.glider = p; 2067 } 2068 } else if (c.type === Const.OBJECT_TYPE_TURTLE) { 2069 tangent = board.create( 2070 "line", 2071 [ 2072 function () { 2073 var i = Math.floor(p.position); 2074 2075 // run through all curves of this turtle 2076 for (j = 0; j < c.objects.length; j++) { 2077 el = c.objects[j]; 2078 2079 if (el.type === Const.OBJECT_TYPE_CURVE) { 2080 if (i < el.numberPoints) { 2081 break; 2082 } 2083 2084 i -= el.numberPoints; 2085 } 2086 } 2087 2088 if (i === el.numberPoints - 1) { 2089 i--; 2090 } 2091 2092 if (i < 0) { 2093 return 1; 2094 } 2095 2096 return el.Y(i) * el.X(i + 1) - el.X(i) * el.Y(i + 1); 2097 }, 2098 function () { 2099 var i = Math.floor(p.position); 2100 2101 // run through all curves of this turtle 2102 for (j = 0; j < c.objects.length; j++) { 2103 el = c.objects[j]; 2104 2105 if (el.type === Const.OBJECT_TYPE_CURVE) { 2106 if (i < el.numberPoints) { 2107 break; 2108 } 2109 2110 i -= el.numberPoints; 2111 } 2112 } 2113 2114 if (i === el.numberPoints - 1) { 2115 i--; 2116 } 2117 if (i < 0) { 2118 return 0; 2119 } 2120 2121 return el.Y(i + 1) - el.Y(i); 2122 }, 2123 function () { 2124 var i = Math.floor(p.position); 2125 2126 // run through all curves of this turtle 2127 for (j = 0; j < c.objects.length; j++) { 2128 el = c.objects[j]; 2129 if (el.type === Const.OBJECT_TYPE_CURVE) { 2130 if (i < el.numberPoints) { 2131 break; 2132 } 2133 i -= el.numberPoints; 2134 } 2135 } 2136 if (i === el.numberPoints - 1) { 2137 i--; 2138 } 2139 2140 if (i < 0) { 2141 return 0; 2142 } 2143 2144 return el.X(i) - el.X(i + 1); 2145 } 2146 ], 2147 attr 2148 ); 2149 p.addChild(tangent); 2150 2151 // this is required for the geogebra reader to display a slope 2152 tangent.glider = p; 2153 } else if ( 2154 c.elementClass === Const.OBJECT_CLASS_CIRCLE || 2155 c.type === Const.OBJECT_TYPE_CONIC 2156 ) { 2157 // If p is not on c, the tangent is the polar. 2158 // This construction should work on conics, too. p has to lie on c. 2159 tangent = board.create( 2160 "line", 2161 [ 2162 function () { 2163 return Mat.matVecMult(c.quadraticform, p.coords.usrCoords)[0]; 2164 }, 2165 function () { 2166 return Mat.matVecMult(c.quadraticform, p.coords.usrCoords)[1]; 2167 }, 2168 function () { 2169 return Mat.matVecMult(c.quadraticform, p.coords.usrCoords)[2]; 2170 } 2171 ], 2172 attr 2173 ); 2174 2175 p.addChild(tangent); 2176 // this is required for the geogebra reader to display a slope 2177 tangent.glider = p; 2178 } 2179 2180 if (!Type.exists(tangent)) { 2181 throw new Error("JSXGraph: Couldn't create tangent with the given parents."); 2182 } 2183 2184 tangent.elType = "tangent"; 2185 tangent.type = Const.OBJECT_TYPE_TANGENT; 2186 tangent.setParents(parents); 2187 2188 return tangent; 2189 }; 2190 2191 /** 2192 * @class This element is used to provide a constructor for the radical axis with respect to two circles with distinct centers. 2193 * The angular bisector of the polar lines of the circle centers with respect to the other circle is always the radical axis. 2194 * The radical axis passes through the intersection points when the circles intersect. 2195 * 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. 2196 * @pseudo 2197 * @name RadicalAxis 2198 * @augments JXG.Line 2199 * @constructor 2200 * @type JXG.Line 2201 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 2202 * @param {JXG.Circle} circle Circle one of the two respective circles. 2203 * @param {JXG.Circle} circle Circle the other of the two respective circles. 2204 * @example 2205 * // Create the radical axis line with respect to two circles 2206 * var board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-5018d0d17a98', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 2207 * var p1 = board.create('point', [2, 3]); 2208 * var p2 = board.create('point', [1, 4]); 2209 * var c1 = board.create('circle', [p1, p2]); 2210 * var p3 = board.create('point', [6, 5]); 2211 * var p4 = board.create('point', [8, 6]); 2212 * var c2 = board.create('circle', [p3, p4]); 2213 * var r1 = board.create('radicalaxis', [c1, c2]); 2214 * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-5018d0d17a98" class="jxgbox" style="width:400px; height:400px;"></div> 2215 * <script type='text/javascript'> 2216 * var rlex1_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-5018d0d17a98', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 2217 * var rlex1_p1 = rlex1_board.create('point', [2, 3]); 2218 * var rlex1_p2 = rlex1_board.create('point', [1, 4]); 2219 * var rlex1_c1 = rlex1_board.create('circle', [rlex1_p1, rlex1_p2]); 2220 * var rlex1_p3 = rlex1_board.create('point', [6, 5]); 2221 * var rlex1_p4 = rlex1_board.create('point', [8, 6]); 2222 * var rlex1_c2 = rlex1_board.create('circle', [rlex1_p3, rlex1_p4]); 2223 * var rlex1_r1 = rlex1_board.create('radicalaxis', [rlex1_c1, rlex1_c2]); 2224 * </script><pre> 2225 */ 2226 JXG.createRadicalAxis = function (board, parents, attributes) { 2227 var el, el1, el2; 2228 2229 if ( 2230 parents.length !== 2 || 2231 parents[0].elementClass !== Const.OBJECT_CLASS_CIRCLE || 2232 parents[1].elementClass !== Const.OBJECT_CLASS_CIRCLE 2233 ) { 2234 // Failure 2235 throw new Error( 2236 "JSXGraph: Can't create 'radical axis' with parent types '" + 2237 typeof parents[0] + 2238 "' and '" + 2239 typeof parents[1] + 2240 "'." + 2241 "\nPossible parent type: [circle,circle]" 2242 ); 2243 } 2244 2245 el1 = board.select(parents[0]); 2246 el2 = board.select(parents[1]); 2247 2248 el = board.create( 2249 "line", 2250 [ 2251 function () { 2252 var a = el1.stdform, 2253 b = el2.stdform; 2254 2255 return Mat.matVecMult(Mat.transpose([a.slice(0, 3), b.slice(0, 3)]), [ 2256 b[3], 2257 -a[3] 2258 ]); 2259 } 2260 ], 2261 attributes 2262 ); 2263 2264 el.elType = "radicalaxis"; 2265 el.setParents([el1.id, el2.id]); 2266 2267 el1.addChild(el); 2268 el2.addChild(el); 2269 2270 return el; 2271 }; 2272 2273 /** 2274 * @class This element is used to provide a constructor for the polar line of a point with respect to a conic or a circle. 2275 * @pseudo 2276 * @description The polar line is the unique reciprocal relationship of a point with respect to a conic. 2277 * 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. 2278 * 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. 2279 * See {@link https://en.wikipedia.org/wiki/Pole_and_polar} for more information on pole and polar. 2280 * @name PolarLine 2281 * @augments JXG.Line 2282 * @constructor 2283 * @type JXG.Line 2284 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 2285 * @param {JXG.Conic,JXG.Circle_JXG.Point} el1,el2 or 2286 * @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. 2287 * @example 2288 * // Create the polar line of a point with respect to a conic 2289 * var p1 = board.create('point', [-1, 2]); 2290 * var p2 = board.create('point', [ 1, 4]); 2291 * var p3 = board.create('point', [-1,-2]); 2292 * var p4 = board.create('point', [ 0, 0]); 2293 * var p5 = board.create('point', [ 4,-2]); 2294 * var c1 = board.create('conic',[p1,p2,p3,p4,p5]); 2295 * var p6 = board.create('point', [-1, 1]); 2296 * var l1 = board.create('polarline', [c1, p6]); 2297 * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-6018d0d17a98" class="jxgbox" style="width:400px; height:400px;"></div> 2298 * <script type='text/javascript'> 2299 * var plex1_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-6018d0d17a98', {boundingbox: [-3, 5, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 2300 * var plex1_p1 = plex1_board.create('point', [-1, 2]); 2301 * var plex1_p2 = plex1_board.create('point', [ 1, 4]); 2302 * var plex1_p3 = plex1_board.create('point', [-1,-2]); 2303 * var plex1_p4 = plex1_board.create('point', [ 0, 0]); 2304 * var plex1_p5 = plex1_board.create('point', [ 4,-2]); 2305 * var plex1_c1 = plex1_board.create('conic',[plex1_p1,plex1_p2,plex1_p3,plex1_p4,plex1_p5]); 2306 * var plex1_p6 = plex1_board.create('point', [-1, 1]); 2307 * var plex1_l1 = plex1_board.create('polarline', [plex1_c1, plex1_p6]); 2308 * </script><pre> 2309 * @example 2310 * // Create the polar line of a point with respect to a circle. 2311 * var p1 = board.create('point', [ 1, 1]); 2312 * var p2 = board.create('point', [ 2, 3]); 2313 * var c1 = board.create('circle',[p1,p2]); 2314 * var p3 = board.create('point', [ 6, 6]); 2315 * var l1 = board.create('polarline', [c1, p3]); 2316 * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-7018d0d17a98" class="jxgbox" style="width:400px; height:400px;"></div> 2317 * <script type='text/javascript'> 2318 * var plex2_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-7018d0d17a98', {boundingbox: [-3, 7, 7, -3], axis: true, showcopyright: false, shownavigation: false}); 2319 * var plex2_p1 = plex2_board.create('point', [ 1, 1]); 2320 * var plex2_p2 = plex2_board.create('point', [ 2, 3]); 2321 * var plex2_c1 = plex2_board.create('circle',[plex2_p1,plex2_p2]); 2322 * var plex2_p3 = plex2_board.create('point', [ 6, 6]); 2323 * var plex2_l1 = plex2_board.create('polarline', [plex2_c1, plex2_p3]); 2324 * </script><pre> 2325 */ 2326 JXG.createPolarLine = function (board, parents, attributes) { 2327 var el, 2328 el1, 2329 el2, 2330 firstParentIsConic, 2331 secondParentIsConic, 2332 firstParentIsPoint, 2333 secondParentIsPoint; 2334 2335 if (parents.length > 1) { 2336 firstParentIsConic = 2337 parents[0].type === Const.OBJECT_TYPE_CONIC || 2338 parents[0].elementClass === Const.OBJECT_CLASS_CIRCLE; 2339 secondParentIsConic = 2340 parents[1].type === Const.OBJECT_TYPE_CONIC || 2341 parents[1].elementClass === Const.OBJECT_CLASS_CIRCLE; 2342 2343 firstParentIsPoint = Type.isPoint(parents[0]); 2344 secondParentIsPoint = Type.isPoint(parents[1]); 2345 } 2346 2347 if ( 2348 parents.length !== 2 || 2349 !( 2350 (firstParentIsConic && secondParentIsPoint) || 2351 (firstParentIsPoint && secondParentIsConic) 2352 ) 2353 ) { 2354 // Failure 2355 throw new Error( 2356 "JSXGraph: Can't create 'polar line' with parent types '" + 2357 typeof parents[0] + 2358 "' and '" + 2359 typeof parents[1] + 2360 "'." + 2361 "\nPossible parent type: [conic|circle,point], [point,conic|circle]" 2362 ); 2363 } 2364 2365 if (secondParentIsPoint) { 2366 el1 = board.select(parents[0]); 2367 el2 = board.select(parents[1]); 2368 } else { 2369 el1 = board.select(parents[1]); 2370 el2 = board.select(parents[0]); 2371 } 2372 2373 // Polar lines have been already provided in the tangent element. 2374 el = board.create("tangent", [el1, el2], attributes); 2375 2376 el.elType = "polarline"; 2377 return el; 2378 }; 2379 2380 /** 2381 * Register the element type tangent at JSXGraph 2382 * @private 2383 */ 2384 JXG.registerElement("tangent", JXG.createTangent); 2385 JXG.registerElement("polar", JXG.createTangent); 2386 JXG.registerElement("radicalaxis", JXG.createRadicalAxis); 2387 JXG.registerElement("polarline", JXG.createPolarLine); 2388 2389 export default JXG.Line; 2390 // export default { 2391 // Line: JXG.Line, 2392 // createLine: JXG.createLine, 2393 // createTangent: JXG.createTangent, 2394 // createPolar: JXG.createTangent, 2395 // createSegment: JXG.createSegment, 2396 // createAxis: JXG.createAxis, 2397 // createArrow: JXG.createArrow, 2398 // createRadicalAxis: JXG.createRadicalAxis, 2399 // createPolarLine: JXG.createPolarLine 2400 // }; 2401