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