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