1 /* 2 Copyright 2008-2011 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: you can redistribute it and/or modify 13 it under the terms of the GNU Lesser General Public License as published by 14 the Free Software Foundation, either version 3 of the License, or 15 (at your option) any later version. 16 17 JSXGraph is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 GNU Lesser General Public License for more details. 21 22 You should have received a copy of the GNU Lesser General Public License 23 along with JSXGraph. If not, see <http://www.gnu.org/licenses/>. 24 */ 25 26 /** 27 * @fileoverview The geometry object Line is defined in this file. Line stores all 28 * style and functional properties that are required to draw and move a line on 29 * a board. 30 */ 31 32 /** 33 * 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 34 * be intersected with some other geometry elements. 35 * @class Creates a new basic line object. Do not use this constructor to create a line. Use {@link JXG.Board#create} with 36 * type {@link Line}, {@link Arrow}, or {@link Axis} instead. 37 * @constructor 38 * @augments JXG.GeometryElement 39 * @param {String,JXG.Board} board The board the new line is drawn on. 40 * @param {Point} p1 Startpoint of the line. 41 * @param {Point} p2 Endpoint of the line. 42 * @param {String} id Unique identifier for this object. If null or an empty string is given, 43 * an unique id will be generated by Board 44 * @param {String} name Not necessarily unique name. If null or an 45 * empty string is given, an unique name will be generated. 46 * @param {Boolean} withLabel construct label, yes/no 47 * @param {Number} layer display layer [0-9] 48 * @see JXG.Board#generateName 49 */ 50 JXG.Line = function (board, p1, p2, attributes) { 51 this.constructor(board, attributes, JXG.OBJECT_TYPE_LINE, JXG.OBJECT_CLASS_LINE); 52 53 /** 54 * Startpoint of the line. You really should not set this field directly as it may break JSXGraph's 55 * udpate system so your construction won't be updated properly. 56 * @type JXG.Point 57 */ 58 this.point1 = JXG.getReference(this.board, p1); 59 60 /** 61 * Endpoint of the line. Just like {@link #point1} you shouldn't write this field directly. 62 * @type JXG.Point 63 */ 64 this.point2 = JXG.getReference(this.board, p2); 65 66 /** 67 * Array of ticks storing all the ticks on this line. Do not set this field directly and use 68 * {@link JXG.Line#addTicks} and {@link JXG.Line#removeTicks} to add and remove ticks to and from the line. 69 * @type Array 70 * @see JXG.Ticks 71 */ 72 this.ticks = []; 73 74 /** 75 * Reference of the ticks created automatically when constructing an axis. 76 * @type JXG.Ticks 77 * @see JXG.Ticks 78 */ 79 this.defaultTicks = null; 80 81 /** 82 * If the line is the border of a polygon, the polygon object is stored, otherwise null. 83 * @type JXG.Polygon 84 * @default null 85 * @private 86 */ 87 this.parentPolygon = null; 88 89 /* Register line at board */ 90 this.id = this.board.setId(this, 'L'); 91 this.board.renderer.drawLine(this); 92 this.board.finalizeAdding(this); 93 94 this.elType = 'line'; 95 96 /* Add arrow as child to defining points */ 97 this.point1.addChild(this); 98 this.point2.addChild(this); 99 100 101 this.updateStdform(); // This is needed in the following situation: 102 // * the line is defined by three coordinates 103 // * and it will have a glider 104 // * and board.suspendUpdate() has been called. 105 106 // create Label 107 this.createLabel(); 108 }; 109 110 JXG.Line.prototype = new JXG.GeometryElement; 111 112 113 JXG.extend(JXG.Line.prototype, /** @lends JXG.Line.prototype */ { 114 /** 115 * Checks whether (x,y) is near the line. 116 * @param {Number} x Coordinate in x direction, screen coordinates. 117 * @param {Number} y Coordinate in y direction, screen coordinates. 118 * @return {Boolean} True if (x,y) is near the line, False otherwise. 119 */ 120 hasPoint: function (x, y) { 121 // Compute the stdform of the line in screen coordinates. 122 var c = [], s, 123 v = [1, x, y], 124 vnew, 125 p1c, p2c, d, pos, i; 126 127 c[0] = this.stdform[0] - 128 this.stdform[1]*this.board.origin.scrCoords[1]/this.board.unitX+ 129 this.stdform[2]*this.board.origin.scrCoords[2]/this.board.unitY; 130 c[1] = this.stdform[1]/this.board.unitX; 131 c[2] = this.stdform[2]/(-this.board.unitY); 132 133 /* 134 135 // The point is too far away from the line 136 // dist(v,vnew)^2 projective 137 s = (v[1]-vnew[1])*(v[1]-vnew[1])+(v[2]-vnew[2])*(v[2]-vnew[2]); 138 */ 139 140 s = JXG.Math.Geometry.distPointLine(v, c); 141 if (isNaN(s) || s>this.board.options.precision.hasPoint) { 142 return false; 143 } 144 145 if(this.visProp.straightfirst && this.visProp.straightlast) { 146 return true; 147 } else { // If the line is a ray or segment we have to check if the projected point is between P1 and P2. 148 p1c = this.point1.coords; 149 p2c = this.point2.coords; 150 // Project the point orthogonally onto the line 151 vnew = [0, c[1], c[2]]; 152 vnew = JXG.Math.crossProduct(vnew, v); // Orthogonal line to c through v 153 vnew = JXG.Math.crossProduct(vnew, c); // Intersect orthogonal line with line 154 155 // Normalize the projected point 156 vnew[1] /= vnew[0]; 157 vnew[2] /= vnew[0]; 158 vnew[0] = 1.0; 159 160 vnew = (new JXG.Coords(JXG.COORDS_BY_SCREEN, vnew.slice(1), this.board)).usrCoords; 161 d = p1c.distance(JXG.COORDS_BY_USER, p2c); 162 p1c = p1c.usrCoords.slice(0); 163 p2c = p2c.usrCoords.slice(0); 164 if (d<JXG.Math.eps) { // The defining points are identical 165 pos = 0.0; 166 } else { 167 /* 168 * Handle the cases, where one of the defining points is an ideal point. 169 * d is set to something close to infinity, namely 1/eps. 170 * The ideal point is (temporarily) replaced by a finite point which has 171 * distance d from the other point. 172 * This is accomplishrd by extracting the x- and y-coordinates (x,y)=:v of the ideal point. 173 * v determines the direction of the line. v is normalized, i.e. set to length 1 by deividing through its length. 174 * Finally, the new point is the sum of the other point and v*d. 175 * 176 */ 177 if (d==Number.POSITIVE_INFINITY) { // At least one point is an ideal point 178 d = 1.0/JXG.Math.eps; 179 if (Math.abs(p2c[0])<JXG.Math.eps) { // The second point is an ideal point 180 d /= JXG.Math.Geometry.distance([0,0,0], p2c); 181 p2c = [1, p1c[1]+p2c[1]*d, p1c[2]+p2c[2]*d]; 182 } else { // The first point is an ideal point 183 d /= JXG.Math.Geometry.distance([0,0,0], p1c); 184 p1c = [1, p2c[1]+p1c[1]*d, p2c[2]+p1c[2]*d]; 185 } 186 } 187 i = 1; 188 d = p2c[i] - p1c[i]; 189 if (Math.abs(d)<JXG.Math.eps) { 190 i = 2; 191 d = p2c[i] - p1c[i]; 192 } 193 pos = (vnew[i] - p1c[i]) / d; 194 } 195 if(!this.visProp.straightfirst && pos<0) { 196 return false; 197 } 198 if(!this.visProp.straightlast && pos>1.0) { 199 return false; 200 } 201 return true; 202 } 203 }, 204 205 /** 206 * TODO description. maybe. already documented in geometryelement? 207 * @private 208 */ 209 update: function() { 210 var funps; 211 212 if (!this.needsUpdate) { return this; } 213 214 if(this.constrained) { 215 if(typeof this.funps != 'undefined') { 216 funps = this.funps(); 217 if (funps && funps.length && funps.length === 2) { 218 this.point1 = funps[0]; 219 this.point2 = funps[1]; 220 } 221 } else { 222 if (typeof this.funp1 === 'function') { 223 funps = this.funp1(); 224 if (JXG.isPoint(funps)) { 225 this.point1 = funps; 226 } else if (funps && funps.length && funps.length === 2) { 227 this.point1.setPositionDirectly(JXG.COORDS_BY_USER, funps); 228 } 229 } 230 if (typeof this.funp2 === 'function') { 231 funps = this.funp2(); 232 if (JXG.isPoint(funps)) { 233 this.point2 = funps; 234 } else if (funps && funps.length && funps.length === 2) { 235 this.point2.setPositionDirectly(JXG.COORDS_BY_USER, funps); 236 } 237 } 238 } 239 } 240 241 this.updateSegmentFixedLength(); 242 243 this.updateStdform(); 244 245 if(this.visProp.trace) { 246 this.cloneToBackground(true); 247 } 248 return this; 249 }, 250 251 /** 252 * Update segments with fixed length and at least one movable point. 253 * @private 254 */ 255 updateSegmentFixedLength: function() { 256 var d, dnew, d1, d2, drag1, drag2, x, y; 257 // 258 if (!this.hasFixedLength) { return this; } 259 260 // Compute the actual length of the segment 261 d = this.point1.Dist(this.point2); 262 // Determine the length the segment ought to have 263 dnew = this.fixedLength(); 264 // Distances between the two points and their respective 265 // position before the update 266 d1 = this.fixedLengthOldCoords[0].distance(JXG.COORDS_BY_USER, this.point1.coords); 267 d2 = this.fixedLengthOldCoords[1].distance(JXG.COORDS_BY_USER, this.point2.coords); 268 269 // If the position of the points or the fixed length function has been changed 270 // we have to work. 271 if (d1>JXG.Math.eps || d2>JXG.Math.eps || d!=dnew) { 272 drag1 = this.point1.isDraggable && (this.point1.type != JXG.OBJECT_TYPE_GLIDER) && !this.point1.visProp.fixed; 273 drag2 = this.point2.isDraggable && (this.point2.type != JXG.OBJECT_TYPE_GLIDER) && !this.point2.visProp.fixed; 274 275 // First case: the two points are different 276 // Then we try to adapt the point that was not dragged 277 // If this point can not be moved (e.g. because it is a glider) 278 // we try move the other point 279 if (d>JXG.Math.eps) { 280 if ((d1>d2 && drag2) || 281 (d1<=d2 && drag2 && !drag1)) { 282 this.point2.setPositionDirectly(JXG.COORDS_BY_USER, 283 [this.point1.X() + (this.point2.X()-this.point1.X())*dnew/d, 284 this.point1.Y() + (this.point2.Y()-this.point1.Y())*dnew/d] 285 ); 286 this.point2.prepareUpdate().updateRenderer(); 287 } else if ((d1<=d2 && drag1) || 288 (d1>d2 && drag1 && !drag2)) { 289 this.point1.setPositionDirectly(JXG.COORDS_BY_USER, 290 [this.point2.X() + (this.point1.X()-this.point2.X())*dnew/d, 291 this.point2.Y() + (this.point1.Y()-this.point2.Y())*dnew/d] 292 ); 293 this.point1.prepareUpdate().updateRenderer(); 294 } 295 // Second case: the two points are identical. In this situation 296 // we choose a random direction. 297 } else { 298 x = Math.random()-0.5; 299 y = Math.random()-0.5; 300 d = Math.sqrt(x*x+y*y); 301 if (drag2) { 302 this.point2.setPositionDirectly(JXG.COORDS_BY_USER, 303 [this.point1.X() + x*dnew/d, 304 this.point1.Y() + y*dnew/d] 305 ); 306 this.point2.prepareUpdate().updateRenderer(); 307 } else if (drag1) { 308 this.point1.setPositionDirectly(JXG.COORDS_BY_USER, 309 [this.point2.X() + x*dnew/d, 310 this.point2.Y() + y*dnew/d] 311 ); 312 this.point1.prepareUpdate().updateRenderer(); 313 } 314 } 315 // Finally, we save the position of the two points. 316 this.fixedLengthOldCoords[0].setCoordinates(JXG.COORDS_BY_USER, this.point1.coords.usrCoords); 317 this.fixedLengthOldCoords[1].setCoordinates(JXG.COORDS_BY_USER, this.point2.coords.usrCoords); 318 } 319 return this; 320 }, 321 322 /** 323 * TODO description. already documented in geometryelement? 324 * @private 325 */ 326 updateStdform: function() { 327 var v = JXG.Math.crossProduct(this.point1.coords.usrCoords, this.point2.coords.usrCoords); 328 this.stdform[0] = v[0]; 329 this.stdform[1] = v[1]; 330 this.stdform[2] = v[2]; 331 this.stdform[3] = 0; 332 this.normalize(); 333 }, 334 335 /** 336 * Uses the boards renderer to update the line. 337 * @private 338 */ 339 updateRenderer: function () { 340 var wasReal; 341 342 if (this.needsUpdate && this.visProp.visible) { 343 wasReal = this.isReal; 344 this.isReal = ( 345 !isNaN(this.point1.coords.usrCoords[1] + 346 this.point1.coords.usrCoords[2] + 347 this.point2.coords.usrCoords[1] + 348 this.point2.coords.usrCoords[2] 349 ) 350 && (JXG.Math.innerProduct(this.stdform,this.stdform,3)>=JXG.Math.eps*JXG.Math.eps) 351 ); 352 if (this.isReal) { 353 if (wasReal!=this.isReal) { 354 this.board.renderer.show(this); 355 if(this.hasLabel && this.label.content.visProp.visible) this.board.renderer.show(this.label.content); 356 } 357 this.board.renderer.updateLine(this); 358 } else { 359 if (wasReal!=this.isReal) { 360 this.board.renderer.hide(this); 361 if(this.hasLabel && this.label.content.visProp.visible) this.board.renderer.hide(this.label.content); 362 } 363 } 364 365 //this.board.renderer.updateLine(this); // Why should we need this? 366 this.needsUpdate = false; 367 } 368 369 /* Update the label if visible. */ 370 if(this.hasLabel && this.label.content.visProp.visible && this.isReal) { 371 //this.label.setCoordinates(this.coords); 372 this.label.content.update(); 373 //this.board.renderer.updateLabel(this.label); 374 this.board.renderer.updateText(this.label.content); 375 } 376 return this; 377 }, 378 379 /** 380 * Used to generate a polynomial for a point p that lies on this line, i.e. p is collinear to {@link #point1} 381 * and {@link #point2}. 382 * @param p The point for that the polynomial is generated. 383 * @return An array containing the generated polynomial. 384 * @private 385 */ 386 generatePolynomial: function (/** JXG.Point */ p) /** array */{ 387 var u1 = this.point1.symbolic.x, 388 u2 = this.point1.symbolic.y, 389 v1 = this.point2.symbolic.x, 390 v2 = this.point2.symbolic.y, 391 w1 = p.symbolic.x, 392 w2 = p.symbolic.y; 393 394 /* 395 * The polynomial in this case is determined by three points being collinear: 396 * 397 * U (u1,u2) W (w1,w2) V (v1,v2) 398 * ----x--------------x------------------------x---------------- 399 * 400 * The collinearity condition is 401 * 402 * u2-w2 w2-v2 403 * ------- = ------- (1) 404 * u1-w1 w1-v1 405 * 406 * Multiplying (1) with denominators and simplifying is 407 * 408 * u2w1 - u2v1 + w2v1 - u1w2 + u1v2 - w1v2 = 0 409 */ 410 411 return [['(',u2,')*(',w1,')-(',u2,')*(',v1,')+(',w2,')*(',v1,')-(',u1,')*(',w2,')+(',u1,')*(',v2,')-(',w1,')*(',v2,')'].join('')]; 412 }, 413 414 /** 415 * Calculates the y intersect of the line. 416 * @type Number 417 * @return The y intersect. 418 */ 419 getRise: function () { 420 if (Math.abs(this.stdform[2])>=JXG.Math.eps) { 421 return -this.stdform[0]/this.stdform[2]; 422 } else { 423 return Infinity; 424 } 425 }, 426 427 /** 428 * Calculates the slope of the line. 429 * @type Number 430 * @return The slope of the line or Infinity if the line is parallel to the y-axis. 431 */ 432 getSlope: function () { 433 if (Math.abs(this.stdform[2])>=JXG.Math.eps) { 434 return -this.stdform[1]/this.stdform[2]; 435 } else { 436 return Infinity; 437 } 438 }, 439 440 /** 441 * Determines the angle between the positive x axis and the line. 442 * @returns {Number} 443 */ 444 getAngle: function () { 445 return Math.atan2(this.point2.Y() - this.point1.Y(), this.point2.X() - this.point1.X()); 446 }, 447 448 /** 449 * Determines whether the line is drawn beyond {@link #point1} and {@link #point2} and updates the line. 450 * @param {Boolean} straightFirst True if the Line shall be drawn beyond {@link #point1}, false otherwise. 451 * @param {Boolean} straightLast True if the Line shall be drawn beyond {@link #point2}, false otherwise. 452 * @see #straightFirst 453 * @see #straightLast 454 * @private 455 */ 456 setStraight: function (straightFirst, straightLast) { 457 this.visProp.straightfirst = straightFirst; 458 this.visProp.straightlast = straightLast; 459 460 this.board.renderer.updateLine(this); 461 return this; 462 }, 463 464 // documented in geometry element 465 getTextAnchor: function() { 466 return new JXG.Coords(JXG.COORDS_BY_USER, [0.5*(this.point2.X() + this.point1.X()), 0.5*(this.point2.Y() + this.point1.Y())],this.board); 467 }, 468 469 /** 470 * Adjusts Label coords relative to Anchor. DESCRIPTION 471 * @private 472 */ 473 setLabelRelativeCoords: function(relCoords) { 474 if (JXG.exists(this.label.content)) { 475 this.label.content.relativeCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [relCoords[0],-relCoords[1]], this.board); 476 } 477 }, 478 479 // documented in geometry element 480 getLabelAnchor: function() { 481 var x, y, 482 fs = 0, 483 sx = 0, 484 sy = 0, 485 c1 = new JXG.Coords(JXG.COORDS_BY_USER, this.point1.coords.usrCoords, this.board), 486 c2 = new JXG.Coords(JXG.COORDS_BY_USER, this.point2.coords.usrCoords, this.board); 487 488 if (this.visProp.straightfirst || this.visProp.straightlast) { 489 JXG.Math.Geometry.calcStraight(this, c1, c2, 0); 490 } 491 c1 = c1.scrCoords; 492 c2 = c2.scrCoords; 493 494 if (!JXG.exists(this.label.content)) { 495 return new JXG.Coords(JXG.COORDS_BY_SCREEN, [NaN, NaN], this.board); 496 } 497 498 switch (this.label.content.visProp.position) { 499 case 'lft': 500 case 'llft': 501 case 'ulft': 502 if (c1[1] <= c2[1]) { 503 x = c1[1]; 504 y = c1[2]; 505 } else { 506 x = c2[1]; 507 y = c2[2]; 508 } 509 break; 510 case 'rt': 511 case 'lrt': 512 case 'urt': 513 if (c1[1] > c2[1]) { 514 x = c1[1]; 515 y = c1[2]; 516 } else { 517 x = c2[1]; 518 y = c2[2]; 519 } 520 break; 521 default: 522 x = 0.5*(c1[1] + c2[1]); 523 y = 0.5*(c1[2] + c2[2]); 524 } 525 526 if (this.visProp.straightfirst || this.visProp.straightlast) { 527 if (JXG.exists(this.label.content)) { // Does not exist during createLabel 528 sx = parseFloat(this.label.content.visProp.offset[0]); 529 sy = parseFloat(this.label.content.visProp.offset[1]); 530 fs = this.label.content.visProp.fontsize; 531 } 532 533 if (Math.abs(x)<JXG.Math.eps) { 534 x = sx; 535 //} else if (Math.abs(x-this.board.canvasWidth) < JXG.Math.eps) { 536 } else if (this.board.canvasWidth+JXG.Math.eps>x && x>this.board.canvasWidth-fs-JXG.Math.eps) { 537 x = this.board.canvasWidth - sx - fs; 538 } else { 539 x += sx; 540 } 541 542 if (JXG.Math.eps+fs > y && y > -JXG.Math.eps) { 543 y = sy + fs; 544 } else if (this.board.canvasHeight+JXG.Math.eps > y && y > this.board.canvasHeight-fs-JXG.Math.eps) { 545 y = this.board.canvasHeight - sy; 546 } else { 547 y += sy; 548 } 549 } 550 return new JXG.Coords(JXG.COORDS_BY_SCREEN, [x, y], this.board); 551 }, 552 553 // documented in geometry element 554 cloneToBackground: function() { 555 var copy = {}, r, s, er; 556 557 copy.id = this.id + 'T' + this.numTraces; 558 copy.elementClass = JXG.OBJECT_CLASS_LINE; 559 this.numTraces++; 560 copy.point1 = this.point1; 561 copy.point2 = this.point2; 562 563 copy.stdform = this.stdform; 564 565 copy.board = this.board; 566 567 copy.visProp = JXG.deepCopy(this.visProp, this.visProp.traceattributes, true); 568 copy.visProp.layer = this.board.options.layer.trace; 569 JXG.clearVisPropOld(copy); 570 571 s = this.getSlope(); 572 r = this.getRise(); 573 copy.getSlope = function() { return s; }; 574 copy.getRise = function() { return r; }; 575 576 er = this.board.renderer.enhancedRendering; 577 this.board.renderer.enhancedRendering = true; 578 this.board.renderer.drawLine(copy); 579 this.board.renderer.enhancedRendering = er; 580 this.traces[copy.id] = copy.rendNode; 581 582 delete copy; 583 584 return this; 585 }, 586 587 /** 588 * Add transformations to this line. 589 * @param {JXG.Transform|Array} transform Either one {@link JXG.Transform} or an array of {@link JXG.Transform}s. 590 * @returns {JXG.Line} Reference to this line object. 591 */ 592 addTransform: function (transform) { 593 var i, 594 list = JXG.isArray(transform) ? transform : [transform], 595 len = list.length; 596 597 for (i = 0; i < len; i++) { 598 this.point1.transformations.push(list[i]); 599 this.point2.transformations.push(list[i]); 600 } 601 602 return this; 603 }, 604 605 /** 606 * Apply a translation by <tt>tv = (x, y)</tt> to the line. 607 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 608 * @param {Array} tv (x, y) 609 * @returns {JXG.Line} Reference to this line object. 610 */ 611 setPosition: function (method, tv) { 612 var t; 613 614 tv = new JXG.Coords(method, tv, this.board); 615 t = this.board.create('transform', tv.usrCoords.slice(1), {type:'translate'}); 616 617 if (this.point1.transformations.length > 0 && this.point1.transformations[this.point1.transformations.length - 1].isNumericMatrix) { 618 this.point1.transformations[this.point1.transformations.length - 1].melt(t); 619 } else { 620 this.point1.addTransform(this.point1, t); 621 } 622 if (this.point2.transformations.length > 0 && this.point2.transformations[this.point2.transformations.length - 1].isNumericMatrix) { 623 this.point2.transformations[this.point2.transformations.length - 1].melt(t); 624 } else { 625 this.point2.addTransform(this.point2, t); 626 } 627 628 return this; 629 }, 630 631 /** 632 * Moves the line by the difference of two coordinates. 633 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 634 * @param {Array} coords coordinates in screen/user units 635 * @param {Array} oldcoords previous coordinates in screen/user units 636 * @returns {JXG.Line} 637 */ 638 setPositionDirectly: function (method, coords, oldcoords) { 639 var dc, t, 640 c = new JXG.Coords(method, coords, this.board), 641 oldc = new JXG.Coords(method, oldcoords, this.board); 642 643 if (!this.point1.draggable() || !this.point2.draggable()) { 644 return this; 645 } 646 647 dc = JXG.Math.Statistics.subtract(c.usrCoords, oldc.usrCoords); 648 t = this.board.create('transform', dc.slice(1), {type:'translate'}); 649 t.applyOnce([this.point1, this.point2] ); 650 651 return this; 652 }, 653 654 // see geometryelement.js 655 snapToGrid: function () { 656 if (this.visProp.snaptogrid) { 657 this.point1.snapToGrid(); 658 this.point2.snapToGrid(); 659 } 660 661 return this; 662 }, 663 664 /** 665 * Treat the line as parametric curve in homogeneous coordinates, where the parameter t runs from 0 to 1. 666 * First we transform the interval [0,1] to [-1,1]. 667 * If the line has homogeneous coordinates [c,a,b] = stdform[] then the direction of the line is [b,-a]. 668 * 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). 669 * Let the coordinates of that point be [z, x, y]. 670 * Then, the curve runs linearly from 671 * [0, b, -a] (t=-1) to [z, x, y] (t=0) 672 * and 673 * [z, x, y] (t=0) to [0, -b, a] (t=1) 674 * 675 * @param {Number} t Parameter running from 0 to 1. 676 * @returns {Number} X(t) x-coordinate of the line treated as parametric curve. 677 * */ 678 X: function (t) { 679 var b = this.stdform[2], x; 680 681 x = (Math.abs(this.point1.coords.usrCoords[0])>JXG.Math.eps) ? 682 this.point1.coords.usrCoords[1] : this.point2.coords.usrCoords[1]; 683 t = (t-0.5)*2.0; 684 if (t<0.0) { 685 t *= (-1); 686 return (1.0-t)*x + t*b; 687 } else { 688 return (1.0-t)*x - t*b; 689 } 690 }, 691 692 /** 693 * Treat the line as parametric curve in homogeneous coordinates. See {@link #X} for a detailed description. 694 * @param {Number} t Parameter running from 0 to 1. 695 * @returns {Number} Y(t) y-coordinate of the line treated as parametric curve. 696 */ 697 Y: function (t) { 698 var a = this.stdform[1], y; 699 700 y = (Math.abs(this.point1.coords.usrCoords[0])>JXG.Math.eps) ? 701 this.point1.coords.usrCoords[2] : this.point2.coords.usrCoords[2]; 702 t = (t-0.5)*2.0; 703 if (t<0.0) { 704 t *= (-1); 705 return (1.0-t)*y - t*a; 706 } else { 707 return (1.0-t)*y + t*a; 708 } 709 }, 710 711 /** 712 * Treat the line as parametric curve in homogeneous coordinates. See {@link #X} for a detailed description. 713 * @param {Number} t Parameter running from 0 to 1. 714 * @returns {Number} Z(t) z-coordinate of the line treated as parametric curve. 715 */ 716 Z: function (t) { 717 var z = (Math.abs(this.point1.coords.usrCoords[0])>JXG.Math.eps) ? 718 this.point1.coords.usrCoords[0] : this.point2.coords.usrCoords[0]; 719 720 t = (t-0.5)*2.0; 721 if (t<0.0) { 722 t *= (-1); 723 } 724 return (1.0-t) * z; 725 }, 726 727 728 /* 729 * This is needed for GEONExT compatibility 730 * @type Number 731 * @return the distance between the two points defining the line 732 */ 733 L: function() { 734 return this.point1.Dist(this.point2); 735 }, 736 737 /** 738 * TODO circle?!? --michael 739 * private or public? --michael 740 * Treat the circle as parametric curve: 741 * t runs from 0 to 1 742 * @private 743 */ 744 minX: function () { 745 return 0.0; 746 }, 747 748 /** 749 * TODO circle?!? --michael 750 * private or public? --michael 751 * Treat the circle as parametric curve: 752 * t runs from 0 to 1 753 * @private 754 */ 755 maxX: function () { 756 return 1.0; 757 }, 758 759 // documented in geometry element 760 bounds: function () { 761 var p1c = this.point1.coords.usrCoords, 762 p2c = this.point2.coords.usrCoords; 763 764 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])]; 765 }, 766 767 /** 768 * Adds ticks to this line. Ticks can be added to any kind of line: line, arrow, and axis. 769 * @param {JXG.Ticks} ticks Reference to a ticks object which is describing the ticks (color, distance, how many, etc.). 770 * @type String 771 * @return Id of the ticks object. 772 */ 773 addTicks: function(ticks) { 774 if(ticks.id == '' || typeof ticks.id == 'undefined') 775 ticks.id = this.id + '_ticks_' + (this.ticks.length+1); 776 777 this.board.renderer.drawTicks(ticks); 778 this.ticks.push(ticks); 779 780 return ticks.id; 781 }, 782 783 // documented in GeometryElement.js 784 remove: function () { 785 this.removeAllTicks(); 786 JXG.GeometryElement.prototype.remove.call(this); 787 }, 788 789 /** 790 * Removes all ticks from a line. 791 */ 792 removeAllTicks: function() { 793 var i, t; 794 795 for(t = this.ticks.length; t > 0; t--) { 796 this.removeTicks(this.ticks[t-1]); 797 } 798 799 this.ticks = new Array(); 800 this.board.update(); 801 }, 802 803 /** 804 * Removes ticks identified by parameter named tick from this line. 805 * @param {JXG.Ticks} tick Reference to tick object to remove. 806 */ 807 removeTicks: function(tick) { 808 var t, j; 809 if(this.defaultTicks != null && this.defaultTicks == tick) { 810 this.defaultTicks = null; 811 } 812 813 for(t = this.ticks.length; t > 0; t--) { 814 if(this.ticks[t-1] == tick) { 815 this.board.removeObject(this.ticks[t-1]); 816 817 if (this.ticks[t-1].ticks) { 818 for(j = 0; j < this.ticks[t-1].ticks.length; j++) { 819 if(this.ticks[t-1].labels[j] != null) { 820 this.board.removeObject(this.ticks[t-1].labels[j]); 821 } 822 } 823 } 824 delete(this.ticks[t-1]); 825 break; 826 } 827 } 828 }, 829 830 hideElement: function () { 831 var i; 832 833 JXG.GeometryElement.prototype.hideElement.call(this); 834 835 for (i = 0; i < this.ticks.length; i++) { 836 this.ticks[i].hideElement(); 837 } 838 }, 839 840 showElement: function () { 841 var i; 842 843 JXG.GeometryElement.prototype.showElement.call(this); 844 845 for (i = 0; i < this.ticks.length; i++) { 846 this.ticks[i].showElement(); 847 } 848 } 849 }); 850 851 /** 852 * @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 853 * a line can be used as an arrow and/or axis. 854 * @pseudo 855 * @description 856 * @name Line 857 * @augments JXG.Line 858 * @constructor 859 * @type JXG.Line 860 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 861 * @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 862 * numbers describing the coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 863 * It is possible to provide a function returning an array or a point, instead of providing an array or a point. 864 * @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 865 * 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. 866 * @param {function} f This function must return an array containing three numbers forming the line's homogeneous coordinates. 867 * @example 868 * // Create a line using point and coordinates/ 869 * // The second point will be fixed and invisible. 870 * var p1 = board.create('point', [4.5, 2.0]); 871 * var l1 = board.create('line', [p1, [1.0, 1.0]]); 872 * </pre><div id="c0ae3461-10c4-4d39-b9be-81d74759d122" style="width: 300px; height: 300px;"></div> 873 * <script type="text/javascript"> 874 * var glex1_board = JXG.JSXGraph.initBoard('c0ae3461-10c4-4d39-b9be-81d74759d122', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 875 * var glex1_p1 = glex1_board.create('point', [4.5, 2.0]); 876 * var glex1_l1 = glex1_board.create('line', [glex1_p1, [1.0, 1.0]]); 877 * </script><pre> 878 * @example 879 * // Create a line using three coordinates 880 * var l1 = board.create('line', [1.0, -2.0, 3.0]); 881 * </pre><div id="cf45e462-f964-4ba4-be3a-c9db94e2593f" style="width: 300px; height: 300px;"></div> 882 * <script type="text/javascript"> 883 * var glex2_board = JXG.JSXGraph.initBoard('cf45e462-f964-4ba4-be3a-c9db94e2593f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 884 * var glex2_l1 = glex2_board.create('line', [1.0, -2.0, 3.0]); 885 * </script><pre> 886 */ 887 JXG.createLine = function(board, parents, attributes) { 888 var el, p1, p2, i, attr, 889 c = [], 890 constrained = false, 891 isDraggable; 892 893 /** 894 * The line is defined by two points or coordinates of two points. 895 * In the latter case, the points are created. 896 */ 897 if (parents.length == 2) { 898 // point 1 given by coordinates 899 if (JXG.isArray(parents[0]) && parents[0].length>1) { 900 attr = JXG.copyAttributes(attributes, board.options, 'line', 'point1'); 901 p1 = board.create('point', parents[0], attr); 902 } else if (JXG.isString(parents[0]) || parents[0].elementClass == JXG.OBJECT_CLASS_POINT) { 903 p1 = JXG.getReference(board,parents[0]); 904 } else if ((typeof parents[0] == 'function') && (parents[0]().elementClass == JXG.OBJECT_CLASS_POINT)) { 905 p1 = parents[0](); 906 constrained = true; 907 } else if ((typeof parents[0] == 'function') && (parents[0]().length && parents[0]().length === 2)) { 908 attr = JXG.copyAttributes(attributes, board.options, 'line', 'point1'); 909 p1 = JXG.createPoint(board, parents[0](), attr); 910 constrained = true; 911 } else 912 throw new Error("JSXGraph: Can't create line with parent types '" + 913 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 914 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"); 915 916 // point 2 given by coordinates 917 if (JXG.isArray(parents[1]) && parents[1].length>1) { 918 attr = JXG.copyAttributes(attributes, board.options, 'line', 'point2'); 919 p2 = board.create('point', parents[1], attr); 920 } else if (JXG.isString(parents[1]) || parents[1].elementClass == JXG.OBJECT_CLASS_POINT) { 921 p2 = JXG.getReference(board, parents[1]); 922 } else if ((typeof parents[1] == 'function') && (parents[1]().elementClass == JXG.OBJECT_CLASS_POINT)) { 923 p2 = parents[1](); 924 constrained = true; 925 } else if ((typeof parents[1] == 'function') && (parents[1]().length && parents[1]().length === 2)) { 926 attr = JXG.copyAttributes(attributes, board.options, 'line', 'point2'); 927 p2 = JXG.createPoint(board, parents[1](), attr); 928 constrained = true; 929 } else 930 throw new Error("JSXGraph: Can't create line with parent types '" + 931 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 932 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"); 933 934 attr = JXG.copyAttributes(attributes, board.options, 'line'); 935 936 el = new JXG.Line(board, p1, p2, attr); 937 if (constrained) { 938 el.constrained = true; 939 el.funp1 = parents[0]; 940 el.funp2 = parents[1]; 941 } else { 942 el.isDraggable = true; 943 } 944 945 if (!el.constrained) { 946 el.parents = [p1.id, p2.id]; 947 } 948 } 949 /** 950 * Line is defined by three homogeneous coordinates. 951 * Also in this case points are created. 952 */ 953 else if (parents.length==3) { 954 // free line 955 isDraggable = true; 956 for (i=0;i<3;i++) { 957 if (typeof parents[i]=='number') { 958 c[i] = function(z){ return function() { return z; }; }(parents[i]); 959 } else if (typeof parents[i]=='function') { 960 c[i] = parents[i]; 961 isDraggable = false; 962 } else { 963 throw new Error("JSXGraph: Can't create line with parent types '" + 964 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2])+ "'." + 965 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"); 966 } 967 } 968 // point 1 is the midpoint between (0,c,-b) and point 2. => point1 is finite. 969 attr = JXG.copyAttributes(attributes, board.options, 'line', 'point1'); 970 if (isDraggable) { 971 p1 = board.create('point',[c[2]()*c[2]()+c[1]()*c[1](), c[2]()-c[1]()*c[0]()+c[2](), -c[1]()-c[2]()*c[0]()-c[1]()], attr); 972 } else { 973 p1 = board.create('point',[ 974 function() { return (0.0 + c[2]()*c[2]()+c[1]()*c[1]())*0.5;}, 975 function() { return (c[2]() - c[1]()*c[0]()+c[2]())*0.5;}, 976 function() { return (-c[1]() - c[2]()*c[0]()-c[1]())*0.5;}], attr); 977 } 978 /* 979 */ 980 // point 2: (b^2+c^2,-ba+c,-ca-b) 981 attr = JXG.copyAttributes(attributes, board.options, 'line', 'point2'); 982 if (isDraggable) { 983 p2 = board.create('point',[c[2]()*c[2]()+c[1]()*c[1](), -c[1]()*c[0]()+c[2](), -c[2]()*c[0]()-c[1]()], attr); 984 } else { 985 p2 = board.create('point',[ 986 function() { return c[2]()*c[2]()+c[1]()*c[1]();}, 987 function() { return -c[1]()*c[0]()+c[2]();}, 988 function() { return -c[2]()*c[0]()-c[1]();}], attr); 989 } 990 991 // If the line will have a glider 992 // and board.suspendUpdate() has been called, we 993 // need to compute the initial position of the two points p1 and p2. 994 p1.prepareUpdate().update(); 995 p2.prepareUpdate().update(); 996 attr = JXG.copyAttributes(attributes, board.options, 'line'); 997 el = new JXG.Line(board, p1, p2, attr); 998 el.isDraggable = isDraggable; // Not yet working, because the points are not draggable. 999 1000 if (isDraggable) { 1001 el.parents = [c[0](), c[1](), c[2]()]; 1002 } 1003 } 1004 /** 1005 * The parent array contains a function which returns two points. 1006 */ 1007 else if ((parents.length==1) && (typeof parents[0] == 'function') && (parents[0]().length == 2) && 1008 (parents[0]()[0].elementClass == JXG.OBJECT_CLASS_POINT) && (parents[0]()[1].elementClass == JXG.OBJECT_CLASS_POINT)) { 1009 var ps = parents[0](); 1010 attr = JXG.copyAttributes(attributes, board.options, 'line'); 1011 el = new JXG.Line(board, ps[0], ps[1], attr); 1012 el.constrained = true; 1013 el.funps = parents[0]; 1014 } else if ((parents.length==1) && (typeof parents[0] == 'function') && (parents[0]().length == 3) && 1015 (typeof parents[0]()[0] === 'number') && (typeof parents[0]()[1] === 'number') && (typeof parents[0]()[2] === 'number')) { 1016 ps = parents[0]; 1017 1018 attr = JXG.copyAttributes(attributes, board.options, 'line', 'point1'); 1019 p1 = board.create('point',[ 1020 function () { 1021 var c = ps(); 1022 return [ 1023 (0.0 + c[2]*c[2]+c[1]*c[1])*0.5, 1024 (c[2] - c[1]*c[0]+c[2])*0.5, 1025 (-c[1] - c[2]*c[0]-c[1])*0.5 1026 ]; 1027 }], attr); 1028 1029 attr = JXG.copyAttributes(attributes, board.options, 'line', 'point2'); 1030 p2 = board.create('point',[ 1031 function () { 1032 var c = ps(); 1033 return [ 1034 c[2]*c[2]+c[1]*c[1], 1035 -c[1]*c[0]+c[2], 1036 -c[2]*c[0]-c[1] 1037 ]; 1038 }], attr); 1039 1040 attr = JXG.copyAttributes(attributes, board.options, 'line'); 1041 el = new JXG.Line(board, p1, p2, attr); 1042 1043 el.constrained = true; 1044 el.funps = parents[0]; 1045 } else { 1046 throw new Error("JSXGraph: Can't create line with parent types '" + 1047 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1048 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"); 1049 } 1050 1051 return el; 1052 }; 1053 1054 JXG.JSXGraph.registerElement('line', JXG.createLine); 1055 1056 /** 1057 * @class This element is used to provide a constructor for a segment. 1058 * It's strictly spoken just a wrapper for element {@link Line} with {@link JXG.Line#straightFirst} 1059 * and {@link JXG.Line#straightLast} properties set to false. If there is a third variable then the 1060 * segment has a fixed length (which may be a function, too). 1061 * @pseudo 1062 * @description 1063 * @name Segment 1064 * @augments JXG.Line 1065 * @constructor 1066 * @type JXG.Line 1067 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1068 * @param {JXG.Point,array_JXG.Point,array} point1, point2 Parent elements can be two elements either of type {@link JXG.Point} 1069 * or array of numbers describing the 1070 * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 1071 * @param {number,function} length (optional) The points are adapted - if possible - such that their distance 1072 * has a this value. 1073 * @see Line 1074 * @example 1075 * // Create a segment providing two points. 1076 * var p1 = board.create('point', [4.5, 2.0]); 1077 * var p2 = board.create('point', [1.0, 1.0]); 1078 * var l1 = board.create('segment', [p1, p2]); 1079 * </pre><div id="d70e6aac-7c93-4525-a94c-a1820fa38e2f" style="width: 300px; height: 300px;"></div> 1080 * <script type="text/javascript"> 1081 * var slex1_board = JXG.JSXGraph.initBoard('d70e6aac-7c93-4525-a94c-a1820fa38e2f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1082 * var slex1_p1 = slex1_board.create('point', [4.5, 2.0]); 1083 * var slex1_p2 = slex1_board.create('point', [1.0, 1.0]); 1084 * var slex1_l1 = slex1_board.create('segment', [slex1_p1, slex1_p2]); 1085 * </script><pre> 1086 * 1087 * @example 1088 * // Create a segment providing two points. 1089 * var p1 = board.create('point', [4.0, 1.0]); 1090 * var p2 = board.create('point', [1.0, 1.0]); 1091 * var l1 = board.create('segment', [p1, p2]); 1092 * var p3 = board.create('point', [4.0, 2.0]); 1093 * var p4 = board.create('point', [1.0, 2.0]); 1094 * var l2 = board.create('segment', [p3, p4, 3]); 1095 * var p5 = board.create('point', [4.0, 3.0]); 1096 * var p6 = board.create('point', [1.0, 4.0]); 1097 * var l3 = board.create('segment', [p5, p6, function(){ return l1.L();} ]); 1098 * </pre><div id="617336ba-0705-4b2b-a236-c87c28ef25be" style="width: 300px; height: 300px;"></div> 1099 * <script type="text/javascript"> 1100 * var slex2_board = JXG.JSXGraph.initBoard('617336ba-0705-4b2b-a236-c87c28ef25be', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1101 * var slex2_p1 = slex1_board.create('point', [4.0, 1.0]); 1102 * var slex2_p2 = slex1_board.create('point', [1.0, 1.0]); 1103 * var slex2_l1 = slex1_board.create('segment', [slex1_p1, slex1_p2]); 1104 * var slex2_p3 = slex1_board.create('point', [4.0, 2.0]); 1105 * var slex2_p4 = slex1_board.create('point', [1.0, 2.0]); 1106 * var slex2_l2 = slex1_board.create('segment', [slex1_p3, slex1_p4, 3]); 1107 * var slex2_p5 = slex1_board.create('point', [4.0, 2.0]); 1108 * var slex2_p6 = slex1_board.create('point', [1.0, 2.0]); 1109 * var slex2_l3 = slex1_board.create('segment', [slex1_p5, slex1_p6, function(){ return slex2_l1.L();}]); 1110 * </script><pre> 1111 * 1112 */ 1113 JXG.createSegment = function(board, parents, attributes) { 1114 var el, i, attr; 1115 1116 attributes.straightFirst = false; 1117 attributes.straightLast = false; 1118 attr = JXG.copyAttributes(attributes, board.options, 'segment'); 1119 1120 el = board.create('line', parents.slice(0,2), attr); 1121 1122 if (parents.length==3) { 1123 el.hasFixedLength = true; 1124 if (JXG.isNumber(parents[2])) { 1125 el.fixedLength = function() { return parents[2]; }; 1126 } else if (JXG.isFunction(parents[2])) { 1127 el.fixedLength = parents[2]; 1128 } else { 1129 throw new Error("JSXGraph: Can't create segment with third parent type '" + 1130 (typeof parents[2]) + "'." + 1131 "\nPossible third parent types: number or function"); 1132 } 1133 el.fixedLengthOldCoords = []; 1134 el.fixedLengthOldCoords[0] = new JXG.Coords(JXG.COORDS_BY_USER, el.point1.coords.usrCoords.slice(1,3), board); 1135 el.fixedLengthOldCoords[1] = new JXG.Coords(JXG.COORDS_BY_USER, el.point2.coords.usrCoords.slice(1,3), board); 1136 } 1137 1138 el.elType = 'segment'; 1139 1140 return el; 1141 }; 1142 1143 JXG.JSXGraph.registerElement('segment', JXG.createSegment); 1144 1145 /** 1146 * @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} 1147 * and {@link JXG.Line#straightLast} properties set to false and {@link JXG.Line#lastArrow} set to true. 1148 * @pseudo 1149 * @description 1150 * @name Arrow 1151 * @augments JXG.Line 1152 * @constructor 1153 * @type JXG.Line 1154 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1155 * @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 1156 * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 1157 * @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 1158 * of the equation <tt>a*x+b*y+c*z = 0</tt>. 1159 * @see Line 1160 * @example 1161 * // Create an arrow providing two points. 1162 * var p1 = board.create('point', [4.5, 2.0]); 1163 * var p2 = board.create('point', [1.0, 1.0]); 1164 * var l1 = board.create('arrow', [p1, p2]); 1165 * </pre><div id="1d26bd22-7d6d-4018-b164-4c8bc8d22ccf" style="width: 300px; height: 300px;"></div> 1166 * <script type="text/javascript"> 1167 * var alex1_board = JXG.JSXGraph.initBoard('1d26bd22-7d6d-4018-b164-4c8bc8d22ccf', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1168 * var alex1_p1 = alex1_board.create('point', [4.5, 2.0]); 1169 * var alex1_p2 = alex1_board.create('point', [1.0, 1.0]); 1170 * var alex1_l1 = alex1_board.create('arrow', [alex1_p1, alex1_p2]); 1171 * </script><pre> 1172 */ 1173 JXG.createArrow = function(board, parents, attributes) { 1174 var el; 1175 1176 attributes['firstArrow'] = false; 1177 attributes['lastArrow'] = true; 1178 el = board.create('line', parents, attributes).setStraight(false, false); 1179 //el.setArrow(false, true); 1180 el.type = JXG.OBJECT_TYPE_VECTOR; 1181 el.elType = 'arrow'; 1182 1183 return el; 1184 }; 1185 1186 JXG.JSXGraph.registerElement('arrow', JXG.createArrow); 1187 1188 /** 1189 * @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} 1190 * 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. 1191 * @pseudo 1192 * @description 1193 * @name Axis 1194 * @augments JXG.Line 1195 * @constructor 1196 * @type JXG.Line 1197 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1198 * @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 1199 * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 1200 * @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 1201 * of the equation <tt>a*x+b*y+c*z = 0</tt>. 1202 * @example 1203 * // Create an axis providing two coord pairs. 1204 * var l1 = board.create('axis', [[0.0, 1.0], [1.0, 1.3]]); 1205 * </pre><div id="4f414733-624c-42e4-855c-11f5530383ae" style="width: 300px; height: 300px;"></div> 1206 * <script type="text/javascript"> 1207 * var axex1_board = JXG.JSXGraph.initBoard('4f414733-624c-42e4-855c-11f5530383ae', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1208 * var axex1_l1 = axex1_board.create('axis', [[0.0, 1.0], [1.0, 1.3]]); 1209 * </script><pre> 1210 */ 1211 JXG.createAxis = function(board, parents, attributes) { 1212 var attr, 1213 el, 1214 dist; 1215 1216 // Arrays oder Punkte, mehr brauchen wir nicht. 1217 if ( (JXG.isArray(parents[0]) || JXG.isPoint(parents[0]) ) && (JXG.isArray(parents[1]) || JXG.isPoint(parents[1])) ) { 1218 attr = JXG.copyAttributes(attributes, board.options, 'axis'); 1219 el = board.create('line', parents, attr); 1220 el.type = JXG.OBJECT_TYPE_AXIS; 1221 el.isDraggable = false; 1222 el.point1.isDraggable = false; 1223 el.point2.isDraggable = false; 1224 1225 for (var els in el.ancestors) 1226 el.ancestors[els].type = JXG.OBJECT_TYPE_AXISPOINT; 1227 1228 attr = JXG.copyAttributes(attributes, board.options, 'axis', 'ticks'); 1229 if (JXG.exists(attr.ticksdistance)) { 1230 dist = attr.ticksdistance; 1231 } else if(JXG.isArray(attr.ticks)) { 1232 dist = attr.ticks; 1233 } else { 1234 dist = 1.0; 1235 } 1236 1237 /** 1238 * The ticks attached to the axis. 1239 * @memberOf Axis.prototype 1240 * @name defaultTicks 1241 * @type JXG.Ticks 1242 */ 1243 el.defaultTicks = board.create('ticks', [el, dist], attr); 1244 1245 el.defaultTicks.dump = false; 1246 1247 el.elType = 'axis'; 1248 el.subs = { 1249 ticks: el.defaultTicks 1250 }; 1251 } 1252 else 1253 throw new Error("JSXGraph: Can't create axis with parent types '" + 1254 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1255 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]"); 1256 1257 return el; 1258 }; 1259 1260 JXG.JSXGraph.registerElement('axis', JXG.createAxis); 1261 1262 /** 1263 * @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 1264 * by a glider on a line, circle, or curve and describes the tangent in the glider point on that line, circle, or curve. 1265 * @pseudo 1266 * @description 1267 * @name Tangent 1268 * @augments JXG.Line 1269 * @constructor 1270 * @type JXG.Line 1271 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1272 * @param {Glider} g A glider on a line, circle, or curve. 1273 * @example 1274 * // Create a tangent providing a glider on a function graph 1275 * var c1 = board.create('curve', [function(t){return t},function(t){return t*t*t;}]); 1276 * var g1 = board.create('glider', [0.6, 1.2, c1]); 1277 * var t1 = board.create('tangent', [g1]); 1278 * </pre><div id="7b7233a0-f363-47dd-9df5-4018d0d17a98" style="width: 400px; height: 400px;"></div> 1279 * <script type="text/javascript"> 1280 * var tlex1_board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-4018d0d17a98', {boundingbox: [-6, 6, 6, -6], axis: true, showcopyright: false, shownavigation: false}); 1281 * var tlex1_c1 = tlex1_board.create('curve', [function(t){return t},function(t){return t*t*t;}]); 1282 * var tlex1_g1 = tlex1_board.create('glider', [0.6, 1.2, tlex1_c1]); 1283 * var tlex1_t1 = tlex1_board.create('tangent', [tlex1_g1]); 1284 * </script><pre> 1285 */ 1286 JXG.createTangent = function(board, parents, attributes) { 1287 var p, 1288 c, 1289 g, f, i, j, el, tangent; 1290 1291 if (parents.length==1) { // One arguments: glider on line, circle or curve 1292 p = parents[0]; 1293 c = p.slideObject; 1294 } else if (parents.length==2) { // Two arguments: (point,F"|conic) or (line|curve|circle|conic,point). // Not yet: curve! 1295 if (JXG.isPoint(parents[0])) { // In fact, for circles and conics it is the polar. 1296 p = parents[0]; 1297 c = parents[1]; 1298 } else if (JXG.isPoint(parents[1])) { 1299 c = parents[0]; 1300 p = parents[1]; 1301 } else { 1302 throw new Error("JSXGraph: Can't create tangent with parent types '" + 1303 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1304 "\nPossible parent types: [glider], [point,line|curve|circle|conic]"); 1305 } 1306 } else { 1307 throw new Error("JSXGraph: Can't create tangent with parent types '" + 1308 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1309 "\nPossible parent types: [glider], [point,line|curve|circle|conic]"); 1310 } 1311 1312 if (c.elementClass == JXG.OBJECT_CLASS_LINE) { 1313 tangent = board.create('line', [c.point1,c.point2], attributes); 1314 } else if (c.elementClass == JXG.OBJECT_CLASS_CURVE && !(c.type == JXG.OBJECT_TYPE_CONIC)) { 1315 if (c.visProp.curvetype!='plot') { 1316 g = c.X; 1317 f = c.Y; 1318 tangent = board.create('line', [ 1319 function(){ return -p.X()*board.D(f)(p.position)+p.Y()*board.D(g)(p.position);}, 1320 function(){ return board.D(f)(p.position);}, 1321 function(){ return -board.D(g)(p.position);} 1322 ], attributes ); 1323 p.addChild(tangent); 1324 // this is required for the geogebra reader to display a slope 1325 tangent.glider = p; 1326 } else { // curveType 'plot' 1327 // equation of the line segment: 0 = y*(x1-x2) + x*(y2-y1) + y1*x2-x1*y2 1328 tangent = board.create('line', [ 1329 function(){ i=Math.floor(p.position); 1330 if (i==c.numberPoints-1) i--; 1331 if (i<0) return 1.0; 1332 return c.Y(i)*c.X(i+1)-c.X(i)*c.Y(i+1);}, 1333 function(){ i=Math.floor(p.position); 1334 if (i==c.numberPoints-1) i--; 1335 if (i<0) return 0.0; 1336 return c.Y(i+1)-c.Y(i);}, 1337 function(){ i=Math.floor(p.position); 1338 if (i==c.numberPoints-1) i--; 1339 if (i<0) return 0.0; 1340 return c.X(i)-c.X(i+1);} 1341 ], attributes ); 1342 p.addChild(tangent); 1343 // this is required for the geogebra reader to display a slope 1344 tangent.glider = p; 1345 } 1346 } else if (c.type == JXG.OBJECT_TYPE_TURTLE) { 1347 tangent = board.create('line', [ 1348 function(){ i=Math.floor(p.position); 1349 for(j=0;j<c.objects.length;j++) { // run through all curves of this turtle 1350 el = c.objects[j]; 1351 if (el.type==JXG.OBJECT_TYPE_CURVE) { 1352 if (i<el.numberPoints) break; 1353 i-=el.numberPoints; 1354 } 1355 } 1356 if (i==el.numberPoints-1) i--; 1357 if (i<0) return 1.0; 1358 return el.Y(i)*el.X(i+1)-el.X(i)*el.Y(i+1);}, 1359 function(){ i=Math.floor(p.position); 1360 for(j=0;j<c.objects.length;j++) { // run through all curves of this turtle 1361 el = c.objects[j]; 1362 if (el.type==JXG.OBJECT_TYPE_CURVE) { 1363 if (i<el.numberPoints) break; 1364 i-=el.numberPoints;moveTo(funps); 1365 } 1366 } 1367 if (i==el.numberPoints-1) i--; 1368 if (i<0) return 0.0; 1369 return el.Y(i+1)-el.Y(i);}, 1370 function(){ i=Math.floor(p.position); 1371 for(j=0;j<c.objects.length;j++) { // run through all curves of this turtle 1372 el = c.objects[j]; 1373 if (el.type==JXG.OBJECT_TYPE_CURVE) { 1374 if (i<el.numberPoints) break; 1375 i-=el.numberPoints; 1376 } 1377 } 1378 if (i==el.numberPoints-1) i--; 1379 if (i<0) return 0.0; 1380 return el.X(i)-el.X(i+1);} 1381 ], attributes ); 1382 p.addChild(tangent); 1383 // this is required for the geogebra reader to display a slope 1384 tangent.glider = p; 1385 } else if (c.elementClass == JXG.OBJECT_CLASS_CIRCLE || c.type == JXG.OBJECT_TYPE_CONIC) { 1386 // If p is not on c, the tangent is the polar. 1387 // This construction should work on conics, too. p has to lie on c. 1388 tangent = board.create('line', [ 1389 function(){ return JXG.Math.matVecMult(c.quadraticform,p.coords.usrCoords)[0]; }, 1390 function(){ return JXG.Math.matVecMult(c.quadraticform,p.coords.usrCoords)[1]; }, 1391 function(){ return JXG.Math.matVecMult(c.quadraticform,p.coords.usrCoords)[2]; } 1392 ], attributes); 1393 1394 p.addChild(tangent); 1395 // this is required for the geogebra reader to display a slope 1396 tangent.glider = p; 1397 } 1398 1399 if (!JXG.exists(tangent)) { 1400 throw new Error('JSXGraph: Couldn\'t create tangent with the given parents.'); 1401 } 1402 1403 tangent.elType = 'tangent'; 1404 tangent.parents = []; 1405 for (i = 0; i < parents.length; i++) { 1406 tangent.parents.push(parents[i].id); 1407 } 1408 1409 return tangent; 1410 }; 1411 1412 /** 1413 * Register the element type tangent at JSXGraph 1414 * @private 1415 */ 1416 JXG.JSXGraph.registerElement('tangent', JXG.createTangent); 1417 JXG.JSXGraph.registerElement('polar', JXG.createTangent); 1418 // vim: et ts=4 1419