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