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