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