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