1 /* 2 Copyright 2008-2025 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 = this.evalVisProp('strokewidth'); 177 178 if (Type.isObject(this.evalVisProp('precision'))) { 179 type = this.board._inputDevice; 180 prec = this.evalVisProp('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 this.evalVisProp('straightfirst') && 201 this.evalVisProp('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 (!this.evalVisProp('straightfirst') && pos < 0) { 267 return false; 268 } 269 270 return !(!this.evalVisProp('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 (this.evalVisProp('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 = (this.evalVisProp('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 !this.point1.evalVisProp('fixed'); 354 drag2 = 355 this.point2.isDraggable && 356 this.point2.type !== Const.OBJECT_TYPE_GLIDER && 357 !this.point2.evalVisProp('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 Line#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 Line#straightFirst 619 * @see Line#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 = this.evalVisProp('straightfirst'), 665 ev_sl = this.evalVisProp('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 = this.label.evalVisProp('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] + Mat.eps) { 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] + Mat.eps) { 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 * this.label.evalVisProp('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 = this.label.evalVisProp('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 = Type.getCloneObject(this), 794 r, s, 795 er; 796 797 copy.point1 = this.point1; 798 copy.point2 = this.point2; 799 copy.stdform = this.stdform; 800 801 s = this.getSlope(); 802 r = this.getRise(); 803 copy.getSlope = function () { 804 return s; 805 }; 806 copy.getRise = function () { 807 return r; 808 }; 809 810 er = this.board.renderer.enhancedRendering; 811 this.board.renderer.enhancedRendering = true; 812 this.board.renderer.drawLine(copy); 813 this.board.renderer.enhancedRendering = er; 814 this.traces[copy.id] = copy.rendNode; 815 816 return this; 817 }, 818 819 /** 820 * Add transformations to this line. 821 * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of 822 * {@link JXG.Transformation}s. 823 * @returns {JXG.Line} Reference to this line object. 824 */ 825 addTransform: function (transform) { 826 var i, 827 list = Type.isArray(transform) ? transform : [transform], 828 len = list.length; 829 830 for (i = 0; i < len; i++) { 831 this.point1.transformations.push(list[i]); 832 this.point2.transformations.push(list[i]); 833 } 834 835 // Why not like this? 836 // The difference is in setting baseElement 837 // var list = Type.isArray(transform) ? transform : [transform]; 838 // this.point1.addTransform(this, list); 839 // this.point2.addTransform(this, list); 840 841 return this; 842 }, 843 844 // see GeometryElement.js 845 snapToGrid: function (pos) { 846 var c1, c2, dc, t, ticks, x, y, sX, sY; 847 848 if (this.evalVisProp('snaptogrid')) { 849 if (this.parents.length < 3) { 850 // Line through two points 851 this.point1.handleSnapToGrid(true, true); 852 this.point2.handleSnapToGrid(true, true); 853 } else if (Type.exists(pos)) { 854 // Free line 855 sX = this.evalVisProp('snapsizex'); 856 sY = this.evalVisProp('snapsizey'); 857 858 c1 = new Coords(Const.COORDS_BY_SCREEN, [pos.Xprev, pos.Yprev], this.board); 859 860 x = c1.usrCoords[1]; 861 y = c1.usrCoords[2]; 862 863 if ( 864 sX <= 0 && 865 this.board.defaultAxes && 866 this.board.defaultAxes.x.defaultTicks 867 ) { 868 ticks = this.board.defaultAxes.x.defaultTicks; 869 sX = ticks.ticksDelta * (ticks.evalVisProp('minorticks') + 1); 870 } 871 if ( 872 sY <= 0 && 873 this.board.defaultAxes && 874 this.board.defaultAxes.y.defaultTicks 875 ) { 876 ticks = this.board.defaultAxes.y.defaultTicks; 877 sY = ticks.ticksDelta * (ticks.evalVisProp('minorticks') + 1); 878 } 879 880 // if no valid snap sizes are available, don't change the coords. 881 if (sX > 0 && sY > 0) { 882 // projectCoordsToLine 883 /* 884 v = [0, this.stdform[1], this.stdform[2]]; 885 v = Mat.crossProduct(v, c1.usrCoords); 886 c2 = Geometry.meetLineLine(v, this.stdform, 0, this.board); 887 */ 888 c2 = Geometry.projectPointToLine({ coords: c1 }, this, this.board); 889 890 dc = Statistics.subtract( 891 [1, Math.round(x / sX) * sX, Math.round(y / sY) * sY], 892 c2.usrCoords 893 ); 894 t = this.board.create("transform", dc.slice(1), { 895 type: "translate" 896 }); 897 t.applyOnce([this.point1, this.point2]); 898 } 899 } 900 } else { 901 this.point1.handleSnapToGrid(false, true); 902 this.point2.handleSnapToGrid(false, true); 903 } 904 905 return this; 906 }, 907 908 // see element.js 909 snapToPoints: function () { 910 var forceIt = this.evalVisProp('snaptopoints'); 911 912 if (this.parents.length < 3) { 913 // Line through two points 914 this.point1.handleSnapToPoints(forceIt); 915 this.point2.handleSnapToPoints(forceIt); 916 } 917 918 return this; 919 }, 920 921 /** 922 * Treat the line as parametric curve in homogeneous coordinates, where the parameter t runs from 0 to 1. 923 * First we transform the interval [0,1] to [-1,1]. 924 * If the line has homogeneous coordinates [c, a, b] = stdform[] then the direction of the line is [b, -a]. 925 * Now, we take one finite point that defines the line, i.e. we take either point1 or point2 926 * (in case the line is not the ideal line). 927 * Let the coordinates of that point be [z, x, y]. 928 * Then, the curve runs linearly from 929 * [0, b, -a] (t=-1) to [z, x, y] (t=0) 930 * and 931 * [z, x, y] (t=0) to [0, -b, a] (t=1) 932 * 933 * @param {Number} t Parameter running from 0 to 1. 934 * @returns {Number} X(t) x-coordinate of the line treated as parametric curve. 935 * */ 936 X: function (t) { 937 // var x, 938 // c = this.point1.coords.usrCoords, 939 // b = this.stdform[2]; 940 941 // x = (Math.abs(c[0]) > Mat.eps) ? c[1] : c[1]; 942 // t = (t - 0.5) * 2; 943 944 // return (1 - Math.abs(t)) * x - t * b; 945 946 var c1 = this.point1.coords.usrCoords, 947 c2 = this.point2.coords.usrCoords, 948 b = this.stdform[2]; 949 950 if (c1[0] !== 0) { 951 if (c2[0] !== 0) { 952 return c1[1] + (c2[1] - c1[1]) * t; 953 } else { 954 return c1[1] + b * 1.e5 * t; 955 } 956 } else { 957 if (c1[0] !== 0) { 958 return c2[1] - (c1[1] - c2[1]) * t; 959 } else { 960 return c2[1] + b * 1.e5 * t; 961 } 962 } 963 }, 964 965 /** 966 * Treat the line as parametric curve in homogeneous coordinates. 967 * See {@link JXG.Line#X} for a detailed description. 968 * @param {Number} t Parameter running from 0 to 1. 969 * @returns {Number} Y(t) y-coordinate of the line treated as parametric curve. 970 * @see Line#X 971 */ 972 Y: function (t) { 973 // var y, 974 // c = this.point1.coords.usrCoords, 975 // a = this.stdform[1]; 976 977 // y = (Math.abs(c[0]) > Mat.eps) ? c[2] : c[2]; 978 // t = (t - 0.5) * 2; 979 980 // return (1 - Math.abs(t)) * y + t * a; 981 982 var c1 = this.point1.coords.usrCoords, 983 c2 = this.point2.coords.usrCoords, 984 a = this.stdform[1]; 985 986 if (c1[0] !== 0) { 987 if (c2[0] !== 0) { 988 return c1[2] + (c2[2] - c1[2]) * t; 989 } else { 990 return c1[2] - a * 1.e5 * t; 991 } 992 } else { 993 if (c1[0] !== 0) { 994 return c2[2] - (c1[2] - c2[2]) * t; 995 } else { 996 return c2[2] - a * 1.e5 * t; 997 } 998 } 999 }, 1000 1001 /** 1002 * Treat the line as parametric curve in homogeneous coordinates. 1003 * See {@link JXG.Line#X} for a detailed description. 1004 * 1005 * @param {Number} t Parameter running from 0 to 1. 1006 * @returns {Number} Z(t) z-coordinate of the line treated as parametric curve. 1007 * @see Line#Z 1008 */ 1009 Z: function (t) { 1010 // var z, 1011 // c = this.point1.coords.usrCoords; 1012 1013 // z = (Math.abs(c[0]) > Mat.eps) ? c[0] : c[0]; 1014 // t = (t - 0.5) * 2; 1015 1016 // return (1 - Math.abs(t)) * z; 1017 1018 var c1 = this.point1.coords.usrCoords, 1019 c2 = this.point2.coords.usrCoords; 1020 1021 if (t === 1 && c1[0] * c2[0] === 0) { 1022 return 0; 1023 } 1024 return 1; 1025 }, 1026 1027 /** 1028 * Return the homogeneous coordinates of the line treated as curve at t - including all transformations 1029 * applied to the curve. 1030 * @param {Number} t A number 1031 * @returns {Array} [Z(t), X(t), Y(t)] 1032 * @see Line#X 1033 */ 1034 Ft: function(t) { 1035 var c = [this.Z(t), this.X(t), this.Y(t)]; 1036 c[1] /= c[0]; 1037 c[2] /= c[0]; 1038 c[0] /= c[0]; 1039 // c[0] = 1; 1040 // c[1] = t; 1041 // c[2] = 3; 1042 1043 return c; 1044 }, 1045 1046 /** 1047 * The distance between the two points defining the line. 1048 * @returns {Number} 1049 */ 1050 L: function () { 1051 return this.point1.Dist(this.point2); 1052 }, 1053 1054 /** 1055 * Set a new fixed length, then update the board. 1056 * @param {String|Number|function} l A string, function or number describing the new length. 1057 * @returns {JXG.Line} Reference to this line 1058 */ 1059 setFixedLength: function (l) { 1060 if (!this.hasFixedLength) { 1061 return this; 1062 } 1063 1064 this.fixedLength = Type.createFunction(l, this.board); 1065 this.hasFixedLength = true; 1066 this.addParentsFromJCFunctions([this.fixedLength]); 1067 this.board.update(); 1068 1069 return this; 1070 }, 1071 1072 /** 1073 * Treat the element as a parametric curve 1074 * @private 1075 */ 1076 minX: function () { 1077 return 0.0; 1078 }, 1079 1080 /** 1081 * Treat the element as parametric curve 1082 * @private 1083 */ 1084 maxX: function () { 1085 return 1.0; 1086 }, 1087 1088 // documented in geometry element 1089 bounds: function () { 1090 var p1c = this.point1.coords.usrCoords, 1091 p2c = this.point2.coords.usrCoords; 1092 1093 return [ 1094 Math.min(p1c[1], p2c[1]), 1095 Math.max(p1c[2], p2c[2]), 1096 Math.max(p1c[1], p2c[1]), 1097 Math.min(p1c[2], p2c[2]) 1098 ]; 1099 }, 1100 1101 // documented in GeometryElement.js 1102 remove: function () { 1103 this.removeAllTicks(); 1104 GeometryElement.prototype.remove.call(this); 1105 } 1106 1107 // hideElement: function () { 1108 // var i; 1109 // 1110 // GeometryElement.prototype.hideElement.call(this); 1111 // 1112 // for (i = 0; i < this.ticks.length; i++) { 1113 // this.ticks[i].hideElement(); 1114 // } 1115 // }, 1116 // 1117 // showElement: function () { 1118 // var i; 1119 // GeometryElement.prototype.showElement.call(this); 1120 // 1121 // for (i = 0; i < this.ticks.length; i++) { 1122 // this.ticks[i].showElement(); 1123 // } 1124 // } 1125 1126 } 1127 ); 1128 1129 /** 1130 * @class A general line is given by two points or three coordinates. 1131 * By setting additional properties a line can be used as an arrow and/or axis. 1132 * @pseudo 1133 * @name Line 1134 * @augments JXG.Line 1135 * @constructor 1136 * @type JXG.Line 1137 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1138 * @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 1139 * numbers describing the coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 1140 * It is possible to provide a function returning an array or a point, instead of providing an array or a point. 1141 * @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 1142 * 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. 1143 * It is possible to provide three functions returning numbers, too. 1144 * @param {function} f This function must return an array containing three numbers forming the line's homogeneous coordinates. 1145 * <p> 1146 * Additionally, a line can be created by providing a line and a transformation (or an array of transformations). 1147 * Then, the result is a line which is the transformation of the supplied line. 1148 * @example 1149 * // Create a line using point and coordinates/ 1150 * // The second point will be fixed and invisible. 1151 * var p1 = board.create('point', [4.5, 2.0]); 1152 * var l1 = board.create('line', [p1, [1.0, 1.0]]); 1153 * </pre><div class="jxgbox" id="JXGc0ae3461-10c4-4d39-b9be-81d74759d122" style="width: 300px; height: 300px;"></div> 1154 * <script type="text/javascript"> 1155 * var glex1_board = JXG.JSXGraph.initBoard('JXGc0ae3461-10c4-4d39-b9be-81d74759d122', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1156 * var glex1_p1 = glex1_board.create('point', [4.5, 2.0]); 1157 * var glex1_l1 = glex1_board.create('line', [glex1_p1, [1.0, 1.0]]); 1158 * </script><pre> 1159 * @example 1160 * // Create a line using three coordinates 1161 * var l1 = board.create('line', [1.0, -2.0, 3.0]); 1162 * </pre><div class="jxgbox" id="JXGcf45e462-f964-4ba4-be3a-c9db94e2593f" style="width: 300px; height: 300px;"></div> 1163 * <script type="text/javascript"> 1164 * var glex2_board = JXG.JSXGraph.initBoard('JXGcf45e462-f964-4ba4-be3a-c9db94e2593f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1165 * var glex2_l1 = glex2_board.create('line', [1.0, -2.0, 3.0]); 1166 * </script><pre> 1167 * @example 1168 * // Create a line (l2) as reflection of another line (l1) 1169 * // reflection line 1170 * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'}); 1171 * var reflect = board.create('transform', [li], {type: 'reflect'}); 1172 * 1173 * var l1 = board.create('line', [1,-5,1]); 1174 * var l2 = board.create('line', [l1, reflect]); 1175 * 1176 * </pre><div id="JXGJXGa00d7dd6-d38c-11e7-93b3-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 1177 * <script type="text/javascript"> 1178 * (function() { 1179 * var board = JXG.JSXGraph.initBoard('JXGJXGa00d7dd6-d38c-11e7-93b3-901b0e1b8723', 1180 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1181 * // reflection line 1182 * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'}); 1183 * var reflect = board.create('transform', [li], {type: 'reflect'}); 1184 * 1185 * var l1 = board.create('line', [1,-5,1]); 1186 * var l2 = board.create('line', [l1, reflect]); 1187 * })(); 1188 * 1189 * </script><pre> 1190 * 1191 * @example 1192 * var t = board.create('transform', [2, 1.5], {type: 'scale'}); 1193 * var l1 = board.create('line', [1, -5, 1]); 1194 * var l2 = board.create('line', [l1, t]); 1195 * 1196 * </pre><div id="d16d5b58-6338-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 1197 * <script type="text/javascript"> 1198 * (function() { 1199 * var board = JXG.JSXGraph.initBoard('d16d5b58-6338-11e8-9fb9-901b0e1b8723', 1200 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1201 * var t = board.create('transform', [2, 1.5], {type: 'scale'}); 1202 * var l1 = board.create('line', [1, -5, 1]); 1203 * var l2 = board.create('line', [l1, t]); 1204 * 1205 * })(); 1206 * 1207 * </script><pre> 1208 * 1209 * @example 1210 * //create line between two points 1211 * var p1 = board.create('point', [0,0]); 1212 * var p2 = board.create('point', [2,2]); 1213 * var l1 = board.create('line', [p1,p2], {straightFirst:false, straightLast:false}); 1214 * </pre><div id="d21d5b58-6338-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 1215 * <script type="text/javascript"> 1216 * (function() { 1217 * var board = JXG.JSXGraph.initBoard('d21d5b58-6338-11e8-9fb9-901b0e1b8723', 1218 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1219 * var ex5p1 = board.create('point', [0,0]); 1220 * var ex5p2 = board.create('point', [2,2]); 1221 * var ex5l1 = board.create('line', [ex5p1,ex5p2], {straightFirst:false, straightLast:false}); 1222 * })(); 1223 * 1224 * </script><pre> 1225 */ 1226 JXG.createLine = function (board, parents, attributes) { 1227 var ps, el, p1, p2, i, attr, 1228 c = [], 1229 doTransform = false, 1230 constrained = false, 1231 isDraggable; 1232 1233 if (parents.length === 2) { 1234 // The line is defined by two points or coordinates of two points. 1235 // In the latter case, the points are created. 1236 attr = Type.copyAttributes(attributes, board.options, "line", 'point1'); 1237 if (Type.isArray(parents[0]) && parents[0].length > 1) { 1238 p1 = board.create("point", parents[0], attr); 1239 } else if (Type.isString(parents[0]) || Type.isPoint(parents[0])) { 1240 p1 = board.select(parents[0]); 1241 } else if (Type.isFunction(parents[0]) && Type.isPoint(parents[0]())) { 1242 p1 = parents[0](); 1243 constrained = true; 1244 } else if ( 1245 Type.isFunction(parents[0]) && 1246 parents[0]().length && 1247 parents[0]().length >= 2 1248 ) { 1249 p1 = JXG.createPoint(board, parents[0](), attr); 1250 constrained = true; 1251 } else if (Type.isObject(parents[0]) && Type.isTransformationOrArray(parents[1])) { 1252 doTransform = true; 1253 p1 = board.create("point", [parents[0].point1, parents[1]], attr); 1254 } else { 1255 throw new Error( 1256 "JSXGraph: Can't create line with parent types '" + 1257 typeof parents[0] + 1258 "' and '" + 1259 typeof parents[1] + 1260 "'." + 1261 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]" 1262 ); 1263 } 1264 1265 // point 2 given by coordinates 1266 attr = Type.copyAttributes(attributes, board.options, "line", 'point2'); 1267 if (doTransform) { 1268 p2 = board.create("point", [parents[0].point2, parents[1]], attr); 1269 } else if (Type.isArray(parents[1]) && parents[1].length > 1) { 1270 p2 = board.create("point", parents[1], attr); 1271 } else if (Type.isString(parents[1]) || Type.isPoint(parents[1])) { 1272 p2 = board.select(parents[1]); 1273 } else if (Type.isFunction(parents[1]) && Type.isPoint(parents[1]())) { 1274 p2 = parents[1](); 1275 constrained = true; 1276 } else if ( 1277 Type.isFunction(parents[1]) && 1278 parents[1]().length && 1279 parents[1]().length >= 2 1280 ) { 1281 p2 = JXG.createPoint(board, parents[1](), attr); 1282 constrained = true; 1283 } else { 1284 throw new Error( 1285 "JSXGraph: Can't create line with parent types '" + 1286 typeof parents[0] + 1287 "' and '" + 1288 typeof parents[1] + 1289 "'." + 1290 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]" 1291 ); 1292 } 1293 1294 attr = Type.copyAttributes(attributes, board.options, 'line'); 1295 el = new JXG.Line(board, p1, p2, attr); 1296 1297 if (constrained) { 1298 el.constrained = true; 1299 el.funp1 = parents[0]; 1300 el.funp2 = parents[1]; 1301 } else if (!doTransform) { 1302 el.isDraggable = true; 1303 } 1304 1305 //if (!el.constrained) { 1306 el.setParents([p1.id, p2.id]); 1307 //} 1308 1309 } else if (parents.length === 3) { 1310 // Free line: 1311 // Line is defined by three homogeneous coordinates. 1312 // Also in this case points are created. 1313 isDraggable = true; 1314 for (i = 0; i < 3; i++) { 1315 if (Type.isNumber(parents[i])) { 1316 // createFunction will just wrap a function around our constant number 1317 // that does nothing else but to return that number. 1318 c[i] = Type.createFunction(parents[i]); 1319 } else if (Type.isFunction(parents[i])) { 1320 c[i] = parents[i]; 1321 isDraggable = false; 1322 } else { 1323 throw new Error( 1324 "JSXGraph: Can't create line with parent types '" + 1325 typeof parents[0] + 1326 "' and '" + 1327 typeof parents[1] + 1328 "' and '" + 1329 typeof parents[2] + 1330 "'." + 1331 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]" 1332 ); 1333 } 1334 } 1335 1336 // point 1 is the midpoint between (0, c, -b) and point 2. => point1 is finite. 1337 attr = Type.copyAttributes(attributes, board.options, "line", 'point1'); 1338 if (isDraggable) { 1339 p1 = board.create("point", [ 1340 c[2]() * c[2]() + c[1]() * c[1](), 1341 c[2]() - c[1]() * c[0]() + c[2](), 1342 -c[1]() - c[2]() * c[0]() - c[1]() 1343 ], attr); 1344 } else { 1345 p1 = board.create("point", [ 1346 function () { 1347 return (c[2]() * c[2]() + c[1]() * c[1]()) * 0.5; 1348 }, 1349 function () { 1350 return (c[2]() - c[1]() * c[0]() + c[2]()) * 0.5; 1351 }, 1352 function () { 1353 return (-c[1]() - c[2]() * c[0]() - c[1]()) * 0.5; 1354 } 1355 ], attr); 1356 } 1357 1358 // point 2: (b^2+c^2,-ba+c,-ca-b) 1359 attr = Type.copyAttributes(attributes, board.options, "line", 'point2'); 1360 if (isDraggable) { 1361 p2 = board.create("point", [ 1362 c[2]() * c[2]() + c[1]() * c[1](), 1363 -c[1]() * c[0]() + c[2](), 1364 -c[2]() * c[0]() - c[1]() 1365 ], attr); 1366 } else { 1367 p2 = board.create("point", [ 1368 function () { 1369 return c[2]() * c[2]() + c[1]() * c[1](); 1370 }, 1371 function () { 1372 return -c[1]() * c[0]() + c[2](); 1373 }, 1374 function () { 1375 return -c[2]() * c[0]() - c[1](); 1376 } 1377 ], attr); 1378 } 1379 1380 // If the line will have a glider and board.suspendUpdate() has been called, we 1381 // need to compute the initial position of the two points p1 and p2. 1382 p1.prepareUpdate().update(); 1383 p2.prepareUpdate().update(); 1384 attr = Type.copyAttributes(attributes, board.options, 'line'); 1385 el = new JXG.Line(board, p1, p2, attr); 1386 // Not yet working, because the points are not draggable. 1387 el.isDraggable = isDraggable; 1388 el.setParents([p1, p2]); 1389 1390 } else if ( 1391 // The parent array contains a function which returns two points. 1392 parents.length === 1 && 1393 Type.isFunction(parents[0]) && 1394 parents[0]().length === 2 && 1395 Type.isPoint(parents[0]()[0]) && 1396 Type.isPoint(parents[0]()[1]) 1397 ) { 1398 ps = parents[0](); 1399 attr = Type.copyAttributes(attributes, board.options, 'line'); 1400 el = new JXG.Line(board, ps[0], ps[1], attr); 1401 el.constrained = true; 1402 el.funps = parents[0]; 1403 el.setParents(ps); 1404 } else if ( 1405 parents.length === 1 && 1406 Type.isFunction(parents[0]) && 1407 parents[0]().length === 3 && 1408 Type.isNumber(parents[0]()[0]) && 1409 Type.isNumber(parents[0]()[1]) && 1410 Type.isNumber(parents[0]()[2]) 1411 ) { 1412 ps = parents[0]; 1413 1414 attr = Type.copyAttributes(attributes, board.options, "line", 'point1'); 1415 p1 = board.create("point", [ 1416 function () { 1417 var c = ps(); 1418 1419 return [ 1420 (c[2] * c[2] + c[1] * c[1]) * 0.5, 1421 (c[2] - c[1] * c[0] + c[2]) * 0.5, 1422 (-c[1] - c[2] * c[0] - c[1]) * 0.5 1423 ]; 1424 } 1425 ], attr); 1426 1427 attr = Type.copyAttributes(attributes, board.options, "line", 'point2'); 1428 p2 = board.create("point", [ 1429 function () { 1430 var c = ps(); 1431 1432 return [ 1433 c[2] * c[2] + c[1] * c[1], 1434 -c[1] * c[0] + c[2], 1435 -c[2] * c[0] - c[1] 1436 ]; 1437 } 1438 ], attr); 1439 1440 attr = Type.copyAttributes(attributes, board.options, 'line'); 1441 el = new JXG.Line(board, p1, p2, attr); 1442 1443 el.constrained = true; 1444 el.funps = parents[0]; 1445 el.setParents([p1, p2]); 1446 } else { 1447 throw new Error( 1448 "JSXGraph: Can't create line with parent types '" + 1449 typeof parents[0] + 1450 "' and '" + 1451 typeof parents[1] + 1452 "'." + 1453 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]" 1454 ); 1455 } 1456 1457 return el; 1458 }; 1459 1460 JXG.registerElement("line", JXG.createLine); 1461 1462 /** 1463 * @class A (line) segment defined by two points. 1464 * It's strictly spoken just a wrapper for element {@link Line} with {@link Line#straightFirst} 1465 * and {@link Line#straightLast} properties set to false. If there is a third variable then the 1466 * segment has a fixed length (which may be a function, too) determined by the absolute value of 1467 * that number. 1468 * @pseudo 1469 * @name Segment 1470 * @augments JXG.Line 1471 * @constructor 1472 * @type JXG.Line 1473 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1474 * @param {JXG.Point,array_JXG.Point,array} point1,point2 Parent elements can be two elements either of type {@link JXG.Point} 1475 * or array of numbers describing the 1476 * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 1477 * @param {number,function} [length] The points are adapted - if possible - such that their distance 1478 * is equal to the absolute value of this number. 1479 * @see Line 1480 * @example 1481 * // Create a segment providing two points. 1482 * var p1 = board.create('point', [4.5, 2.0]); 1483 * var p2 = board.create('point', [1.0, 1.0]); 1484 * var l1 = board.create('segment', [p1, p2]); 1485 * </pre><div class="jxgbox" id="JXGd70e6aac-7c93-4525-a94c-a1820fa38e2f" style="width: 300px; height: 300px;"></div> 1486 * <script type="text/javascript"> 1487 * var slex1_board = JXG.JSXGraph.initBoard('JXGd70e6aac-7c93-4525-a94c-a1820fa38e2f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1488 * var slex1_p1 = slex1_board.create('point', [4.5, 2.0]); 1489 * var slex1_p2 = slex1_board.create('point', [1.0, 1.0]); 1490 * var slex1_l1 = slex1_board.create('segment', [slex1_p1, slex1_p2]); 1491 * </script><pre> 1492 * 1493 * @example 1494 * // Create a segment providing two points. 1495 * var p1 = board.create('point', [4.0, 1.0]); 1496 * var p2 = board.create('point', [1.0, 1.0]); 1497 * // AB 1498 * var l1 = board.create('segment', [p1, p2]); 1499 * var p3 = board.create('point', [4.0, 2.0]); 1500 * var p4 = board.create('point', [1.0, 2.0]); 1501 * // CD 1502 * var l2 = board.create('segment', [p3, p4, 3]); // Fixed length 1503 * var p5 = board.create('point', [4.0, 3.0]); 1504 * var p6 = board.create('point', [1.0, 4.0]); 1505 * // EF 1506 * var l3 = board.create('segment', [p5, p6, function(){ return l1.L();} ]); // Fixed, but dependent length 1507 * </pre><div class="jxgbox" id="JXG617336ba-0705-4b2b-a236-c87c28ef25be" style="width: 300px; height: 300px;"></div> 1508 * <script type="text/javascript"> 1509 * var slex2_board = JXG.JSXGraph.initBoard('JXG617336ba-0705-4b2b-a236-c87c28ef25be', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1510 * var slex2_p1 = slex2_board.create('point', [4.0, 1.0]); 1511 * var slex2_p2 = slex2_board.create('point', [1.0, 1.0]); 1512 * var slex2_l1 = slex2_board.create('segment', [slex2_p1, slex2_p2]); 1513 * var slex2_p3 = slex2_board.create('point', [4.0, 2.0]); 1514 * var slex2_p4 = slex2_board.create('point', [1.0, 2.0]); 1515 * var slex2_l2 = slex2_board.create('segment', [slex2_p3, slex2_p4, 3]); 1516 * var slex2_p5 = slex2_board.create('point', [4.0, 2.0]); 1517 * var slex2_p6 = slex2_board.create('point', [1.0, 2.0]); 1518 * var slex2_l3 = slex2_board.create('segment', [slex2_p5, slex2_p6, function(){ return slex2_l1.L();}]); 1519 * </script><pre> 1520 * 1521 */ 1522 JXG.createSegment = function (board, parents, attributes) { 1523 var el, attr; 1524 1525 attributes.straightFirst = false; 1526 attributes.straightLast = false; 1527 attr = Type.copyAttributes(attributes, board.options, 'segment'); 1528 1529 el = board.create("line", parents.slice(0, 2), attr); 1530 1531 if (parents.length === 3) { 1532 try { 1533 el.hasFixedLength = true; 1534 el.fixedLengthOldCoords = []; 1535 el.fixedLengthOldCoords[0] = new Coords( 1536 Const.COORDS_BY_USER, 1537 el.point1.coords.usrCoords.slice(1, 3), 1538 board 1539 ); 1540 el.fixedLengthOldCoords[1] = new Coords( 1541 Const.COORDS_BY_USER, 1542 el.point2.coords.usrCoords.slice(1, 3), 1543 board 1544 ); 1545 1546 el.setFixedLength(parents[2]); 1547 } catch (err) { 1548 throw new Error( 1549 "JSXGraph: Can't create segment with third parent type '" + 1550 typeof parents[2] + 1551 "'." + 1552 "\nPossible third parent types: number or function" 1553 ); 1554 } 1555 // if (Type.isNumber(parents[2])) { 1556 // el.fixedLength = function () { 1557 // return parents[2]; 1558 // }; 1559 // } else if (Type.isFunction(parents[2])) { 1560 // el.fixedLength = Type.createFunction(parents[2], this.board); 1561 // } else { 1562 // throw new Error( 1563 // "JSXGraph: Can't create segment with third parent type '" + 1564 // typeof parents[2] + 1565 // "'." + 1566 // "\nPossible third parent types: number or function" 1567 // ); 1568 // } 1569 1570 el.getParents = function () { 1571 return this.parents.concat(this.fixedLength()); 1572 }; 1573 1574 } 1575 1576 el.elType = 'segment'; 1577 1578 return el; 1579 }; 1580 1581 JXG.registerElement("segment", JXG.createSegment); 1582 1583 /** 1584 * @class A segment with an arrow head. 1585 * This element is just a wrapper for element 1586 * {@link Line} with {@link Line#straightFirst} 1587 * and {@link Line#straightLast} properties set to false and {@link Line#lastArrow} set to true. 1588 * @pseudo 1589 * @name Arrow 1590 * @augments JXG.Line 1591 * @constructor 1592 * @type JXG.Line 1593 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1594 * @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 1595 * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 1596 * @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 1597 * of the equation <tt>a*x+b*y+c*z = 0</tt>. 1598 * @see Line 1599 * @example 1600 * // Create an arrow providing two points. 1601 * var p1 = board.create('point', [4.5, 2.0]); 1602 * var p2 = board.create('point', [1.0, 1.0]); 1603 * var l1 = board.create('arrow', [p1, p2]); 1604 * </pre><div class="jxgbox" id="JXG1d26bd22-7d6d-4018-b164-4c8bc8d22ccf" style="width: 300px; height: 300px;"></div> 1605 * <script type="text/javascript"> 1606 * var alex1_board = JXG.JSXGraph.initBoard('JXG1d26bd22-7d6d-4018-b164-4c8bc8d22ccf', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1607 * var alex1_p1 = alex1_board.create('point', [4.5, 2.0]); 1608 * var alex1_p2 = alex1_board.create('point', [1.0, 1.0]); 1609 * var alex1_l1 = alex1_board.create('arrow', [alex1_p1, alex1_p2]); 1610 * </script><pre> 1611 */ 1612 JXG.createArrow = function (board, parents, attributes) { 1613 var el, attr; 1614 1615 attributes.straightFirst = false; 1616 attributes.straightLast = false; 1617 attr = Type.copyAttributes(attributes, board.options, 'arrow'); 1618 el = board.create("line", parents, attr); 1619 //el.setArrow(false, true); 1620 el.type = Const.OBJECT_TYPE_VECTOR; 1621 el.elType = 'arrow'; 1622 1623 return el; 1624 }; 1625 1626 JXG.registerElement("arrow", JXG.createArrow); 1627 1628 /** 1629 * @class Axis is a line with optional ticks and labels. 1630 * It's strictly spoken just a wrapper for element {@link Line} with {@link Line#straightFirst} 1631 * and {@link Line#straightLast} properties set to true. Additionally {@link Line#lastArrow} is set to true and default {@link Ticks} will be created. 1632 * @pseudo 1633 * @name Axis 1634 * @augments JXG.Line 1635 * @constructor 1636 * @type JXG.Line 1637 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1638 * @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 1639 * coordinates of a point. In the latter case, the point will be constructed automatically as a fixed invisible point. 1640 * @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 1641 * of the equation <tt>a*x+b*y+c*z = 0</tt>. 1642 * @example 1643 * // Create an axis providing two coords pairs. 1644 * var l1 = board.create('axis', [[0.0, 1.0], [1.0, 1.3]]); 1645 * </pre><div class="jxgbox" id="JXG4f414733-624c-42e4-855c-11f5530383ae" style="width: 300px; height: 300px;"></div> 1646 * <script type="text/javascript"> 1647 * var axex1_board = JXG.JSXGraph.initBoard('JXG4f414733-624c-42e4-855c-11f5530383ae', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1648 * var axex1_l1 = axex1_board.create('axis', [[0.0, 1.0], [1.0, 1.3]]); 1649 * </script><pre> 1650 * @example 1651 * // Create ticks labels as fractions 1652 * board.create('axis', [[0,1], [1,1]], { 1653 * ticks: { 1654 * label: { 1655 * toFraction: true, 1656 * useMathjax: false, 1657 * anchorX: 'middle', 1658 * offset: [0, -10] 1659 * } 1660 * } 1661 * }); 1662 * 1663 * 1664 * </pre><div id="JXG34174cc4-0050-4ab4-af69-e91365d0666f" class="jxgbox" style="width: 300px; height: 300px;"></div> 1665 * <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" id="MathJax-script"></script> 1666 * <script type="text/javascript"> 1667 * (function() { 1668 * var board = JXG.JSXGraph.initBoard('JXG34174cc4-0050-4ab4-af69-e91365d0666f', 1669 * {boundingbox: [-1.2, 2.3, 1.2, -2.3], axis: true, showcopyright: false, shownavigation: false}); 1670 * board.create('axis', [[0,1], [1,1]], { 1671 * ticks: { 1672 * label: { 1673 * toFraction: true, 1674 * useMathjax: false, 1675 * anchorX: 'middle', 1676 * offset: [0, -10] 1677 * } 1678 * } 1679 * }); 1680 * 1681 * 1682 * })(); 1683 * 1684 * </script><pre> 1685 * 1686 */ 1687 JXG.createAxis = function (board, parents, attributes) { 1688 var axis, attr, 1689 ancestor, ticksDist; 1690 1691 // Create line 1692 attr = Type.copyAttributes(attributes, board.options, 'axis'); 1693 try { 1694 axis = board.create("line", parents, attr); 1695 } catch (err) { 1696 throw new Error( 1697 "JSXGraph: Can't create axis with parent types '" + 1698 typeof parents[0] + 1699 "' and '" + 1700 typeof parents[1] + 1701 "'." + 1702 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]" 1703 ); 1704 } 1705 1706 axis.type = Const.OBJECT_TYPE_AXIS; 1707 axis.isDraggable = false; 1708 axis.point1.isDraggable = false; 1709 axis.point2.isDraggable = false; 1710 1711 // Save usrCoords of points 1712 axis._point1UsrCoordsOrg = axis.point1.coords.usrCoords.slice(); 1713 axis._point2UsrCoordsOrg = axis.point2.coords.usrCoords.slice(); 1714 1715 for (ancestor in axis.ancestors) { 1716 if (axis.ancestors.hasOwnProperty(ancestor)) { 1717 axis.ancestors[ancestor].type = Const.OBJECT_TYPE_AXISPOINT; 1718 } 1719 } 1720 1721 // Create ticks 1722 // attrTicks = attr.ticks; 1723 if (Type.exists(attr.ticks.ticksdistance)) { 1724 ticksDist = attr.ticks.ticksdistance; 1725 } else if (Type.isArray(attr.ticks.ticks)) { 1726 ticksDist = attr.ticks.ticks; 1727 } else { 1728 ticksDist = 1.0; 1729 } 1730 1731 /** 1732 * The ticks attached to the axis. 1733 * @memberOf Axis.prototype 1734 * @name defaultTicks 1735 * @type JXG.Ticks 1736 */ 1737 axis.defaultTicks = board.create("ticks", [axis, ticksDist], attr.ticks); 1738 axis.defaultTicks.dump = false; 1739 axis.elType = 'axis'; 1740 axis.subs = { 1741 ticks: axis.defaultTicks 1742 }; 1743 axis.inherits.push(axis.defaultTicks); 1744 1745 axis.update = function () { 1746 var bbox, 1747 position, i, 1748 direction, horizontal, vertical, 1749 ticksAutoPos, ticksAutoPosThres, dist, 1750 anchor, left, right, 1751 distUsr, 1752 newPosP1, newPosP2, 1753 locationOrg, 1754 visLabel, anchr, off; 1755 1756 if (!this.needsUpdate) { 1757 return this; 1758 } 1759 1760 bbox = this.board.getBoundingBox(); 1761 position = this.evalVisProp('position'); 1762 direction = this.Direction(); 1763 horizontal = this.isHorizontal(); 1764 vertical = this.isVertical(); 1765 ticksAutoPos = this.evalVisProp('ticksautopos'); 1766 ticksAutoPosThres = this.evalVisProp('ticksautoposthreshold'); 1767 1768 if (horizontal) { 1769 ticksAutoPosThres = Type.parseNumber(ticksAutoPosThres, Math.abs(bbox[1] - bbox[3]), 1 / this.board.unitX) * this.board.unitX; 1770 } else if (vertical) { 1771 ticksAutoPosThres = Type.parseNumber(ticksAutoPosThres, Math.abs(bbox[1] - bbox[3]), 1 / this.board.unitY) * this.board.unitY; 1772 } else { 1773 ticksAutoPosThres = Type.parseNumber(ticksAutoPosThres, 1, 1); 1774 } 1775 1776 anchor = this.evalVisProp('anchor'); 1777 left = anchor.indexOf('left') > -1; 1778 right = anchor.indexOf('right') > -1; 1779 1780 distUsr = this.evalVisProp('anchordist'); 1781 if (horizontal) { 1782 distUsr = Type.parseNumber(distUsr, Math.abs(bbox[1] - bbox[3]), 1 / this.board.unitX); 1783 } else if (vertical) { 1784 distUsr = Type.parseNumber(distUsr, Math.abs(bbox[0] - bbox[2]), 1 / this.board.unitY); 1785 } else { 1786 distUsr = 0; 1787 } 1788 1789 locationOrg = this.board.getPointLoc(this._point1UsrCoordsOrg, distUsr); 1790 1791 // Set position of axis 1792 newPosP1 = this.point1.coords.usrCoords.slice(); 1793 newPosP2 = this.point2.coords.usrCoords.slice(); 1794 1795 if (position === 'static' || (!vertical && !horizontal)) { 1796 // Do nothing 1797 1798 } else if (position === 'fixed') { 1799 if (horizontal) { // direction[1] === 0 1800 if ((direction[0] > 0 && right) || (direction[0] < 0 && left)) { 1801 newPosP1[2] = bbox[3] + distUsr; 1802 newPosP2[2] = bbox[3] + distUsr; 1803 } else if ((direction[0] > 0 && left) || (direction[0] < 0 && right)) { 1804 newPosP1[2] = bbox[1] - distUsr; 1805 newPosP2[2] = bbox[1] - distUsr; 1806 1807 } else { 1808 newPosP1 = this._point1UsrCoordsOrg.slice(); 1809 newPosP2 = this._point2UsrCoordsOrg.slice(); 1810 } 1811 } 1812 if (vertical) { // direction[0] === 0 1813 if ((direction[1] > 0 && left) || (direction[1] < 0 && right)) { 1814 newPosP1[1] = bbox[0] + distUsr; 1815 newPosP2[1] = bbox[0] + distUsr; 1816 1817 } else if ((direction[1] > 0 && right) || (direction[1] < 0 && left)) { 1818 newPosP1[1] = bbox[2] - distUsr; 1819 newPosP2[1] = bbox[2] - distUsr; 1820 1821 } else { 1822 newPosP1 = this._point1UsrCoordsOrg.slice(); 1823 newPosP2 = this._point2UsrCoordsOrg.slice(); 1824 } 1825 } 1826 1827 } else if (position === 'sticky') { 1828 if (horizontal) { // direction[1] === 0 1829 if (locationOrg[1] < 0 && ((direction[0] > 0 && right) || (direction[0] < 0 && left))) { 1830 newPosP1[2] = bbox[3] + distUsr; 1831 newPosP2[2] = bbox[3] + distUsr; 1832 1833 } else if (locationOrg[1] > 0 && ((direction[0] > 0 && left) || (direction[0] < 0 && right))) { 1834 newPosP1[2] = bbox[1] - distUsr; 1835 newPosP2[2] = bbox[1] - distUsr; 1836 1837 } else { 1838 newPosP1 = this._point1UsrCoordsOrg.slice(); 1839 newPosP2 = this._point2UsrCoordsOrg.slice(); 1840 } 1841 } 1842 if (vertical) { // direction[0] === 0 1843 if (locationOrg[0] < 0 && ((direction[1] > 0 && left) || (direction[1] < 0 && right))) { 1844 newPosP1[1] = bbox[0] + distUsr; 1845 newPosP2[1] = bbox[0] + distUsr; 1846 1847 } else if (locationOrg[0] > 0 && ((direction[1] > 0 && right) || (direction[1] < 0 && left))) { 1848 newPosP1[1] = bbox[2] - distUsr; 1849 newPosP2[1] = bbox[2] - distUsr; 1850 1851 } else { 1852 newPosP1 = this._point1UsrCoordsOrg.slice(); 1853 newPosP2 = this._point2UsrCoordsOrg.slice(); 1854 } 1855 } 1856 } 1857 1858 this.point1.setPositionDirectly(JXG.COORDS_BY_USER, newPosP1); 1859 this.point2.setPositionDirectly(JXG.COORDS_BY_USER, newPosP2); 1860 1861 // Set position of tick labels 1862 if (Type.exists(this.defaultTicks)) { 1863 visLabel = this.defaultTicks.visProp.label; 1864 if (ticksAutoPos && (horizontal || vertical)) { 1865 1866 if (!Type.exists(visLabel._anchorx_org)) { 1867 visLabel._anchorx_org = Type.def(visLabel.anchorx, this.board.options.text.anchorX); 1868 } 1869 if (!Type.exists(visLabel._anchory_org)) { 1870 visLabel._anchory_org = Type.def(visLabel.anchory, this.board.options.text.anchorY); 1871 } 1872 if (!Type.exists(visLabel._offset_org)) { 1873 visLabel._offset_org = visLabel.offset.slice(); 1874 } 1875 1876 off = visLabel.offset; 1877 if (horizontal) { 1878 dist = axis.point1.coords.scrCoords[2] - (this.board.canvasHeight * 0.5); 1879 1880 anchr = visLabel.anchory; 1881 1882 // The last position of the labels is stored in visLabel._side 1883 if (dist < 0 && Math.abs(dist) > ticksAutoPosThres) { 1884 // Put labels on top of the line 1885 if (visLabel._side === 'bottom') { 1886 // Switch position 1887 if (visLabel.anchory === 'top') { 1888 anchr = 'bottom'; 1889 } 1890 off[1] *= -1; 1891 visLabel._side = 'top'; 1892 } 1893 1894 } else if (dist > 0 && Math.abs(dist) > ticksAutoPosThres) { 1895 // Put labels below the line 1896 if (visLabel._side === 'top') { 1897 // Switch position 1898 if (visLabel.anchory === 'bottom') { 1899 anchr = 'top'; 1900 } 1901 off[1] *= -1; 1902 visLabel._side = 'bottom'; 1903 } 1904 1905 } else { 1906 // Put to original position 1907 anchr = visLabel._anchory_org; 1908 off = visLabel._offset_org.slice(); 1909 1910 if (anchr === 'top') { 1911 visLabel._side = 'bottom'; 1912 } else if (anchr === 'bottom') { 1913 visLabel._side = 'top'; 1914 } else if (off[1] < 0) { 1915 visLabel._side = 'bottom'; 1916 } else { 1917 visLabel._side = 'top'; 1918 } 1919 } 1920 1921 for (i = 0; i < axis.defaultTicks.labels.length; i++) { 1922 this.defaultTicks.labels[i].visProp.anchory = anchr; 1923 } 1924 visLabel.anchory = anchr; 1925 1926 } else if (vertical) { 1927 dist = axis.point1.coords.scrCoords[1] - (this.board.canvasWidth * 0.5); 1928 1929 if (dist < 0 && Math.abs(dist) > ticksAutoPosThres) { 1930 // Put labels to the left of the line 1931 if (visLabel._side === 'right') { 1932 // Switch position 1933 if (visLabel.anchorx === 'left') { 1934 anchr = 'right'; 1935 } 1936 off[0] *= -1; 1937 visLabel._side = 'left'; 1938 } 1939 1940 } else if (dist > 0 && Math.abs(dist) > ticksAutoPosThres) { 1941 // Put labels to the right of the line 1942 if (visLabel._side === 'left') { 1943 // Switch position 1944 if (visLabel.anchorx === 'right') { 1945 anchr = 'left'; 1946 } 1947 off[0] *= -1; 1948 visLabel._side = 'right'; 1949 } 1950 1951 } else { 1952 // Put to original position 1953 anchr = visLabel._anchorx_org; 1954 off = visLabel._offset_org.slice(); 1955 1956 if (anchr === 'left') { 1957 visLabel._side = 'right'; 1958 } else if (anchr === 'right') { 1959 visLabel._side = 'left'; 1960 } else if (off[0] < 0) { 1961 visLabel._side = 'left'; 1962 } else { 1963 visLabel._side = 'right'; 1964 } 1965 } 1966 1967 for (i = 0; i < axis.defaultTicks.labels.length; i++) { 1968 this.defaultTicks.labels[i].visProp.anchorx = anchr; 1969 } 1970 visLabel.anchorx = anchr; 1971 } 1972 visLabel.offset = off; 1973 1974 } else { 1975 delete visLabel._anchorx_org; 1976 delete visLabel._anchory_org; 1977 delete visLabel._offset_org; 1978 } 1979 this.defaultTicks.needsUpdate = true; 1980 } 1981 1982 JXG.Line.prototype.update.call(this); 1983 1984 return this; 1985 }; 1986 1987 return axis; 1988 }; 1989 1990 JXG.registerElement("axis", JXG.createAxis); 1991 1992 /** 1993 * @class The tangent line at a point on a line, circle, conic, turtle, or curve. 1994 * A tangent line is always constructed 1995 * by a point on a line, circle, or curve and describes the tangent in the point on that line, circle, or curve. 1996 * <p> 1997 * If the point is not on the object (line, circle, conic, curve, turtle) the output depends on the type of the object. 1998 * For conics and circles, the polar line will be constructed. For function graphs, 1999 * the tangent of the vertical projection of the point to the function graph is constructed. For all other objects, the tangent 2000 * in the orthogonal projection of the point to the object will be constructed. 2001 * @pseudo 2002 * @name Tangent 2003 * @augments JXG.Line 2004 * @constructor 2005 * @type JXG.Line 2006 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 2007 * @param {Glider} g A glider on a line, circle, or curve. 2008 * @param {JXG.GeometryElement} [c] Optional element for which the tangent is constructed 2009 * @example 2010 * // Create a tangent providing a glider on a function graph 2011 * var c1 = board.create('curve', [function(t){return t},function(t){return t*t*t;}]); 2012 * var g1 = board.create('glider', [0.6, 1.2, c1]); 2013 * var t1 = board.create('tangent', [g1]); 2014 * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-4018d0d17a98" style="width: 400px; height: 400px;"></div> 2015 * <script type="text/javascript"> 2016 * var tlex1_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-4018d0d17a98', {boundingbox: [-6, 6, 6, -6], axis: true, showcopyright: false, shownavigation: false}); 2017 * var tlex1_c1 = tlex1_board.create('curve', [function(t){return t},function(t){return t*t*t;}]); 2018 * var tlex1_g1 = tlex1_board.create('glider', [0.6, 1.2, tlex1_c1]); 2019 * var tlex1_t1 = tlex1_board.create('tangent', [tlex1_g1]); 2020 * </script><pre> 2021 */ 2022 JXG.createTangent = function (board, parents, attributes) { 2023 var p, c, j, el, tangent, attr, 2024 getCurveTangentDir, 2025 res, isTransformed, 2026 slides = []; 2027 2028 if (parents.length === 1) { 2029 // One argument: glider on line, circle or curve 2030 p = parents[0]; 2031 c = p.slideObject; 2032 2033 } else if (parents.length === 2) { 2034 // Two arguments: (point,line|curve|circle|conic) or (line|curve|circle|conic,point). 2035 // In fact, for circles and conics it is the polar 2036 if (Type.isPoint(parents[0])) { 2037 p = parents[0]; 2038 c = parents[1]; 2039 } else if (Type.isPoint(parents[1])) { 2040 c = parents[0]; 2041 p = parents[1]; 2042 } else { 2043 throw new Error( 2044 "JSXGraph: Can't create tangent with parent types '" + 2045 typeof parents[0] + 2046 "' and '" + 2047 typeof parents[1] + 2048 "'." + 2049 "\nPossible parent types: [glider|point], [point,line|curve|circle|conic]" 2050 ); 2051 } 2052 } else { 2053 throw new Error( 2054 "JSXGraph: Can't create tangent with parent types '" + 2055 typeof parents[0] + 2056 "' and '" + 2057 typeof parents[1] + 2058 "'." + 2059 "\nPossible parent types: [glider|point], [point,line|curve|circle|conic]" 2060 ); 2061 } 2062 2063 attr = Type.copyAttributes(attributes, board.options, 'tangent'); 2064 if (c.elementClass === Const.OBJECT_CLASS_LINE) { 2065 tangent = board.create("line", [c.point1, c.point2], attr); 2066 tangent.glider = p; 2067 } else if ( 2068 c.elementClass === Const.OBJECT_CLASS_CURVE && 2069 c.type !== Const.OBJECT_TYPE_CONIC 2070 ) { 2071 res = c.getTransformationSource(); 2072 isTransformed = res[0]; 2073 if (isTransformed) { 2074 // Curve is result of a transformation 2075 // We recursively collect all curves from which 2076 // the curve is transformed. 2077 slides.push(c); 2078 while (res[0] && Type.exists(res[1]._transformationSource)) { 2079 slides.push(res[1]); 2080 res = res[1].getTransformationSource(); 2081 } 2082 } 2083 2084 if (c.evalVisProp('curvetype') !== "plot" || isTransformed) { 2085 // Functiongraph or parametric curve or 2086 // transformed curve thereof. 2087 tangent = board.create( 2088 "line", 2089 [ 2090 function () { 2091 var g = c.X, 2092 f = c.Y, 2093 df, dg, 2094 li, i, c_org, invMat, po, 2095 t; 2096 2097 if (p.type === Const.OBJECT_TYPE_GLIDER) { 2098 t = p.position; 2099 } else if (c.evalVisProp('curvetype') === 'functiongraph') { 2100 t = p.X(); 2101 } else { 2102 t = Geometry.projectPointToCurve(p, c, board)[1]; 2103 } 2104 2105 // po are the coordinates of the point 2106 // on the "original" curve. That is the curve or 2107 // the original curve which is transformed (maybe multiple times) 2108 // to this curve. 2109 // t is the position of the point on the "original" curve 2110 po = p.Coords(true); 2111 if (isTransformed) { 2112 c_org = slides[slides.length - 1]._transformationSource; 2113 g = c_org.X; 2114 f = c_org.Y; 2115 for (i = 0; i < slides.length; i++) { 2116 slides[i].updateTransformMatrix(); 2117 invMat = Mat.inverse(slides[i].transformMat); 2118 po = Mat.matVecMult(invMat, po); 2119 } 2120 2121 if (p.type !== Const.OBJECT_TYPE_GLIDER) { 2122 po[1] /= po[0]; 2123 po[2] /= po[0]; 2124 po[0] /= po[0]; 2125 t = Geometry.projectCoordsToCurve(po[1], po[2], 0, c_org, board)[1]; 2126 } 2127 } 2128 2129 // li are the coordinates of the line on the "original" curve 2130 df = Numerics.D(f)(t); 2131 dg = Numerics.D(g)(t); 2132 li = [ 2133 -po[1] * df + po[2] * dg, 2134 po[0] * df, 2135 -po[0] * dg 2136 ]; 2137 2138 if (isTransformed) { 2139 // Transform the line to the transformed curve 2140 for (i = slides.length - 1; i >= 0; i--) { 2141 invMat = Mat.transpose(Mat.inverse(slides[i].transformMat)); 2142 li = Mat.matVecMult(invMat, li); 2143 } 2144 } 2145 2146 return li; 2147 } 2148 ], 2149 attr 2150 ); 2151 2152 p.addChild(tangent); 2153 // this is required for the geogebra reader to display a slope 2154 tangent.glider = p; 2155 } else { 2156 // curveType 'plot': discrete data 2157 /** 2158 * @ignore 2159 * 2160 * In case of bezierDegree == 1: 2161 * Find two points p1, p2 enclosing the glider. 2162 * Then the equation of the line segment is: 0 = y*(x1-x2) + x*(y2-y1) + y1*x2-x1*y2, 2163 * which is the cross product of p1 and p2. 2164 * 2165 * In case of bezierDegree === 3: 2166 * The slope dy / dx of the tangent is determined. Then the 2167 * tangent is computed as cross product between 2168 * the glider p and [1, p.X() + dx, p.Y() + dy] 2169 * 2170 */ 2171 getCurveTangentDir = function (position, c, num) { 2172 var i = Math.floor(position), 2173 p1, p2, t, A, B, C, D, dx, dy, d, 2174 points, le; 2175 2176 if (c.bezierDegree === 1) { 2177 if (i === c.numberPoints - 1) { 2178 i--; 2179 } 2180 } else if (c.bezierDegree === 3) { 2181 // i is start of the Bezier segment 2182 // t is the position in the Bezier segment 2183 if (c.elType === 'sector') { 2184 points = c.points.slice(3, c.numberPoints - 3); 2185 le = points.length; 2186 } else { 2187 points = c.points; 2188 le = points.length; 2189 } 2190 i = Math.floor((position * (le - 1)) / 3) * 3; 2191 t = (position * (le - 1) - i) / 3; 2192 if (i >= le - 1) { 2193 i = le - 4; 2194 t = 1; 2195 } 2196 } else { 2197 return 0; 2198 } 2199 2200 if (i < 0) { 2201 return 1; 2202 } 2203 2204 // The curve points are transformed (if there is a transformation) 2205 // c.X(i) is not transformed. 2206 if (c.bezierDegree === 1) { 2207 p1 = c.points[i].usrCoords; 2208 p2 = c.points[i + 1].usrCoords; 2209 } else { 2210 A = points[i].usrCoords; 2211 B = points[i + 1].usrCoords; 2212 C = points[i + 2].usrCoords; 2213 D = points[i + 3].usrCoords; 2214 dx = (1 - t) * (1 - t) * (B[1] - A[1]) + 2215 2 * (1 - t) * t * (C[1] - B[1]) + 2216 t * t * (D[1] - C[1]); 2217 dy = (1 - t) * (1 - t) * (B[2] - A[2]) + 2218 2 * (1 - t) * t * (C[2] - B[2]) + 2219 t * t * (D[2] - C[2]); 2220 d = Mat.hypot(dx, dy); 2221 dx /= d; 2222 dy /= d; 2223 p1 = p.coords.usrCoords; 2224 p2 = [1, p1[1] + dx, p1[2] + dy]; 2225 } 2226 2227 switch (num) { 2228 case 0: 2229 return p1[2] * p2[1] - p1[1] * p2[2]; 2230 case 1: 2231 return p2[2] - p1[2]; 2232 case 2: 2233 return p1[1] - p2[1]; 2234 default: 2235 return [ 2236 p1[2] * p2[1] - p1[1] * p2[2], 2237 p2[2] - p1[2], 2238 p1[1] - p2[1] 2239 ]; 2240 } 2241 }; 2242 2243 tangent = board.create( 2244 "line", 2245 [ 2246 function () { 2247 var t; 2248 2249 if (p.type === Const.OBJECT_TYPE_GLIDER) { 2250 t = p.position; 2251 } else { 2252 t = Geometry.projectPointToCurve(p, c, board)[1]; 2253 } 2254 2255 return getCurveTangentDir(t, c); 2256 } 2257 ], 2258 attr 2259 ); 2260 2261 p.addChild(tangent); 2262 // this is required for the geogebra reader to display a slope 2263 tangent.glider = p; 2264 } 2265 } else if (c.type === Const.OBJECT_TYPE_TURTLE) { 2266 tangent = board.create( 2267 "line", 2268 [ 2269 function () { 2270 var i, t; 2271 if (p.type === Const.OBJECT_TYPE_GLIDER) { 2272 t = p.position; 2273 } else { 2274 t = Geometry.projectPointToTurtle(p, c, board)[1]; 2275 } 2276 2277 i = Math.floor(t); 2278 2279 // run through all curves of this turtle 2280 for (j = 0; j < c.objects.length; j++) { 2281 el = c.objects[j]; 2282 2283 if (el.type === Const.OBJECT_TYPE_CURVE) { 2284 if (i < el.numberPoints) { 2285 break; 2286 } 2287 2288 i -= el.numberPoints; 2289 } 2290 } 2291 2292 if (i === el.numberPoints - 1) { 2293 i--; 2294 } 2295 2296 if (i < 0) { 2297 return [1, 0, 0]; 2298 } 2299 2300 return [ 2301 el.Y(i) * el.X(i + 1) - el.X(i) * el.Y(i + 1), 2302 el.Y(i + 1) - el.Y(i), 2303 el.X(i) - el.X(i + 1) 2304 ]; 2305 } 2306 ], 2307 attr 2308 ); 2309 p.addChild(tangent); 2310 2311 // this is required for the geogebra reader to display a slope 2312 tangent.glider = p; 2313 } else if ( 2314 c.elementClass === Const.OBJECT_CLASS_CIRCLE || 2315 c.type === Const.OBJECT_TYPE_CONIC 2316 ) { 2317 // If p is not on c, the tangent is the polar. 2318 // This construction should work on conics, too. p has to lie on c. 2319 tangent = board.create( 2320 "line", 2321 [ 2322 function () { 2323 return Mat.matVecMult(c.quadraticform, p.coords.usrCoords); 2324 } 2325 ], 2326 attr 2327 ); 2328 2329 p.addChild(tangent); 2330 // this is required for the geogebra reader to display a slope 2331 tangent.glider = p; 2332 } 2333 2334 if (!Type.exists(tangent)) { 2335 throw new Error("JSXGraph: Couldn't create tangent with the given parents."); 2336 } 2337 2338 tangent.elType = 'tangent'; 2339 tangent.type = Const.OBJECT_TYPE_TANGENT; 2340 tangent.setParents(parents); 2341 2342 return tangent; 2343 }; 2344 2345 /** 2346 * @class A normal is the line perpendicular to a line or to a tangent of a circle or curve. 2347 * @pseudo 2348 * @description A normal is a line through a given point on an element of type line, circle, curve, or turtle and orthogonal to that object. 2349 * @constructor 2350 * @name Normal 2351 * @type JXG.Line 2352 * @augments JXG.Line 2353 * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown. 2354 * @param {JXG.Line,JXG.Circle,JXG.Curve,JXG.Turtle_JXG.Point} o,p The constructed line contains p which lies on the object and is orthogonal 2355 * to the tangent to the object in the given point. 2356 * @param {Glider} p Works like above, however the object is given by {@link JXG.CoordsElement#slideObject}. 2357 * @example 2358 * // Create a normal to a circle. 2359 * var p1 = board.create('point', [2.0, 2.0]); 2360 * var p2 = board.create('point', [3.0, 2.0]); 2361 * var c1 = board.create('circle', [p1, p2]); 2362 * 2363 * var norm1 = board.create('normal', [c1, p2]); 2364 * </pre><div class="jxgbox" id="JXG4154753d-3d29-40fb-a860-0b08aa4f3743" style="width: 400px; height: 400px;"></div> 2365 * <script type="text/javascript"> 2366 * var nlex1_board = JXG.JSXGraph.initBoard('JXG4154753d-3d29-40fb-a860-0b08aa4f3743', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 2367 * var nlex1_p1 = nlex1_board.create('point', [2.0, 2.0]); 2368 * var nlex1_p2 = nlex1_board.create('point', [3.0, 2.0]); 2369 * var nlex1_c1 = nlex1_board.create('circle', [nlex1_p1, nlex1_p2]); 2370 * 2371 * // var nlex1_p3 = nlex1_board.create('point', [1.0, 2.0]); 2372 * var nlex1_norm1 = nlex1_board.create('normal', [nlex1_c1, nlex1_p2]); 2373 * </script><pre> 2374 */ 2375 JXG.createNormal = function (board, parents, attributes) { 2376 var p, c, l, i, attr, pp, attrp, 2377 getCurveNormalDir, 2378 res, isTransformed, 2379 slides = []; 2380 2381 for (i = 0; i < parents.length; ++i) { 2382 parents[i] = board.select(parents[i]); 2383 } 2384 // One arguments: glider on line, circle or curve 2385 if (parents.length === 1) { 2386 p = parents[0]; 2387 c = p.slideObject; 2388 // Two arguments: (point,line), (point,circle), (line,point) or (circle,point) 2389 } else if (parents.length === 2) { 2390 if (Type.isPointType(board, parents[0])) { 2391 p = Type.providePoints(board, [parents[0]], attributes, 'point')[0]; 2392 c = parents[1]; 2393 } else if (Type.isPointType(board, parents[1])) { 2394 c = parents[0]; 2395 p = Type.providePoints(board, [parents[1]], attributes, 'point')[0]; 2396 } else { 2397 throw new Error( 2398 "JSXGraph: Can't create normal with parent types '" + 2399 typeof parents[0] + 2400 "' and '" + 2401 typeof parents[1] + 2402 "'." + 2403 "\nPossible parent types: [point,line], [point,circle], [glider]" 2404 ); 2405 } 2406 } else { 2407 throw new Error( 2408 "JSXGraph: Can't create normal with parent types '" + 2409 typeof parents[0] + 2410 "' and '" + 2411 typeof parents[1] + 2412 "'." + 2413 "\nPossible parent types: [point,line], [point,circle], [glider]" 2414 ); 2415 } 2416 2417 attr = Type.copyAttributes(attributes, board.options, 'normal'); 2418 if (c.elementClass === Const.OBJECT_CLASS_LINE) { 2419 // Private point 2420 attrp = Type.copyAttributes(attributes, board.options, "normal", 'point'); 2421 pp = board.create( 2422 "point", 2423 [ 2424 function () { 2425 var p = Mat.crossProduct([1, 0, 0], c.stdform); 2426 return [p[0], -p[2], p[1]]; 2427 } 2428 ], 2429 attrp 2430 ); 2431 pp.isDraggable = true; 2432 2433 l = board.create("line", [p, pp], attr); 2434 2435 /** 2436 * A helper point used to create a normal to a {@link JXG.Line} object. For normals to circles or curves this 2437 * element is <tt>undefined</tt>. 2438 * @type JXG.Point 2439 * @name point 2440 * @memberOf Normal.prototype 2441 */ 2442 l.point = pp; 2443 l.subs = { 2444 point: pp 2445 }; 2446 l.inherits.push(pp); 2447 } else if (c.elementClass === Const.OBJECT_CLASS_CIRCLE) { 2448 l = board.create("line", [c.midpoint, p], attr); 2449 } else if (c.elementClass === Const.OBJECT_CLASS_CURVE) { 2450 res = c.getTransformationSource(); 2451 isTransformed = res[0]; 2452 if (isTransformed) { 2453 // Curve is result of a transformation 2454 // We recursively collect all curves from which 2455 // the curve is transformed. 2456 slides.push(c); 2457 while (res[0] && Type.exists(res[1]._transformationSource)) { 2458 slides.push(res[1]); 2459 res = res[1].getTransformationSource(); 2460 } 2461 } 2462 2463 if (c.evalVisProp('curvetype') !== "plot" || isTransformed) { 2464 // Functiongraph or parametric curve or 2465 // transformed curve thereof. 2466 l = board.create( 2467 "line", 2468 [ 2469 function () { 2470 var g = c.X, 2471 f = c.Y, 2472 df, dg, 2473 li, i, c_org, invMat, po, 2474 t; 2475 2476 if (p.type === Const.OBJECT_TYPE_GLIDER) { 2477 t = p.position; 2478 } else if (c.evalVisProp('curvetype') === 'functiongraph') { 2479 t = p.X(); 2480 } else { 2481 t = Geometry.projectPointToCurve(p, c, board)[1]; 2482 } 2483 2484 // po are the coordinates of the point 2485 // on the "original" curve. That is the curve or 2486 // the original curve which is transformed (maybe multiple times) 2487 // to this curve. 2488 // t is the position of the point on the "original" curve 2489 po = p.Coords(true); 2490 if (isTransformed) { 2491 c_org = slides[slides.length - 1]._transformationSource; 2492 g = c_org.X; 2493 f = c_org.Y; 2494 for (i = 0; i < slides.length; i++) { 2495 slides[i].updateTransformMatrix(); 2496 invMat = Mat.inverse(slides[i].transformMat); 2497 po = Mat.matVecMult(invMat, po); 2498 } 2499 2500 if (p.type !== Const.OBJECT_TYPE_GLIDER) { 2501 po[1] /= po[0]; 2502 po[2] /= po[0]; 2503 po[0] /= po[0]; 2504 t = Geometry.projectCoordsToCurve(po[1], po[2], 0, c_org, board)[1]; 2505 } 2506 } 2507 2508 df = Numerics.D(f)(t); 2509 dg = Numerics.D(g)(t); 2510 li = [ 2511 -po[1] * dg - po[2] * df, 2512 po[0] * dg, 2513 po[0] * df 2514 ]; 2515 2516 if (isTransformed) { 2517 // Transform the line to the transformed curve 2518 for (i = slides.length - 1; i >= 0; i--) { 2519 invMat = Mat.transpose(Mat.inverse(slides[i].transformMat)); 2520 li = Mat.matVecMult(invMat, li); 2521 } 2522 } 2523 2524 return li; 2525 } 2526 ], 2527 attr 2528 ); 2529 } else { 2530 // curveType 'plot': discrete data 2531 getCurveNormalDir = function (position, c, num) { 2532 var i = Math.floor(position), 2533 lbda, 2534 p1, p2, t, A, B, C, D, dx, dy, d, 2535 li, p_org, pp, 2536 points, le; 2537 2538 2539 if (c.bezierDegree === 1) { 2540 if (i === c.numberPoints - 1) { 2541 i--; 2542 } 2543 t = position; 2544 } else if (c.bezierDegree === 3) { 2545 // i is start of the Bezier segment 2546 // t is the position in the Bezier segment 2547 if (c.elType === 'sector') { 2548 points = c.points.slice(3, c.numberPoints - 3); 2549 le = points.length; 2550 } else { 2551 points = c.points; 2552 le = points.length; 2553 } 2554 i = Math.floor((position * (le - 1)) / 3) * 3; 2555 t = (position * (le - 1) - i) / 3; 2556 if (i >= le - 1) { 2557 i = le - 4; 2558 t = 1; 2559 } 2560 } else { 2561 return 0; 2562 } 2563 2564 if (i < 0) { 2565 return 1; 2566 } 2567 2568 lbda = t - i; 2569 if (c.bezierDegree === 1) { 2570 p1 = c.points[i].usrCoords; 2571 p2 = c.points[i + 1].usrCoords; 2572 p_org = [ 2573 p1[0] + lbda * (p2[0] - p1[0]), 2574 p1[1] + lbda * (p2[1] - p1[1]), 2575 p1[2] + lbda * (p2[2] - p1[2]) 2576 ]; 2577 li = Mat.crossProduct(p1, p2); 2578 pp = Mat.crossProduct([1, 0, 0], li); 2579 pp = [pp[0], -pp[2], pp[1]]; 2580 li = Mat.crossProduct(p_org, pp); 2581 2582 } else { 2583 A = points[i].usrCoords; 2584 B = points[i + 1].usrCoords; 2585 C = points[i + 2].usrCoords; 2586 D = points[i + 3].usrCoords; 2587 dx = 2588 (1 - t) * (1 - t) * (B[1] - A[1]) + 2589 2 * (1 - t) * t * (C[1] - B[1]) + 2590 t * t * (D[1] - C[1]); 2591 dy = 2592 (1 - t) * (1 - t) * (B[2] - A[2]) + 2593 2 * (1 - t) * t * (C[2] - B[2]) + 2594 t * t * (D[2] - C[2]); 2595 d = Mat.hypot(dx, dy); 2596 dx /= d; 2597 dy /= d; 2598 p1 = p.coords.usrCoords; 2599 p2 = [1, p1[1] - dy, p1[2] + dx]; 2600 2601 li = [ 2602 p1[2] * p2[1] - p1[1] * p2[2], 2603 p2[2] - p1[2], 2604 p1[1] - p2[1] 2605 ]; 2606 } 2607 2608 switch (num) { 2609 case 0: 2610 return li[0]; 2611 case 1: 2612 return li[1]; 2613 case 2: 2614 return li[2]; 2615 default: 2616 return li; 2617 } 2618 }; 2619 2620 l = board.create( 2621 "line", 2622 [ 2623 function () { 2624 var t; 2625 2626 if (p.type === Const.OBJECT_TYPE_GLIDER) { 2627 t = p.position; 2628 } else { 2629 t = Geometry.projectPointToCurve(p, c, board)[1]; 2630 } 2631 2632 return getCurveNormalDir(t, c); 2633 } 2634 ], 2635 attr 2636 ); 2637 p.addChild(l); 2638 l.glider = p; 2639 } 2640 } else if (c.type === Const.OBJECT_TYPE_TURTLE) { 2641 l = board.create( 2642 "line", 2643 [ 2644 function () { 2645 var el, 2646 j, 2647 i = Math.floor(p.position), 2648 lbda = p.position - i; 2649 2650 // run through all curves of this turtle 2651 for (j = 0; j < c.objects.length; j++) { 2652 el = c.objects[j]; 2653 2654 if (el.type === Const.OBJECT_TYPE_CURVE) { 2655 if (i < el.numberPoints) { 2656 break; 2657 } 2658 2659 i -= el.numberPoints; 2660 } 2661 } 2662 2663 if (i === el.numberPoints - 1) { 2664 i -= 1; 2665 lbda = 1; 2666 } 2667 2668 if (i < 0) { 2669 return 1; 2670 } 2671 2672 return ( 2673 (el.Y(i) + lbda * (el.Y(i + 1) - el.Y(i))) * (el.Y(i) - el.Y(i + 1)) - 2674 (el.X(i) + lbda * (el.X(i + 1) - el.X(i))) * (el.X(i + 1) - el.X(i)) 2675 ); 2676 }, 2677 function () { 2678 var el, 2679 j, 2680 i = Math.floor(p.position); 2681 2682 // run through all curves of this turtle 2683 for (j = 0; j < c.objects.length; j++) { 2684 el = c.objects[j]; 2685 if (el.type === Const.OBJECT_TYPE_CURVE) { 2686 if (i < el.numberPoints) { 2687 break; 2688 } 2689 2690 i -= el.numberPoints; 2691 } 2692 } 2693 2694 if (i === el.numberPoints - 1) { 2695 i -= 1; 2696 } 2697 2698 if (i < 0) { 2699 return 0; 2700 } 2701 2702 return el.X(i + 1) - el.X(i); 2703 }, 2704 function () { 2705 var el, 2706 j, 2707 i = Math.floor(p.position); 2708 2709 // run through all curves of this turtle 2710 for (j = 0; j < c.objects.length; j++) { 2711 el = c.objects[j]; 2712 if (el.type === Const.OBJECT_TYPE_CURVE) { 2713 if (i < el.numberPoints) { 2714 break; 2715 } 2716 2717 i -= el.numberPoints; 2718 } 2719 } 2720 2721 if (i === el.numberPoints - 1) { 2722 i -= 1; 2723 } 2724 2725 if (i < 0) { 2726 return 0; 2727 } 2728 2729 return el.Y(i + 1) - el.Y(i); 2730 } 2731 ], 2732 attr 2733 ); 2734 } else { 2735 throw new Error( 2736 "JSXGraph: Can't create normal with parent types '" + 2737 typeof parents[0] + 2738 "' and '" + 2739 typeof parents[1] + 2740 "'." + 2741 "\nPossible parent types: [point,line], [point,circle], [glider]" 2742 ); 2743 } 2744 2745 l.elType = 'normal'; 2746 l.setParents(parents); 2747 2748 if (Type.exists(p._is_new)) { 2749 l.addChild(p); 2750 delete p._is_new; 2751 } else { 2752 p.addChild(l); 2753 } 2754 c.addChild(l); 2755 2756 return l; 2757 }; 2758 2759 /** 2760 * @class The radical axis is the line connecting the two interstion points of two circles with distinct centers. 2761 * The angular bisector of the polar lines of the circle centers with respect to the other circle is always the radical axis. 2762 * The radical axis passes through the intersection points when the circles intersect. 2763 * 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. 2764 * @pseudo 2765 * @name RadicalAxis 2766 * @augments JXG.Line 2767 * @constructor 2768 * @type JXG.Line 2769 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 2770 * @param {JXG.Circle} circle one of the two respective circles. 2771 * @param {JXG.Circle} circle the other of the two respective circles. 2772 * @example 2773 * // Create the radical axis line with respect to two circles 2774 * var board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-5018d0d17a98', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 2775 * var p1 = board.create('point', [2, 3]); 2776 * var p2 = board.create('point', [1, 4]); 2777 * var c1 = board.create('circle', [p1, p2]); 2778 * var p3 = board.create('point', [6, 5]); 2779 * var p4 = board.create('point', [8, 6]); 2780 * var c2 = board.create('circle', [p3, p4]); 2781 * var r1 = board.create('radicalaxis', [c1, c2]); 2782 * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-5018d0d17a98" class="jxgbox" style="width:400px; height:400px;"></div> 2783 * <script type='text/javascript'> 2784 * var rlex1_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-5018d0d17a98', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false}); 2785 * var rlex1_p1 = rlex1_board.create('point', [2, 3]); 2786 * var rlex1_p2 = rlex1_board.create('point', [1, 4]); 2787 * var rlex1_c1 = rlex1_board.create('circle', [rlex1_p1, rlex1_p2]); 2788 * var rlex1_p3 = rlex1_board.create('point', [6, 5]); 2789 * var rlex1_p4 = rlex1_board.create('point', [8, 6]); 2790 * var rlex1_c2 = rlex1_board.create('circle', [rlex1_p3, rlex1_p4]); 2791 * var rlex1_r1 = rlex1_board.create('radicalaxis', [rlex1_c1, rlex1_c2]); 2792 * </script><pre> 2793 */ 2794 JXG.createRadicalAxis = function (board, parents, attributes) { 2795 var el, el1, el2; 2796 2797 if ( 2798 parents.length !== 2 || 2799 parents[0].elementClass !== Const.OBJECT_CLASS_CIRCLE || 2800 parents[1].elementClass !== Const.OBJECT_CLASS_CIRCLE 2801 ) { 2802 // Failure 2803 throw new Error( 2804 "JSXGraph: Can't create 'radical axis' with parent types '" + 2805 typeof parents[0] + 2806 "' and '" + 2807 typeof parents[1] + 2808 "'." + 2809 "\nPossible parent type: [circle,circle]" 2810 ); 2811 } 2812 2813 el1 = board.select(parents[0]); 2814 el2 = board.select(parents[1]); 2815 2816 el = board.create( 2817 "line", 2818 [ 2819 function () { 2820 var a = el1.stdform, 2821 b = el2.stdform; 2822 2823 return Mat.matVecMult(Mat.transpose([a.slice(0, 3), b.slice(0, 3)]), [ 2824 b[3], 2825 -a[3] 2826 ]); 2827 } 2828 ], 2829 attributes 2830 ); 2831 2832 el.elType = 'radicalaxis'; 2833 el.setParents([el1.id, el2.id]); 2834 2835 el1.addChild(el); 2836 el2.addChild(el); 2837 2838 return el; 2839 }; 2840 2841 /** 2842 * @class The polar line of a point with respect to a conic or a circle. 2843 * @pseudo 2844 * @description The polar line is the unique reciprocal relationship of a point with respect to a conic. 2845 * The lines through the intersections of a conic and the polar line of a point 2846 * with respect to that conic and through that point are tangent to the conic. 2847 * A point on a conic has the polar line of that point with respect to that 2848 * conic as the tangent line to that conic at that point. 2849 * See {@link https://en.wikipedia.org/wiki/Pole_and_polar} for more information on pole and polar. 2850 * @name PolarLine 2851 * @augments JXG.Line 2852 * @constructor 2853 * @type JXG.Line 2854 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 2855 * @param {JXG.Conic,JXG.Circle_JXG.Point} el1,el2 or 2856 * @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. 2857 * @example 2858 * // Create the polar line of a point with respect to a conic 2859 * var p1 = board.create('point', [-1, 2]); 2860 * var p2 = board.create('point', [ 1, 4]); 2861 * var p3 = board.create('point', [-1,-2]); 2862 * var p4 = board.create('point', [ 0, 0]); 2863 * var p5 = board.create('point', [ 4,-2]); 2864 * var c1 = board.create('conic',[p1,p2,p3,p4,p5]); 2865 * var p6 = board.create('point', [-1, 1]); 2866 * var l1 = board.create('polarline', [c1, p6]); 2867 * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-6018d0d17a98" class="jxgbox" style="width:400px; height:400px;"></div> 2868 * <script type='text/javascript'> 2869 * var plex1_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-6018d0d17a98', {boundingbox: [-3, 5, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 2870 * var plex1_p1 = plex1_board.create('point', [-1, 2]); 2871 * var plex1_p2 = plex1_board.create('point', [ 1, 4]); 2872 * var plex1_p3 = plex1_board.create('point', [-1,-2]); 2873 * var plex1_p4 = plex1_board.create('point', [ 0, 0]); 2874 * var plex1_p5 = plex1_board.create('point', [ 4,-2]); 2875 * var plex1_c1 = plex1_board.create('conic',[plex1_p1,plex1_p2,plex1_p3,plex1_p4,plex1_p5]); 2876 * var plex1_p6 = plex1_board.create('point', [-1, 1]); 2877 * var plex1_l1 = plex1_board.create('polarline', [plex1_c1, plex1_p6]); 2878 * </script><pre> 2879 * @example 2880 * // Create the polar line of a point with respect to a circle. 2881 * var p1 = board.create('point', [ 1, 1]); 2882 * var p2 = board.create('point', [ 2, 3]); 2883 * var c1 = board.create('circle',[p1,p2]); 2884 * var p3 = board.create('point', [ 6, 6]); 2885 * var l1 = board.create('polarline', [c1, p3]); 2886 * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-7018d0d17a98" class="jxgbox" style="width:400px; height:400px;"></div> 2887 * <script type='text/javascript'> 2888 * var plex2_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-7018d0d17a98', {boundingbox: [-3, 7, 7, -3], axis: true, showcopyright: false, shownavigation: false}); 2889 * var plex2_p1 = plex2_board.create('point', [ 1, 1]); 2890 * var plex2_p2 = plex2_board.create('point', [ 2, 3]); 2891 * var plex2_c1 = plex2_board.create('circle',[plex2_p1,plex2_p2]); 2892 * var plex2_p3 = plex2_board.create('point', [ 6, 6]); 2893 * var plex2_l1 = plex2_board.create('polarline', [plex2_c1, plex2_p3]); 2894 * </script><pre> 2895 */ 2896 JXG.createPolarLine = function (board, parents, attributes) { 2897 var el, 2898 el1, 2899 el2, 2900 firstParentIsConic, 2901 secondParentIsConic, 2902 firstParentIsPoint, 2903 secondParentIsPoint; 2904 2905 if (parents.length > 1) { 2906 firstParentIsConic = 2907 parents[0].type === Const.OBJECT_TYPE_CONIC || 2908 parents[0].elementClass === Const.OBJECT_CLASS_CIRCLE; 2909 secondParentIsConic = 2910 parents[1].type === Const.OBJECT_TYPE_CONIC || 2911 parents[1].elementClass === Const.OBJECT_CLASS_CIRCLE; 2912 2913 firstParentIsPoint = Type.isPoint(parents[0]); 2914 secondParentIsPoint = Type.isPoint(parents[1]); 2915 } 2916 2917 if ( 2918 parents.length !== 2 || 2919 !( 2920 (firstParentIsConic && secondParentIsPoint) || 2921 (firstParentIsPoint && secondParentIsConic) 2922 ) 2923 ) { 2924 // Failure 2925 throw new Error( 2926 "JSXGraph: Can't create 'polar line' with parent types '" + 2927 typeof parents[0] + 2928 "' and '" + 2929 typeof parents[1] + 2930 "'." + 2931 "\nPossible parent type: [conic|circle,point], [point,conic|circle]" 2932 ); 2933 } 2934 2935 if (secondParentIsPoint) { 2936 el1 = board.select(parents[0]); 2937 el2 = board.select(parents[1]); 2938 } else { 2939 el1 = board.select(parents[1]); 2940 el2 = board.select(parents[0]); 2941 } 2942 2943 // Polar lines have been already provided in the tangent element. 2944 el = board.create("tangent", [el1, el2], attributes); 2945 2946 el.elType = 'polarline'; 2947 return el; 2948 }; 2949 2950 /** 2951 * 2952 * @class One of the two tangent lines to a conic or a circle through an external point. 2953 * @pseudo 2954 * @description Construct the tangent line through a point to a conic or a circle. There will be either two, one or no 2955 * such tangent, depending if the point is outside of the conic, on the conic, or inside of the conic. 2956 * Similar to the intersection of a line with a circle, the specific tangent can be chosen with a third (optional) parameter 2957 * <i>number</i>. 2958 * <p> 2959 * Attention: from a technical point of view, the point from which the tangent to the conic/circle is constructed is not an element of 2960 * the tangent line. 2961 * @name TangentTo 2962 * @augments JXG.Line 2963 * @constructor 2964 * @type JXG.Line 2965 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 2966 * @param {JXG.Conic,JXG.Circle_JXG.Point_Number} conic,point,[number=0] The result will be the tangent line through 2967 * the point with respect to the conic or circle. 2968 * 2969 * @example 2970 * var c = board.create('circle', [[3, 0], [3, 4]]); 2971 * var p = board.create('point', [0, 6]); 2972 * var t0 = board.create('tangentto', [c, p, 0], { color: 'black', polar: {visible: true}, point: {visible: true} }); 2973 * var t1 = board.create('tangentto', [c, p, 1], { color: 'black' }); 2974 * 2975 * </pre><div id="JXGd4b359c7-3a29-44c3-a19d-d51b42a00c8b" class="jxgbox" style="width: 300px; height: 300px;"></div> 2976 * <script type="text/javascript"> 2977 * (function() { 2978 * var board = JXG.JSXGraph.initBoard('JXGd4b359c7-3a29-44c3-a19d-d51b42a00c8b', 2979 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 2980 * var c = board.create('circle', [[3, 0], [3, 4]]); 2981 * var p = board.create('point', [0, 6]); 2982 * var t0 = board.create('tangentto', [c, p, 0], { color: 'black', polar: {visible: true}, point: {visible: true} }); 2983 * var t1 = board.create('tangentto', [c, p, 1], { color: 'black' }); 2984 * 2985 * })(); 2986 * 2987 * </script><pre> 2988 * 2989 * @example 2990 * var p = board.create('point', [0, 6]); 2991 * var ell = board.create('ellipse', [[-5, 1], [-2, -1], [-3, 2]]); 2992 * var t0 = board.create('tangentto', [ell, p, 0]); 2993 * var t1 = board.create('tangentto', [ell, p, 1]); 2994 * 2995 * </pre><div id="JXG6e625663-1c3e-4e08-a9df-574972a374e8" class="jxgbox" style="width: 300px; height: 300px;"></div> 2996 * <script type="text/javascript"> 2997 * (function() { 2998 * var board = JXG.JSXGraph.initBoard('JXG6e625663-1c3e-4e08-a9df-574972a374e8', 2999 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 3000 * var p = board.create('point', [0, 6]); 3001 * var ell = board.create('ellipse', [[-5, 1], [-2, -1], [-3, 2]]); 3002 * var t0 = board.create('tangentto', [ell, p, 0]); 3003 * var t1 = board.create('tangentto', [ell, p, 1]); 3004 * 3005 * })(); 3006 * 3007 * </script><pre> 3008 * 3009 */ 3010 JXG.createTangentTo = function (board, parents, attributes) { 3011 var el, attr, 3012 conic, pointFrom, num, 3013 intersect, polar; 3014 3015 conic = board.select(parents[0]); 3016 pointFrom = Type.providePoints(board, parents[1], attributes, 'point')[0]; 3017 num = Type.def(parents[2], 0); 3018 3019 if ( 3020 (conic.type !== Const.OBJECT_TYPE_CIRCLE && conic.type !== Const.OBJECT_TYPE_CONIC) || 3021 (pointFrom.elementClass !== Const.OBJECT_CLASS_POINT) 3022 ) { 3023 throw new Error( 3024 "JSXGraph: Can't create tangentto with parent types '" + 3025 typeof parents[0] + 3026 "' and '" + 3027 typeof parents[1] + 3028 "' and '" + 3029 typeof parents[2] + 3030 "'." + 3031 "\nPossible parent types: [circle|conic,point,number]" 3032 ); 3033 } 3034 3035 attr = Type.copyAttributes(attributes, board.options, 'tangentto'); 3036 // A direct analytic geometry approach would be in 3037 // Richter-Gebert: Perspectives on projective geometry, 11.3 3038 polar = board.create('polar', [conic, pointFrom], attr.polar); 3039 intersect = board.create('intersection', [polar, conic, num], attr.point); 3040 3041 el = board.create('tangent', [conic, intersect], attr); 3042 3043 /** 3044 * The intersection point of the conic/circle with the polar line of the tangentto construction. 3045 * @memberOf TangentTo.prototype 3046 * @name point 3047 * @type JXG.Point 3048 */ 3049 el.point = intersect; 3050 3051 /** 3052 * The polar line of the tangentto construction. 3053 * @memberOf TangentTo.prototype 3054 * @name polar 3055 * @type JXG.Line 3056 */ 3057 el.polar = polar; 3058 3059 el.elType = 'tangentto'; 3060 3061 return el; 3062 }; 3063 3064 /** 3065 * Register the element type tangent at JSXGraph 3066 * @private 3067 */ 3068 JXG.registerElement("tangent", JXG.createTangent); 3069 JXG.registerElement("normal", JXG.createNormal); 3070 JXG.registerElement('tangentto', JXG.createTangentTo); 3071 JXG.registerElement("polar", JXG.createTangent); 3072 JXG.registerElement("radicalaxis", JXG.createRadicalAxis); 3073 JXG.registerElement("polarline", JXG.createPolarLine); 3074 3075 export default JXG.Line; 3076 // export default { 3077 // Line: JXG.Line, 3078 // createLine: JXG.createLine, 3079 // createTangent: JXG.createTangent, 3080 // createPolar: JXG.createTangent, 3081 // createSegment: JXG.createSegment, 3082 // createAxis: JXG.createAxis, 3083 // createArrow: JXG.createArrow, 3084 // createRadicalAxis: JXG.createRadicalAxis, 3085 // createPolarLine: JXG.createPolarLine 3086 // }; 3087