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