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