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, id, name, withLabel, layer) { 51 /* Call the constructor of GeometryElement */ 52 this.constructor(); 53 54 /** 55 * Sets type of GeometryElement, value is OBJECT_TYPE_LINE. 56 * @constant 57 * @type int 58 * @default JXG#OBJECT_TYPE_LINE 59 * @private 60 */ 61 this.type = JXG.OBJECT_TYPE_LINE; 62 63 /** 64 * Class of element, value is OBJECT_CLASS_LINE; 65 * @type int 66 * @constant 67 * @default JXG#OBJECT_CLASS_LINE 68 * @private 69 */ 70 this.elementClass = JXG.OBJECT_CLASS_LINE; 71 72 this.init(board, id, name); 73 74 /** 75 * Set the display layer. 76 */ 77 if (layer == null) layer = board.options.layer['line']; 78 this.layer = layer; 79 80 /** 81 * Startpoint of the line. You really should not set this field directly as it may break JSXGraph's 82 * udpate system so your construction won't be updated properly. 83 * @type JXG.Point 84 */ 85 this.point1 = JXG.getReference(this.board, p1); 86 87 /** 88 * Endpoint of the line. Just like {@link #point1} you shouldn't write this field directly. 89 * @type JXG.Point 90 */ 91 this.point2 = JXG.getReference(this.board, p2); 92 93 /** 94 * This is just for the hasPoint() method. 95 * @type int 96 * @private 97 */ 98 //this.r = this.board.options.precision.hasPoint; 99 100 /* Wurde in GeometryElement schon dokumentiert. */ 101 this.visProp['fillColor'] = this.board.options.line.fillColor; 102 this.visProp['highlightFillColor'] = this.board.options.line.highlightFillColor; 103 this.visProp['strokeColor'] = this.board.options.line.strokeColor; 104 this.visProp['highlightStrokeColor'] = this.board.options.line.highlightStrokeColor; 105 106 /** 107 * Determines if a line is drawn beyond {@link #point1}. 108 * @name JXG.Line#straightFirst 109 * @type boolean 110 * @default JXG.Options.line#straightFirst 111 * @field 112 * @see JXG.Line#straightLast 113 */ 114 this.visProp['straightFirst'] = this.board.options.line.straightFirst; 115 116 /** 117 * Determines if a line is drawn beyond {@link #point2}. 118 * @name JXG.Line#straightLast 119 * @type boolean 120 * @default JXG.Options.line#straightLast 121 * @field 122 * @see JXG.Line#straightFirst 123 */ 124 this.visProp['straightLast'] = this.board.options.line.straightLast; 125 126 /* Already documented in JXG.GeometryElement */ 127 this.visProp['visible'] = true; 128 129 /** 130 * Determines if a line has an arrow at {@link #point1}. 131 * @name JXG.Line#firstArrow 132 * @type boolean 133 * @default JXG.Options.line#firstArrow 134 * @field 135 * @see JXG.Line#lastArrow 136 */ 137 this.visProp['firstArrow'] = this.board.options.line.firstArrow; 138 139 /** 140 * Determines if a line has an arrow at {@link #point1}. 141 * @name JXG.Line#lastArrow 142 * @type boolean 143 * @default JXG.Options.line#lastArrow 144 * @field 145 * @see JXG.Line#firstArrow 146 */ 147 this.visProp['lastArrow'] = this.board.options.line.lastArrow; 148 149 /** 150 * Array of ticks storing all the ticks on this line. Do not set this field directly and use 151 * {@link #addTicks} and {@link #removeTicks} to add and remove ticks to and from the line. 152 * @type array 153 * @see JXG.Ticks 154 */ 155 this.ticks = []; 156 157 /** 158 * Reference of the ticks created automatically when constructing an axis. 159 * @type JXG.Ticks 160 * @see JXG.Ticks 161 */ 162 this.defaultTicks = null; 163 164 /** 165 * If the line is the border of a polygon, the polygon object is stored, otherwise null. 166 * @type JXG.Polygon 167 * @default null 168 * @private 169 */ 170 this.parentPolygon = null; 171 172 /** 173 * Label offsets from label anchor 174 * @type array 175 * @default JXG.Options.line#labelOffsets 176 * @private 177 */ 178 //this.labelOffsets = [].concat(this.board.options.line.labelOffsets); 179 this.labelOffsets = [].concat(this.board.options.line.labelOffsets); 180 //make sure we have absolute values 181 this.labelOffsets[0] = Math.abs(this.labelOffsets[0]); 182 this.labelOffsets[1] = Math.abs(this.labelOffsets[1]); 183 184 // create Label 185 this.createLabel(withLabel); 186 187 /* Register line at board */ 188 this.id = this.board.setId(this, 'L'); 189 this.board.renderer.drawLine(this); 190 this.board.finalizeAdding(this); 191 192 /* Add arrow as child to defining points */ 193 this.point1.addChild(this); 194 this.point2.addChild(this); 195 this.needsUpdate = true; this.update(); 196 }; 197 198 JXG.Line.prototype = new JXG.GeometryElement; 199 200 /** 201 * Checks whether (x,y) is near the line. 202 * @param {int} x Coordinate in x direction, screen coordinates. 203 * @param {int} y Coordinate in y direction, screen coordinates. 204 * @return {boolean} True if (x,y) is near the line, False otherwise. 205 */ 206 JXG.Line.prototype.hasPoint = function (x, y) { 207 // Compute the stdform of the line in screen coordinates. 208 var c = [], s, 209 v = [1,x,y], 210 vnew = [], 211 mu, i, coords, p1Scr, p2Scr, distP1P, distP2P, distP1P2; 212 213 c[0] = this.stdform[0] - 214 this.stdform[1]*this.board.origin.scrCoords[1]/this.board.stretchX+ 215 this.stdform[2]*this.board.origin.scrCoords[2]/this.board.stretchY; 216 c[1] = this.stdform[1]/this.board.stretchX; 217 c[2] = this.stdform[2]/(-this.board.stretchY); 218 219 // Project the point orthogonally onto the line 220 var vnew = [0,c[1],c[2]]; 221 vnew = JXG.Math.crossProduct(vnew,v); // Orthogonal line to c through v 222 vnew = JXG.Math.crossProduct(vnew,c); // Intersect orthogonal line with line 223 224 // Normalize the projected point 225 vnew[1] /= vnew[0]; 226 vnew[2] /= vnew[0]; 227 vnew[0] = 1.0; 228 229 // The point is too far away from the line 230 // dist(v,vnew)^2 projective 231 //if (JXG.Math.Geometry.distance(v,vnew)>this.board.options.precision.hasPoint) { 232 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]); 233 if (isNaN(s) || s>this.board.options.precision.hasPoint*this.board.options.precision.hasPoint) { 234 return false; 235 } 236 237 if(this.visProp['straightFirst'] && this.visProp['straightLast']) { 238 return true; 239 } else { // If the line is a ray or segment we have to check if the projected point is "inside" P1 and P2. 240 /* 241 coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [vnew[1],vnew[2]], this.board); 242 p1Scr = this.point1.coords.scrCoords; 243 p2Scr = this.point2.coords.scrCoords; 244 distP1P = coords.distance(JXG.COORDS_BY_SCREEN, this.point1.coords); 245 distP2P = coords.distance(JXG.COORDS_BY_SCREEN, this.point2.coords); 246 distP1P2 = this.point1.coords.distance(JXG.COORDS_BY_SCREEN, this.point2.coords); 247 */ 248 p1Scr = this.point1.coords.scrCoords; 249 p2Scr = this.point2.coords.scrCoords; 250 distP1P2 = (p2Scr[1]-p1Scr[1])*(p2Scr[1]-p1Scr[1])+(p2Scr[2]-p1Scr[2])*(p2Scr[2]-p1Scr[2]); // dist(p1,p2)^2 affine 251 distP1P = (vnew[1]-p1Scr[1])*(vnew[1]-p1Scr[1])+(vnew[2]-p1Scr[2])*(vnew[2]-p1Scr[2]); // dist(vnew,p1)^2 affine 252 distP2P = (vnew[1]-p2Scr[1])*(vnew[1]-p2Scr[1])+(vnew[2]-p2Scr[2])*(vnew[2]-p2Scr[2]); // dist(vnew,p2)^2 affine 253 254 if((distP1P > distP1P2) || (distP2P > distP1P2)) { // Check if P(x|y) is not between P1 and P2 255 if(distP1P < distP2P) { // P liegt auf der Seite von P1 256 if(!this.visProp['straightFirst']) { 257 return false; 258 } 259 } else { // P liegt auf der Seite von P2 260 if(!this.visProp['straightLast']) { 261 return false; 262 } 263 } 264 } 265 return true; 266 } 267 }; 268 269 /** 270 * TODO description. maybe. already documented in geometryelement? 271 * @private 272 */ 273 JXG.Line.prototype.update = function() { 274 var i, funps; 275 276 if(this.constrained) { 277 if(typeof this.funps != 'undefined') { 278 funps = this.funps(); 279 this.point1 = funps[0]; 280 this.point2 = funps[1]; 281 } else { 282 this.point1 = this.funp1(); 283 this.point2 = this.funp2(); 284 } 285 } 286 287 if (this.needsUpdate) { 288 if (true || !this.board.geonextCompatibilityMode) { 289 this.updateStdform(); 290 } 291 /* 292 // This is now done in Ticks.updateRenderer() 293 for(i=0; i<this.ticks.length; i++) { 294 // i don't know why we need this, but if we don't check it, an error will be reported 295 // when the origin is moved. it seems like this.ticks.length is lying. 296 if(typeof this.ticks[i] != 'undefined') 297 this.ticks[i].calculateTicksCoordinates(); 298 } 299 */ 300 } 301 if(this.traced) { 302 this.cloneToBackground(true); 303 } 304 }; 305 306 /** 307 * TODO description. already documented in geometryelement? 308 * @private 309 */ 310 JXG.Line.prototype.updateStdform = function() { 311 /* 312 var nx = -(this.point2.coords.usrCoords[2]-this.point1.coords.usrCoords[2]); 313 var ny = this.point2.coords.usrCoords[1]-this.point1.coords.usrCoords[1]; 314 var c = -(nx*this.point1.coords.usrCoords[1]+ny*this.point1.coords.usrCoords[2]); 315 316 this.stdform[0] = c; 317 this.stdform[1] = nx; 318 this.stdform[2] = ny; 319 */ 320 var v = JXG.Math.crossProduct(this.point1.coords.usrCoords,this.point2.coords.usrCoords); 321 this.stdform[0] = v[0]; 322 this.stdform[1] = v[1]; 323 this.stdform[2] = v[2]; 324 this.stdform[3] = 0; 325 this.normalize(); 326 }; 327 328 /** 329 * Uses the boards renderer to update the line. 330 * @private 331 */ 332 JXG.Line.prototype.updateRenderer = function () { 333 var wasReal, i; 334 if (this.needsUpdate && this.visProp['visible']) { 335 wasReal = this.isReal; 336 this.isReal = (isNaN(this.point1.coords.usrCoords[1]+this.point1.coords.usrCoords[2]+this.point2.coords.usrCoords[1]+this.point2.coords.usrCoords[2]))?false:true; 337 if (this.isReal) { 338 if (wasReal!=this.isReal) { 339 this.board.renderer.show(this); 340 if(this.hasLabel && this.label.content.visProp['visible']) this.board.renderer.show(this.label.content); 341 } 342 this.board.renderer.updateLine(this); 343 /* 344 if (this.board.options.renderer == 'canvas') { 345 for (i=0;i<this.ticks.length;i++) // This is necessary for the CanvasRenderer 346 this.ticks[i].prepareUpdate().updateRenderer(); // No idea, why other Renderer work without it. 347 } 348 */ 349 } else { 350 if (wasReal!=this.isReal) { 351 this.board.renderer.hide(this); 352 if(this.hasLabel && this.label.content.visProp['visible']) this.board.renderer.hide(this.label.content); 353 } 354 } 355 356 //this.board.renderer.updateLine(this); // Why should we need this? 357 this.needsUpdate = false; 358 } 359 360 /* Update the label if visible. */ 361 if(this.hasLabel && this.label.content.visProp['visible'] && this.isReal) { 362 //this.label.setCoordinates(this.coords); 363 this.label.content.update(); 364 //this.board.renderer.updateLabel(this.label); 365 this.board.renderer.updateText(this.label.content); 366 } 367 }; 368 369 /** 370 * Used to generate a polynomial for a point p that lies on this line, i.e. p is collinear to {@link #point1} 371 * and {@link #point2}. 372 * @param p The point for that the polynomial is generated. 373 * @return An array containing the generated polynomial. 374 * @private 375 */ 376 JXG.Line.prototype.generatePolynomial = function (/** JXG.Point */ p) /** array */{ 377 var u1 = this.point1.symbolic.x, 378 u2 = this.point1.symbolic.y, 379 v1 = this.point2.symbolic.x, 380 v2 = this.point2.symbolic.y, 381 w1 = p.symbolic.x, 382 w2 = p.symbolic.y; 383 384 /* 385 * The polynomial in this case is determined by three points being collinear: 386 * 387 * U (u1,u2) W (w1,w2) V (v1,v2) 388 * ----x--------------x------------------------x---------------- 389 * 390 * The collinearity condition is 391 * 392 * u2-w2 w2-v2 393 * ------- = ------- (1) 394 * u1-w1 w1-v1 395 * 396 * Multiplying (1) with denominators and simplifying is 397 * 398 * u2w1 - u2v1 + w2v1 - u1w2 + u1v2 - w1v2 = 0 399 */ 400 401 return [['(',u2,')*(',w1,')-(',u2,')*(',v1,')+(',w2,')*(',v1,')-(',u1,')*(',w2,')+(',u1,')*(',v2,')-(',w1,')*(',v2,')'].join('')]; 402 }; 403 404 /** 405 * Calculates the rise of the line. 406 * @type float 407 * @return The rise of the line. 408 */ 409 JXG.Line.prototype.getRise = function () { 410 if (Math.abs(this.stdform[2])>=JXG.Math.eps) { 411 return -this.stdform[0]/this.stdform[2]; 412 } else { 413 return Infinity; 414 } 415 }; 416 417 /** 418 * Calculates the slope of the line. 419 * @type float 420 * @return The slope of the line or Infinity if the line is parallel to the y-axis. 421 */ 422 JXG.Line.prototype.getSlope = function () { 423 if (Math.abs(this.stdform[2])>=JXG.Math.eps) { 424 return -this.stdform[1]/this.stdform[2]; 425 } else { 426 return Infinity; 427 } 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 JXG.Line.prototype.setStraight = function (straightFirst, straightLast) { 439 this.visProp['straightFirst'] = straightFirst; 440 this.visProp['straightLast'] = straightLast; 441 442 this.board.renderer.updateLine(this); 443 }; 444 445 /** 446 * Determines whether the line has arrows at start or end of the line. Is stored in visProp['firstArrow'] and visProp['lastArrow'] 447 * @param {boolean} firstArrow True if there is an arrow at the start of the line, false otherwise. 448 * @param {boolean} lastArrow True if there is an arrow at the end of the line, false otherwise. 449 * @private 450 */ 451 /* 452 JXG.Line.prototype.setArrow = function (firstArrow, lastArrow) { 453 this.visProp['firstArrow'] = firstArrow; 454 this.visProp['lastArrow'] = lastArrow; 455 456 this.board.renderer.updateLine(this); 457 }; 458 */ 459 460 /** 461 * Calculates TextAnchor. DESCRIPTION 462 * @type JXG.Coords 463 * @return Text anchor coordinates as JXG.Coords object. 464 * @private 465 */ 466 JXG.Line.prototype.getTextAnchor = function() { 467 return new JXG.Coords(JXG.COORDS_BY_USER, [this.point1.X()+0.5*(this.point2.X() - this.point1.X()),this.point1.Y() +0.5*(this.point2.Y() - this.point1.Y())],this.board); 468 }; 469 470 /** 471 * Adjusts Label coords relative to Anchor. DESCRIPTION 472 * @private 473 */ 474 JXG.Line.prototype.setLabelRelativeCoords = function(relCoords) { 475 if (typeof this.label.content!='undefined') { 476 this.label.content.relativeCoords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [relCoords[0],-relCoords[1]],this.board); 477 } 478 } 479 480 /** 481 * Calculates LabelAnchor. DESCRIPTION 482 * @type JXG.Coords 483 * @return Text anchor coordinates as JXG.Coords object. 484 * @private 485 */ 486 JXG.Line.prototype.getLabelAnchor = function() { 487 var coords,screenCoords1,screenCoords2, 488 relCoords, slope, xoffset = this.labelOffsets[0], yoffset = this.labelOffsets[1]; 489 490 if(!this.visProp['straightFirst'] && !this.visProp['straightLast']) { 491 this.setLabelRelativeCoords(this.labelOffsets); 492 return new JXG.Coords(JXG.COORDS_BY_USER, [this.point2.X()-0.5*(this.point2.X() - this.point1.X()),this.point2.Y()-0.5*(this.point2.Y() - this.point1.Y())],this.board); 493 } 494 else { 495 screenCoords1 = new JXG.Coords(JXG.COORDS_BY_USER, this.point1.coords.usrCoords, this.board); 496 screenCoords2 = new JXG.Coords(JXG.COORDS_BY_USER, this.point2.coords.usrCoords, this.board); 497 this.board.renderer.calcStraight(this, screenCoords1, screenCoords2); 498 499 if(this.visProp['straightFirst']) { 500 coords = screenCoords1; 501 } 502 else { 503 coords = screenCoords2; 504 } 505 // Hack 506 if(this.label.content != null) { 507 relCoords = [0,0]; 508 slope = this.getSlope(); 509 if(coords.scrCoords[2]==0) { 510 if(slope == Infinity) { 511 relCoords = [xoffset,-yoffset]; 512 } 513 else if(slope >= 0) { 514 relCoords = [xoffset,-yoffset]; 515 } 516 else { 517 relCoords = [-xoffset,-yoffset]; 518 } 519 } 520 else if(coords.scrCoords[2]==this.board.canvasHeight) { 521 if(slope == Infinity) { 522 relCoords = [xoffset,yoffset]; 523 } 524 else if(slope >= 0) { 525 relCoords = [-xoffset,yoffset]; 526 } 527 else { 528 relCoords = [xoffset,yoffset]; 529 } 530 } 531 if(coords.scrCoords[1]==0) { 532 if(slope == Infinity) { 533 relCoords = [xoffset,yoffset]; // ?? 534 } 535 else if(slope >= 0) { 536 relCoords = [xoffset,-yoffset]; 537 } 538 else { 539 relCoords = [xoffset,yoffset]; 540 } 541 } 542 else if(coords.scrCoords[1]==this.board.canvasWidth) { 543 if(slope == Infinity) { 544 relCoords = [-xoffset,yoffset]; // ?? 545 } 546 else if(slope >= 0) { 547 relCoords = [-xoffset,yoffset]; 548 } 549 else { 550 relCoords = [-xoffset,-yoffset]; 551 } 552 } 553 this.setLabelRelativeCoords(relCoords); 554 } 555 return coords; 556 } 557 }; 558 559 /** 560 * Clone the element to the background to leave a trail of it on the board. 561 * @param {boolean} addToTrace Not used. 562 */ 563 JXG.Line.prototype.cloneToBackground = function(addToTrace) { 564 var copy = {}, r, s, er; 565 566 copy.id = this.id + 'T' + this.numTraces; 567 copy.elementClass = JXG.OBJECT_CLASS_LINE; 568 this.numTraces++; 569 copy.point1 = this.point1; 570 copy.point2 = this.point2; 571 572 copy.stdform = this.stdform; 573 574 copy.board = {}; 575 copy.board.unitX = this.board.unitX; 576 copy.board.unitY = this.board.unitY; 577 copy.board.zoomX = this.board.zoomX; 578 copy.board.zoomY = this.board.zoomY; 579 copy.board.stretchX = this.board.stretchX; 580 copy.board.stretchY = this.board.stretchY; 581 copy.board.origin = this.board.origin; 582 copy.board.canvasHeight = this.board.canvasHeight; 583 copy.board.canvasWidth = this.board.canvasWidth; 584 copy.board.dimension = this.board.dimension; 585 //copy.board.algebra = this.board.algebra; 586 587 copy.visProp = this.visProp; 588 JXG.clearVisPropOld(copy); 589 590 s = this.getSlope(); 591 r = this.getRise(); 592 copy.getSlope = function() { return s; }; 593 copy.getRise = function() { return r; }; 594 595 er = this.board.renderer.enhancedRendering; 596 this.board.renderer.enhancedRendering = true; 597 this.board.renderer.drawLine(copy); 598 this.board.renderer.enhancedRendering = er; 599 this.traces[copy.id] = copy.rendNode; //this.board.renderer.getElementById(copy.id); 600 601 delete copy; 602 603 /* 604 var id = this.id + 'T' + this.numTraces; 605 this.traces[id] = this.board.renderer.cloneSubTree(this,id,'lines'); 606 this.numTraces++; 607 */ 608 }; 609 610 /** 611 * DESCRIPTION 612 * @param transform A {@link #JXG.Transformation} object or an array of it. 613 */ 614 JXG.Line.prototype.addTransform = function (/** JXG.Transformation,array */ transform) { 615 var list, i; 616 if (JXG.isArray(transform)) { 617 list = transform; 618 } else { 619 list = [transform]; 620 } 621 for (i=0;i<list.length;i++) { 622 this.point1.transformations.push(list[i]); 623 this.point2.transformations.push(list[i]); 624 } 625 }; 626 627 /** 628 * TODO DESCRIPTION. What is this method for? -- michael 629 * @param method TYPE & DESCRIPTION. UNUSED. 630 * @param x TYPE & DESCRIPTION 631 * @param y TYPE & DESCRIPTION 632 */ 633 JXG.Line.prototype.setPosition = function (method, x, y) { 634 //var oldCoords = this.coords; 635 //if(this.group.length != 0) { 636 // AW: Do we need this for lines? 637 // this.coords = new JXG.Coords(method, [x,y], this.board); 638 // this.group[this.group.length-1].dX = this.coords.scrCoords[1] - oldCoords.scrCoords[1]; 639 // this.group[this.group.length-1].dY = this.coords.scrCoords[2] - oldCoords.scrCoords[2]; 640 // this.group[this.group.length-1].update(this); 641 //} else { 642 var t = this.board.create('transform',[x,y],{type:'translate'}); 643 if (this.point1.transformations.length>0 && this.point1.transformations[this.point1.transformations.length-1].isNumericMatrix) { 644 this.point1.transformations[this.point1.transformations.length-1].melt(t); 645 } else { 646 this.point1.addTransform(this.point1,t); 647 } 648 if (this.point2.transformations.length>0 && this.point2.transformations[this.point2.transformations.length-1].isNumericMatrix) { 649 this.point2.transformations[this.point2.transformations.length-1].melt(t); 650 } else { 651 this.point2.addTransform(this.point2,t); 652 } 653 //this.addTransform(t); 654 //this.update(); 655 //} 656 }; 657 658 /** 659 * Treat the line as parametric curve in homogeneuous coordinates. 660 * <pre>x = 1 * sin(theta)*cos(phi) 661 * y = 1 * sin(theta)*sin(phi) 662 * z = 1 * sin(theta)</pre> 663 * and the line is the set of solutions of <tt>a*x+b*y+c*z = 0</tt>. 664 * It follows: 665 * <pre>sin(theta)*(a*cos(phi)+b*sin(phi))+c*cos(theta) = 0</pre> 666 * Define: 667 * <pre> A = (a*cos(phi)+b*sin(phi)) 668 * B = c</pre> 669 * Then 670 * <pre>cos(theta) = A/sqrt(A*A+B*B) 671 * sin(theta) = -B/sqrt(A*A+B*B)</pre> 672 * and <tt>X(phi) = x</tt> from above. 673 * phi runs from 0 to 1 674 * @type float 675 * @return X(phi) TODO description 676 */ 677 JXG.Line.prototype.X = function (phi) { 678 var a = this.stdform[1], 679 b = this.stdform[2], 680 c = this.stdform[0], 681 A, B, sq, sinTheta, cosTheta; 682 phi *= Math.PI; 683 A = a*Math.cos(phi)+b*Math.sin(phi); 684 B = c; 685 sq = Math.sqrt(A*A+B*B); 686 sinTheta = -B/sq; 687 cosTheta = A/sq; 688 if (Math.abs(cosTheta)<JXG.Math.eps) { cosTheta = 1.0; } 689 return sinTheta*Math.cos(phi)/cosTheta; 690 }; 691 692 /** 693 * Treat the line as parametric curve in homogeneous coordinates. See {@link #X} for a detailed description. 694 * @type float 695 * @return Y(phi) TODO description 696 */ 697 JXG.Line.prototype.Y = function (phi) { 698 var a = this.stdform[1], 699 b = this.stdform[2], 700 c = this.stdform[0], 701 A, B, sq, sinTheta, cosTheta; 702 phi *= Math.PI; 703 A = a*Math.cos(phi)+b*Math.sin(phi); 704 B = c; 705 sq = Math.sqrt(A*A+B*B); 706 sinTheta = -B/sq; 707 cosTheta = A/sq; 708 if (Math.abs(cosTheta)<JXG.Math.eps) { cosTheta = 1.0; } 709 return sinTheta*Math.sin(phi)/cosTheta; 710 }; 711 712 /** 713 * Treat the line as parametric curve in homogeneous coordinates. See {@link #X} for a detailed description. 714 * @type float 715 * @return Z(phi) TODO description 716 */ 717 JXG.Line.prototype.Z = function (phi) { 718 var a = this.stdform[1], 719 b = this.stdform[2], 720 c = this.stdform[0], 721 A, B, sq, cosTheta; 722 phi *= Math.PI; 723 A = a*Math.cos(phi)+b*Math.sin(phi); 724 B = c; 725 sq = Math.sqrt(A*A+B*B); 726 cosTheta = A/sq; 727 if (Math.abs(cosTheta)>=JXG.Math.eps) { 728 return 1.0; 729 } else { 730 return 0.0; 731 } 732 }; 733 734 /** 735 * TODO circle?!? --michael 736 * private or public? --michael 737 * Treat the circle as parametric curve: 738 * t runs from 0 to 1 739 * @private 740 */ 741 JXG.Line.prototype.minX = function () { 742 return 0.0; 743 }; 744 745 /** 746 * TODO circle?!? --michael 747 * private or public? --michael 748 * Treat the circle as parametric curve: 749 * t runs from 0 to 1 750 * @private 751 */ 752 JXG.Line.prototype.maxX = function () { 753 return 1.0; 754 }; 755 756 /** 757 * Adds ticks to this line. Ticks can be added to any kind of line: line, arrow, and axis. 758 * @param {JXG.Ticks} ticks Reference to a ticks object which is describing the ticks (color, distance, how many, etc.). 759 * @type String 760 * @return Id of the ticks object. 761 */ 762 JXG.Line.prototype.addTicks = function(ticks) { 763 if(ticks.id == '' || typeof ticks.id == 'undefined') 764 ticks.id = this.id + '_ticks_' + (this.ticks.length+1); 765 766 this.board.renderer.drawTicks(ticks); 767 this.ticks.push(ticks); 768 769 this.ticks[this.ticks.length-1].updateRenderer(); 770 771 return ticks.id; 772 }; 773 774 /** 775 * Removes all ticks from a line. 776 */ 777 JXG.Line.prototype.removeAllTicks = function() { 778 var t; 779 for(t=this.ticks.length; t>0; t--) { 780 this.board.renderer.remove(this.ticks[t-1].rendNode); 781 } 782 this.ticks = new Array(); 783 }; 784 785 /** 786 * Removes ticks identified by parameter named tick from this line. 787 * @param {JXG.Ticks} tick Reference to tick object to remove. 788 */ 789 JXG.Line.prototype.removeTicks = function(tick) { 790 var t, j; 791 if(this.defaultTicks != null && this.defaultTicks == tick) { 792 this.defaultTicks = null; 793 } 794 795 for(t=this.ticks.length; t>0; t--) { 796 if(this.ticks[t-1] == tick) { 797 this.board.renderer.remove(this.ticks[t-1].rendNode); 798 799 for(j=0; j<this.ticks[t-1].ticks.length; j++) { 800 if(this.ticks[t-1].labels[j] != null) 801 if (this.ticks[t-1].labels[j].show) this.board.renderer.remove(this.ticks[t-1].labels[j].rendNode); 802 } 803 delete(this.ticks[t-1]); 804 } 805 } 806 }; 807 808 /** 809 * @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 810 * a line can be used as an arrow and/or axis. 811 * @pseudo 812 * @description 813 * @name Line 814 * @augments JXG.Line 815 * @constructor 816 * @type JXG.Line 817 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 818 * @param {JXG.Point,array_JXG.Point,array} point1,point2 Parent elements can be two elements either of type {@link JXG.Point} or array of 819 * numbers describing the coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 820 * @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 821 * of the equation <tt>a*x+b*y+c*z = 0</tt>. 822 * @example 823 * // Create a line using point and coordinates/ 824 * // The second point will be fixed and invisible. 825 * var p1 = board.create('point', [4.5, 2.0]); 826 * var l1 = board.create('line', [p1, [1.0, 1.0]]); 827 * </pre><div id="c0ae3461-10c4-4d39-b9be-81d74759d122" style="width: 300px; height: 300px;"></div> 828 * <script type="text/javascript"> 829 * var glex1_board = JXG.JSXGraph.initBoard('c0ae3461-10c4-4d39-b9be-81d74759d122', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 830 * var glex1_p1 = glex1_board.create('point', [4.5, 2.0]); 831 * var glex1_l1 = glex1_board.create('line', [glex1_p1, [1.0, 1.0]]); 832 * </script><pre> 833 * @example 834 * // Create a point using three coordinates 835 * var l1 = board.create('line', [1.0, -2.0, 3.0]); 836 * </pre><div id="cf45e462-f964-4ba4-be3a-c9db94e2593f" style="width: 300px; height: 300px;"></div> 837 * <script type="text/javascript"> 838 * var glex2_board = JXG.JSXGraph.initBoard('cf45e462-f964-4ba4-be3a-c9db94e2593f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 839 * var glex2_l1 = glex2_board.create('line', [1.0, -2.0, 3.0]); 840 * </script><pre> 841 */ 842 JXG.createLine = function(board, parents, atts) { 843 var el, p1, p2, i, 844 c = []; 845 846 atts = JXG.checkAttributes(atts,{ 847 withLabel:JXG.readOption(board.options,'line','withLabel'), 848 layer:null, 849 labelOffsets:JXG.readOption(board.options,'line','labelOffsets') 850 }); 851 852 var constrained = false; 853 if (parents.length == 2) { // The line is defined by two points (or coordinates of two points) 854 if (parents[0].length>1) { // point 1 given by coordinates 855 p1 = board.create('point', parents[0], {visible:false,fixed:true}); 856 } else if (parents[0].elementClass == JXG.OBJECT_CLASS_POINT) { 857 p1 = JXG.getReference(board,parents[0]); 858 } else if ((typeof parents[0] == 'function') && (parents[0]().elementClass == JXG.OBJECT_CLASS_POINT)) { 859 p1 = parents[0](); 860 constrained = true; 861 } else 862 throw new Error("JSXGraph: Can't create line with parent types '" + 863 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 864 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"); 865 866 if (parents[1].length>1) { // point 2 given by coordinates 867 p2 = board.create('point', parents[1], {visible:false,fixed:true}); 868 } else if (parents[1].elementClass == JXG.OBJECT_CLASS_POINT) { 869 p2 = JXG.getReference(board,parents[1]); 870 } else if ((typeof parents[1] == 'function') && (parents[1]().elementClass == JXG.OBJECT_CLASS_POINT)) { 871 p2 = parents[1](); 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 el = new JXG.Line(board, p1.id, p2.id, atts['id'], atts['name'],atts['withLabel'],atts['layer']); 878 if(constrained) { 879 el.constrained = true; 880 el.funp1 = parents[0]; 881 el.funp2 = parents[1]; 882 } 883 } 884 else if (parents.length==3) { // Line is defined by three coordinates 885 // free line 886 for (i=0;i<3;i++) { 887 if (typeof parents[i]=='number') { 888 c[i] = function(z){ return function() { return z; }; }(parents[i]); 889 } else if (typeof parents[i]=='function') { 890 c[i] = parents[i]; 891 } else { 892 throw new Error("JSXGraph: Can't create line with parent types '" + 893 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2])+ "'." + 894 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"); 895 } 896 } 897 // point 1: (0,c,-b) 898 /* 899 p1 = board.create('point',[ 900 function() { return 0.0;}, 901 function() { return c[2]();}, 902 function() { return -c[1]();}],{visible:false,name:' '}); 903 */ 904 // New version: point1 is the midpoint between (0,c,-b) and point 2. => point1 is finite. 905 p1 = board.create('point',[ 906 function() { return (0.0 + c[2]()*c[2]()+c[1]()*c[1]())*0.5;}, 907 function() { return (c[2]() - c[1]()*c[0]()+c[2]())*0.5;}, 908 function() { return (-c[1]() - c[2]()*c[0]()-c[1]())*0.5;}],{visible:false,name:' '}); 909 // point 2: (b^2+c^2,-ba+c,-ca-b) 910 p2 = board.create('point',[ 911 function() { return c[2]()*c[2]()+c[1]()*c[1]();}, 912 function() { return -c[1]()*c[0]()+c[2]();}, 913 function() { return -c[2]()*c[0]()-c[1]();}],{visible:false,name:' '}); 914 el = new JXG.Line(board, p1.id, p2.id, atts['id'], atts['name'],atts['withLabel']); 915 } 916 else if ((parents.length==1) && (typeof parents[0] == 'function') && (parents[0]().length == 2) && 917 (parents[0]()[0].elementClass == JXG.OBJECT_CLASS_POINT) && (parents[0]()[1].elementClass == JXG.OBJECT_CLASS_POINT)) { 918 var ps = parents[0](); 919 el = new JXG.Line(board, ps[0].id, ps[1].id, atts['id'], atts['name'],atts['withLabel'],atts['layer']); 920 el.constrained = true; 921 el.funps = parents[0]; 922 } else 923 throw new Error("JSXGraph: Can't create line with parent types '" + 924 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 925 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]"); 926 927 el.labelOffsets = atts['labelOffsets']; 928 return el; 929 }; 930 931 JXG.JSXGraph.registerElement('line', JXG.createLine); 932 933 /** 934 * @class This element is used to provide a constructor for a segment. It's strictly spoken just a wrapper for element {@link Line} with {@link JXG.Line#straightFirst} 935 * and {@link JXG.Line#straightLast} properties set to false. 936 * @pseudo 937 * @description 938 * @name Segment 939 * @augments JXG.Line 940 * @constructor 941 * @type JXG.Line 942 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 943 * @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 944 * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 945 * @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 946 * of the equation <tt>a*x+b*y+c*z = 0</tt>. 947 * @see Line 948 * @example 949 * // Create a segment providing two points. 950 * var p1 = board.create('point', [4.5, 2.0]); 951 * var p2 = board.create('point', [1.0, 1.0]); 952 * var l1 = board.create('segment', [p1, p2]); 953 * </pre><div id="d70e6aac-7c93-4525-a94c-a1820fa38e2f" style="width: 300px; height: 300px;"></div> 954 * <script type="text/javascript"> 955 * var slex1_board = JXG.JSXGraph.initBoard('d70e6aac-7c93-4525-a94c-a1820fa38e2f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 956 * var slex1_p1 = slex1_board.create('point', [4.5, 2.0]); 957 * var slex1_p2 = slex1_board.create('point', [1.0, 1.0]); 958 * var slex1_l1 = slex1_board.create('segment', [slex1_p1, slex1_p2]); 959 * </script><pre> 960 */ 961 JXG.createSegment = function(board, parents, atts) { 962 var el; 963 964 atts = JXG.checkAttributes(atts,{withLabel:JXG.readOption(board.options,'line','withLabel'), layer:null}); 965 atts['straightFirst'] = false; 966 atts['straightLast'] = false; 967 el = board.create('line', parents, atts); 968 969 return el; 970 }; 971 972 JXG.JSXGraph.registerElement('segment', JXG.createSegment); 973 974 /** 975 * @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} 976 * and {@link JXG.Line#straightLast} properties set to false and {@link JXG.Line#lastArrow} set to true. 977 * @pseudo 978 * @description 979 * @name Arrow 980 * @augments JXG.Line 981 * @constructor 982 * @type JXG.Line 983 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 984 * @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 985 * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 986 * @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 987 * of the equation <tt>a*x+b*y+c*z = 0</tt>. 988 * @see Line 989 * @example 990 * // Create an arrow providing two points. 991 * var p1 = board.create('point', [4.5, 2.0]); 992 * var p2 = board.create('point', [1.0, 1.0]); 993 * var l1 = board.create('arrow', [p1, p2]); 994 * </pre><div id="1d26bd22-7d6d-4018-b164-4c8bc8d22ccf" style="width: 300px; height: 300px;"></div> 995 * <script type="text/javascript"> 996 * var alex1_board = JXG.JSXGraph.initBoard('1d26bd22-7d6d-4018-b164-4c8bc8d22ccf', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 997 * var alex1_p1 = alex1_board.create('point', [4.5, 2.0]); 998 * var alex1_p2 = alex1_board.create('point', [1.0, 1.0]); 999 * var alex1_l1 = alex1_board.create('arrow', [alex1_p1, alex1_p2]); 1000 * </script><pre> 1001 */ 1002 JXG.createArrow = function(board, parents, attributes) { 1003 var el; 1004 1005 attributes = JXG.checkAttributes(attributes,{withLabel:JXG.readOption(board.options,'line','withLabel'), layer:null}); 1006 //if ( (JXG.isPoint(parents[0])) && (JXG.isPoint(parents[1])) ) { // The constructability decision is delkegated to the line object 1007 el = board.create('line',parents,attributes); 1008 //el = new JXG.Line(board, parents[0], parents[1], attributes['id'], attributes['name'],attributes['withLabel']); 1009 el.setStraight(false,false); 1010 el.setArrow(false,true); 1011 el.type = JXG.OBJECT_TYPE_VECTOR; 1012 //} // Ansonsten eine fette Exception um die Ohren hauen 1013 //else 1014 // throw new Error("JSXGraph: Can't create arrow with parent types '" + (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'."); 1015 1016 return el; 1017 }; 1018 1019 JXG.JSXGraph.registerElement('arrow', JXG.createArrow); 1020 1021 /** 1022 * @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} 1023 * 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. 1024 * @pseudo 1025 * @description 1026 * @name Axis 1027 * @augments JXG.Line 1028 * @constructor 1029 * @type JXG.Line 1030 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1031 * @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 1032 * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point. 1033 * @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 1034 * of the equation <tt>a*x+b*y+c*z = 0</tt>. 1035 * @example 1036 * // Create an axis providing two coord pairs. 1037 * var l1 = board.create('axis', [[0.0, 1.0], [1.0, 1.3]]); 1038 * </pre><div id="4f414733-624c-42e4-855c-11f5530383ae" style="width: 300px; height: 300px;"></div> 1039 * <script type="text/javascript"> 1040 * var axex1_board = JXG.JSXGraph.initBoard('4f414733-624c-42e4-855c-11f5530383ae', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}); 1041 * var axex1_l1 = axex1_board.create('axis', [[0.0, 1.0], [1.0, 1.3]]); 1042 * </script><pre> 1043 */ 1044 JXG.createAxis = function(board, parents, attributes) { 1045 var point1, 1046 point2, 1047 line, dist, c1, c2, len, defTicks; 1048 1049 // Arrays oder Punkte, mehr brauchen wir nicht. 1050 if ( (JXG.isArray(parents[0]) || JXG.isPoint(parents[0]) ) && (JXG.isArray(parents[1]) || JXG.isPoint(parents[1])) ) { 1051 if( JXG.isPoint(parents[0]) ) 1052 point1 = parents[0]; 1053 else 1054 point1 = new JXG.Point(board, parents[0],'','',false); 1055 1056 if( JXG.isPoint(parents[1]) ) 1057 point2 = parents[1]; 1058 else 1059 point2 = new JXG.Point(board,parents[1],'','',false); 1060 1061 /* Make the points fixed */ 1062 point1.fixed = true; 1063 point2.fixed = true; 1064 1065 attributes = JXG.checkAttributes(attributes, 1066 {'lastArrow':true, 'straightFirst':true, 'straightLast':true, 1067 'strokeWidth':1, 'withLabel':false, 1068 'strokeColor':board.options.axis.strokeColor}); 1069 1070 attributes.highlightStrokeColor = attributes.highlightStrokeColor || attributes.strokeColor || board.options.axis.highlightStrokeColor; 1071 1072 line = board.create('line', [point1, point2], attributes); 1073 line.setProperty({needsRegularUpdate : false}); // Axes only updated after zooming and moving of the origin. 1074 1075 attributes = JXG.checkAttributes(attributes,{'minorTicks':4, 'insertTicks': true}); 1076 1077 if(attributes.ticksDistance != 'undefined' && attributes.ticksDistance != null) { 1078 dist = attributes.ticksDistance; 1079 } else if(JXG.isArray(attributes.ticks)) { 1080 dist = attributes.ticks; 1081 } else { 1082 c1 = new JXG.Coords(JXG.COORDS_BY_USER, [line.point1.coords.usrCoords.slice(1)],board); 1083 c2 = new JXG.Coords(JXG.COORDS_BY_USER, [line.point2.coords.usrCoords.slice(1)],board); 1084 board.renderer.calcStraight(line, c1, c2); 1085 len = c1.distance(JXG.COORDS_BY_USER,c2); 1086 //len *= 0.33; 1087 dist = 1.0; //len; 1088 } 1089 1090 line.defaultTicks = board.create('ticks', [line, dist], attributes); 1091 line.defaultTicks.setProperty({needsRegularUpdate : false}); 1092 } 1093 else 1094 throw new Error("JSXGraph: Can't create point with parent types '" + 1095 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1096 "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]"); 1097 1098 return line; 1099 }; 1100 1101 JXG.JSXGraph.registerElement('axis', JXG.createAxis); 1102 1103 /** 1104 * @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 1105 * by a glider on a line, circle, or curve and describes the tangent in the glider point on that line, circle, or curve. 1106 * @pseudo 1107 * @description 1108 * @name Tangent 1109 * @augments JXG.Line 1110 * @constructor 1111 * @type JXG.Line 1112 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1113 * @param {Glider} g A glider on a line, circle, or curve. 1114 * @example 1115 * // Create a tangent providing a glider on a function graph 1116 * var c1 = board.create('curve', [function(t){return t},function(t){return t*t*t;}]); 1117 * var g1 = board.create('glider', [0.6, 1.2, c1]); 1118 * var t1 = board.create('tangent', [g1]); 1119 * </pre><div id="7b7233a0-f363-47dd-9df5-4018d0d17a98" style="width: 400px; height: 400px;"></div> 1120 * <script type="text/javascript"> 1121 * var tlex1_board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-4018d0d17a98', {boundingbox: [-6, 6, 6, -6], axis: true, showcopyright: false, shownavigation: false}); 1122 * var tlex1_c1 = tlex1_board.create('curve', [function(t){return t},function(t){return t*t*t;}]); 1123 * var tlex1_g1 = tlex1_board.create('glider', [0.6, 1.2, tlex1_c1]); 1124 * var tlex1_t1 = tlex1_board.create('tangent', [tlex1_g1]); 1125 * </script><pre> 1126 */ 1127 JXG.createTangent = function(board, parents, attributes) { 1128 var p, 1129 c, 1130 g, f, i, j, el, Dg, Df, tangent; 1131 1132 if (parents.length==1) { // One arguments: glider on line, circle or curve 1133 p = parents[0]; 1134 c = p.slideObject; 1135 } else if (parents.length==2) { // Two arguments: (point,line|curve|circle|conic) or (line|curve|circle|conic,point). // Not yet: curve! 1136 if (JXG.isPoint(parents[0])) { // In fact, for circles and conics it is the polar. 1137 p = parents[0]; 1138 c = parents[1]; 1139 } else if (JXG.isPoint(parents[1])) { 1140 c = parents[0]; 1141 p = parents[1]; 1142 } else { 1143 throw new Error("JSXGraph: Can't create tangent with parent types '" + 1144 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1145 "\nPossible parent types: [glider], [point,line|curve|circle|conic]"); 1146 } 1147 } else { 1148 throw new Error("JSXGraph: Can't create tangent with parent types '" + 1149 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 1150 "\nPossible parent types: [glider], [point,line|curve|circle|conic]"); 1151 } 1152 1153 attributes = JXG.checkAttributes(attributes,{withLabel:JXG.readOption(board.options,'line','withLabel'), layer:null}); 1154 1155 if (c.elementClass == JXG.OBJECT_CLASS_LINE) { 1156 tangent = board.create('line', [c.point1,c.point2], attributes); 1157 } else if (c.elementClass == JXG.OBJECT_CLASS_CURVE && !(c.type == JXG.OBJECT_TYPE_CONIC)) { 1158 if (c.curveType!='plot') { 1159 g = c.X; 1160 f = c.Y; 1161 tangent = board.create('line', [ 1162 function(){ return -p.X()*board.D(f)(p.position)+p.Y()*board.D(g)(p.position);}, 1163 function(){ return board.D(f)(p.position);}, 1164 function(){ return -board.D(g)(p.position);} 1165 ], attributes ); 1166 p.addChild(tangent); 1167 // this is required for the geogebra reader to display a slope 1168 tangent.glider = p; 1169 } else { // curveType 'plot' 1170 // equation of the line segment: 0 = y*(x1-x2) + x*(y2-y1) + y1*x2-x1*y2 1171 tangent = board.create('line', [ 1172 function(){ i=Math.floor(p.position); 1173 if (i==c.numberPoints-1) i--; 1174 if (i<0) return 1.0; 1175 return c.Y(i)*c.X(i+1)-c.X(i)*c.Y(i+1);}, 1176 function(){ i=Math.floor(p.position); 1177 if (i==c.numberPoints-1) i--; 1178 if (i<0) return 0.0; 1179 return c.Y(i+1)-c.Y(i);}, 1180 function(){ i=Math.floor(p.position); 1181 if (i==c.numberPoints-1) i--; 1182 if (i<0) return 0.0; 1183 return c.X(i)-c.X(i+1);} 1184 ], attributes ); 1185 p.addChild(tangent); 1186 // this is required for the geogebra reader to display a slope 1187 tangent.glider = p; 1188 } 1189 } else if (c.type == JXG.OBJECT_TYPE_TURTLE) { 1190 tangent = board.create('line', [ 1191 function(){ i=Math.floor(p.position); 1192 for(j=0;j<c.objects.length;j++) { // run through all curves of this turtle 1193 el = c.objects[j]; 1194 if (el.type==JXG.OBJECT_TYPE_CURVE) { 1195 if (i<el.numberPoints) break; 1196 i-=el.numberPoints; 1197 } 1198 } 1199 if (i==el.numberPoints-1) i--; 1200 if (i<0) return 1.0; 1201 return el.Y(i)*el.X(i+1)-el.X(i)*el.Y(i+1);}, 1202 function(){ i=Math.floor(p.position); 1203 for(j=0;j<c.objects.length;j++) { // run through all curves of this turtle 1204 el = c.objects[j]; 1205 if (el.type==JXG.OBJECT_TYPE_CURVE) { 1206 if (i<el.numberPoints) break; 1207 i-=el.numberPoints; 1208 } 1209 } 1210 if (i==el.numberPoints-1) i--; 1211 if (i<0) return 0.0; 1212 return el.Y(i+1)-el.Y(i);}, 1213 function(){ i=Math.floor(p.position); 1214 for(j=0;j<c.objects.length;j++) { // run through all curves of this turtle 1215 el = c.objects[j]; 1216 if (el.type==JXG.OBJECT_TYPE_CURVE) { 1217 if (i<el.numberPoints) break; 1218 i-=el.numberPoints; 1219 } 1220 } 1221 if (i==el.numberPoints-1) i--; 1222 if (i<0) return 0.0; 1223 return el.X(i)-el.X(i+1);} 1224 ], attributes ); 1225 p.addChild(tangent); 1226 // this is required for the geogebra reader to display a slope 1227 tangent.glider = p; 1228 } else if (c.elementClass == JXG.OBJECT_CLASS_CIRCLE || c.type == JXG.OBJECT_TYPE_CONIC) { 1229 /* 1230 Dg = function(t){ return -c.Radius()*Math.sin(t); }; 1231 Df = function(t){ return c.Radius()*Math.cos(t); }; 1232 return board.create('line', [ 1233 function(){ return -p.X()*Df(p.position)+p.Y()*Dg(p.position);}, 1234 function(){ return Df(p.position);}, 1235 function(){ return -Dg(p.position);} 1236 ], attributes ); 1237 */ 1238 1239 // If p is not on c, the tangent is the polar. 1240 // This construction should work on conics, too. p has to lie on c. 1241 tangent = board.create('line', [ 1242 function(){ return JXG.Math.matVecMult(c.quadraticform,p.coords.usrCoords)[0]; }, 1243 function(){ return JXG.Math.matVecMult(c.quadraticform,p.coords.usrCoords)[1]; }, 1244 function(){ return JXG.Math.matVecMult(c.quadraticform,p.coords.usrCoords)[2]; } 1245 ] , attributes); 1246 1247 p.addChild(tangent); 1248 // this is required for the geogebra reader to display a slope 1249 tangent.glider = p; 1250 } 1251 1252 return tangent; 1253 }; 1254 1255 /** 1256 * Register the element type tangent at JSXGraph 1257 * @private 1258 */ 1259 JXG.JSXGraph.registerElement('tangent', JXG.createTangent); 1260 JXG.JSXGraph.registerElement('polar', JXG.createTangent); 1261 // vim: et ts=4 1262