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