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