1 /* 2 Copyright 2008,2009 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 In this file the geometry object Ticks is defined. Ticks provides 28 * methods for creation and management of ticks on an axis. 29 * @author graphjs 30 * @version 0.1 31 */ 32 33 /** 34 * Creates ticks for an axis. 35 * @class Ticks provides methods for creation and management 36 * of ticks on an axis. 37 * @param {JXG.Line} line Reference to the axis the ticks are drawn on. 38 * @param {Number|Array} ticks Number defining the distance between two major ticks or an array defining static ticks. 39 * @param {Object} attributes Properties 40 * @see JXG.Line#addTicks 41 * @constructor 42 * @extends JXG.GeometryElement 43 */ 44 JXG.Ticks = function (line, ticks, attributes) { 45 this.constructor(line.board, attributes, JXG.OBJECT_TYPE_TICKS, JXG.OBJECT_CLASS_OTHER); 46 47 /** 48 * The line the ticks belong to. 49 * @type JXG.Line 50 */ 51 this.line = line; 52 53 /** 54 * The board the ticks line is drawn on. 55 * @type JXG.Board 56 */ 57 this.board = this.line.board; 58 59 /** 60 * A function calculating ticks delta depending on the ticks number. 61 * @type Function 62 */ 63 this.ticksFunction = null; 64 65 /** 66 * Array of fixed ticks. 67 * @type Array 68 */ 69 this.fixedTicks = null; 70 71 /** 72 * Equidistant ticks. Distance is defined by ticksFunction 73 * @type Boolean 74 */ 75 this.equidistant = false; 76 77 if(JXG.isFunction(ticks)) { 78 this.ticksFunction = ticks; 79 throw new Error("Function arguments are no longer supported."); 80 } else if(JXG.isArray(ticks)) { 81 this.fixedTicks = ticks; 82 } else { 83 if(Math.abs(ticks) < JXG.Math.eps) 84 ticks = attributes.defaultdistance; 85 this.ticksFunction = function (i) { return ticks; }; 86 this.equidistant = true; 87 } 88 89 /** 90 * Least distance between two ticks, measured in pixels. 91 * @type int 92 */ 93 this.minTicksDistance = attributes.minticksdistance; 94 95 /** 96 * Maximum distance between two ticks, measured in pixels. Is used only when insertTicks 97 * is set to true. 98 * @type int 99 * @see #insertTicks 100 * @deprecated This value will be ignored. 101 */ 102 this.maxTicksDistance = attributes.maxticksdistance; 103 104 /** 105 * Array where the labels are saved. There is an array element for every tick, 106 * even for minor ticks which don't have labels. In this case the array element 107 * contains just <tt>null</tt>. 108 * @type Array 109 */ 110 this.labels = []; 111 112 this.id = this.line.addTicks(this); 113 this.board.setId(this,'Ti'); 114 }; 115 116 JXG.Ticks.prototype = new JXG.GeometryElement(); 117 118 JXG.extend(JXG.Ticks.prototype, /** @lends JXG.Ticks.prototype */ { 119 /** 120 * Checks whether (x,y) is near the line. 121 * @param {Number} x Coordinate in x direction, screen coordinates. 122 * @param {Number} y Coordinate in y direction, screen coordinates. 123 * @return {Boolean} True if (x,y) is near the line, False otherwise. 124 */ 125 hasPoint: function (x, y) { 126 var i, t, 127 len = this.ticks.length, 128 r = this.board.options.precision.hasPoint; 129 130 if (!this.line.visProp.scalable) { 131 return false; 132 } 133 134 // Ignore non-axes and axes that are not horizontal or vertical 135 if (this.line.stdform[1]!=0 136 && this.line.stdform[2]!=0 137 && this.line.type!=JXG.OBJECT_TYPE_AXIS) { 138 139 return false; 140 } 141 142 for (i=0; i<len; i++) { 143 t = this.ticks[i]; 144 if (!t[2]) continue; // Skip minor ticks 145 146 // Ignore ticks at zero 147 if (this.line.stdform[1]==0 && Math.abs(t[0][0]-this.line.point1.coords.scrCoords[1])<JXG.Math.eps) { 148 continue; 149 } else if (this.line.stdform[2]==0 && Math.abs(t[1][0]-this.line.point1.coords.scrCoords[2])<JXG.Math.eps) { 150 continue; 151 } 152 153 if (Math.abs(t[0][0]-t[0][1])>=1 || Math.abs(t[1][0]-t[1][1])>=1) { // tick length is not zero, ie. at least one pixel 154 /* 155 if (t[0][0]-r < x 156 && x < t[0][1]+r 157 && t[1][0]-r < y 158 && y < t[1][1]+r) { 159 160 return true; 161 } 162 */ 163 if (this.line.stdform[1]==0) { 164 if (Math.abs(y - (t[1][0]+t[1][1])*0.5) < 2*r // Allow dragging near axes only. 165 && t[0][0]-r < x 166 && x < t[0][1]+r) { 167 168 return true; 169 } 170 } else if (this.line.stdform[2]==0) { 171 if (Math.abs(x - (t[0][0]+t[0][1])*0.5) < 2*r 172 && t[1][0]-r < y 173 && y < t[1][1]+r) { 174 175 return true; 176 } 177 } 178 } 179 } 180 181 return false; 182 }, 183 184 setPositionDirectly: function (method, coords, oldcoords) { 185 var dx, dy, i, 186 c = new JXG.Coords(method, coords, this.board), 187 oldc = new JXG.Coords(method, oldcoords, this.board), 188 bb = this.board.getBoundingBox(); 189 190 191 if (!this.line.visProp.scalable) { 192 return this; 193 } 194 195 if (Math.abs(this.line.stdform[1])<JXG.Math.eps // horizontal line 196 && Math.abs(c.usrCoords[1]*oldc.usrCoords[1])>JXG.Math.eps) { 197 dx =oldc.usrCoords[1] / c.usrCoords[1]; 198 bb[0] *= dx; 199 bb[2] *= dx; 200 this.board.setBoundingBox(bb, false); 201 } else if (Math.abs(this.line.stdform[2])<JXG.Math.eps // vertical line 202 && Math.abs(c.usrCoords[2]*oldc.usrCoords[2])>JXG.Math.eps) { 203 dy = oldc.usrCoords[2] / c.usrCoords[2]; 204 bb[3] *= dy; 205 bb[1] *= dy; 206 this.board.setBoundingBox(bb, false); 207 } 208 //this.board.mode = this.board.BOARD_MODE_DRAG; 209 //this.board.needsFullUpdate = true; 210 211 return this; 212 }, 213 214 215 /** 216 * (Re-)calculates the ticks coordinates. 217 */ 218 calculateTicksCoordinates: function() { 219 // Point 1 of the line 220 var p1 = this.line.point1, 221 // Point 2 of the line 222 p2 = this.line.point2, 223 // Distance between the two points from above 224 distP1P2 = p1.Dist(p2), 225 // Distance of X coordinates of two major ticks 226 // Initialized with the distance of Point 1 to a point between Point 1 and Point 2 on the line and with distance 1 227 // this equals always 1 for lines parallel to x = 0 or y = 0. It's only important for lines other than that. 228 deltaX = (p2.coords.usrCoords[1] - p1.coords.usrCoords[1])/distP1P2, 229 // The same thing for Y coordinates 230 deltaY = (p2.coords.usrCoords[2] - p1.coords.usrCoords[2])/distP1P2, 231 // Distance of p1 to the unit point in screen coordinates 232 distScr = p1.coords.distance(JXG.COORDS_BY_SCREEN, new JXG.Coords(JXG.COORDS_BY_USER, [p1.coords.usrCoords[1] + deltaX, p1.coords.usrCoords[2] + deltaY], this.board)), 233 // Distance between two major ticks in user coordinates 234 ticksDelta = (this.equidistant ? this.ticksFunction(1) : 1), 235 symbTicksDelta, f, 236 // This factor is for enlarging ticksDelta and it switches between 5 and 2 237 // Hence, if two major ticks are too close together they'll be expanded to a distance of 5 238 // if they're still too close together, they'll be expanded to a distance of 10 etc 239 factor = 5, 240 // Coordinates of the current tick 241 tickCoords, 242 // Coordinates of the first drawn tick 243 startTick, 244 symbStartTick, 245 // two counters 246 i, j, 247 // the distance of the tick to p1. Is displayed on the board using a label 248 // for majorTicks 249 tickPosition, 250 symbTickPosition, 251 // infinite or finite tick length 252 style, 253 // new position 254 nx = 0, 255 ny = 0, 256 ti, 257 dirs = 2, dir = -1, 258 center, d, bb, perp, 259 260 // the following variables are used to define ticks height and slope 261 eps = JXG.Math.eps, pos, lb, ub, 262 distMaj = this.visProp.majorheight * 0.5, 263 distMin = this.visProp.minorheight * 0.5, 264 // ticks width and height in screen units 265 dxMaj, dyMaj, 266 dxMin, dyMin, 267 // ticks width and height in user units 268 dx, dy; 269 // END OF variable declaration 270 271 // This will trap this update routine in an endless loop. Besides, there's not much we can show 272 // on such a tiny board, so we just get out of here immediately. 273 if (this.board.canvasWidth === 0 || this.board.canvasHeight === 0) { 274 return; 275 } 276 277 // Grid-like ticks 278 if (this.visProp.minorheight < 0) { 279 this.minStyle = 'infinite'; 280 } else { 281 this.minStyle = 'finite'; 282 } 283 284 if(this.visProp.majorheight < 0) { 285 this.majStyle = 'infinite'; 286 } else { 287 this.majStyle = 'finite'; 288 } 289 290 // Set lower and upper bound for the tick distance. 291 // This is necessary for segments. 292 if (this.line.visProp.straightfirst) { 293 lb = Number.NEGATIVE_INFINITY; 294 } else { 295 lb = 0 + eps; 296 } 297 298 if (this.line.visProp.straightlast) { 299 ub = Number.POSITIVE_INFINITY; 300 } else { 301 ub = distP1P2 - eps; 302 } 303 304 // This piece of code used to be in AbstractRenderer.updateAxisTicksInnerLoop 305 // and has been moved in here to clean up the renderers code. 306 // 307 // The code above only calculates the position of the ticks. The following code parts 308 // calculate the dx and dy values which make ticks out of this positions, i.e. from the 309 // position (p_x, p_y) calculated above we have to draw a line from 310 // (p_x - dx, py - dy) to (p_x + dx, p_y + dy) to get a tick. 311 dxMaj = this.line.stdform[1]; 312 dyMaj = this.line.stdform[2]; 313 dxMin = dxMaj; 314 dyMin = dyMaj; 315 dx = dxMaj; 316 dy = dyMaj; 317 318 // After this, the length of the vector (dxMaj, dyMaj) in screen coordinates is equal to distMaj pixel. 319 d = Math.sqrt(dxMaj*dxMaj*this.board.unitX*this.board.unitX + dyMaj*dyMaj*this.board.unitY*this.board.unitY); 320 dxMaj *= distMaj/d*this.board.unitX; 321 dyMaj *= distMaj/d*this.board.unitY; 322 dxMin *= distMin/d*this.board.unitX; 323 dyMin *= distMin/d*this.board.unitY; 324 325 // Begin cleanup 326 this.removeTickLabels(); 327 328 // If the parent line is not finite, we can stop here. 329 if (Math.abs(dx)<JXG.Math.eps && Math.abs(dy)<JXG.Math.eps) { 330 return; 331 } 332 333 // initialize storage arrays 334 // ticks stores the ticks coordinates 335 this.ticks = []; 336 337 // labels stores the text to display beside the ticks 338 this.labels = []; 339 // END cleanup 340 341 // we have an array of fixed ticks we have to draw 342 if (!this.equidistant) { 343 for (i = 0; i < this.fixedTicks.length; i++) { 344 nx = p1.coords.usrCoords[1] + this.fixedTicks[i]*deltaX; 345 ny = p1.coords.usrCoords[2] + this.fixedTicks[i]*deltaY; 346 tickCoords = new JXG.Coords(JXG.COORDS_BY_USER, [nx, ny], this.board); 347 348 ti = this._tickEndings(tickCoords, dx, dy, dxMaj, dyMaj, dxMin, dyMin, /*major:*/ true); 349 // Compute the start position and the end position of a tick. 350 // If both positions are out of the canvas, ti is empty. 351 if (ti.length==2 && this.fixedTicks[i]>=lb && this.fixedTicks[i]<ub) { 352 this.ticks.push(ti); 353 } 354 this.labels.push(this._makeLabel(this.visProp.labels[i] || this.fixedTicks[i], tickCoords, this.board, this.visProp.drawlabels, this.id, i)); 355 // visibility test missing 356 } 357 return; 358 } 359 360 symbTicksDelta = ticksDelta; 361 ticksDelta *= this.visProp.scale; 362 363 // ok, we have equidistant ticks and not special ticks, so we continue here with generating them: 364 // adjust distances 365 if (this.visProp.insertticks && this.minTicksDistance > JXG.Math.eps) { 366 f = this._adjustTickDistance(ticksDelta, distScr, factor, p1.coords, deltaX, deltaY); 367 ticksDelta *= f; 368 symbTicksDelta *= f; 369 } 370 371 if (!this.visProp.insertticks) { 372 ticksDelta /= this.visProp.minorticks+1; 373 symbTicksDelta /= this.visProp.minorticks+1; 374 } 375 376 this.ticksDelta = ticksDelta; 377 this.symbTicksDelta = symbTicksDelta; 378 379 380 // We shoot into the middle of the canvas 381 // to the tick position which is closest to the center 382 // of the canvas. We do this by an orthogonal projection 383 // of the canvas center to the line and by rounding of the 384 // distance of the projected point to point1 of the line. 385 // This position is saved in 386 // center and startTick. 387 bb = this.board.getBoundingBox(); 388 nx = (bb[0]+bb[2])*0.5; 389 ny = (bb[1]+bb[3])*0.5; 390 391 // Project the center of the canvas to the line. 392 perp = [nx*this.line.stdform[2]-ny*this.line.stdform[1], 393 -this.line.stdform[2], 394 this.line.stdform[1]]; 395 center = JXG.Math.crossProduct(this.line.stdform, perp); 396 center[1] /= center[0]; 397 center[2] /= center[0]; 398 center[0] = 1; 399 // Round the distance of center to point1 400 tickCoords = new JXG.Coords(JXG.COORDS_BY_USER, center.slice(1), this.board); 401 d = p1.coords.distance(JXG.COORDS_BY_USER, tickCoords); 402 if ((p2.X()-p1.X())*(center[1]-p1.X())<0 || (p2.Y()-p1.Y())*(center[2]-p1.Y())<0) { 403 d *= -1; 404 } 405 tickPosition = Math.round(d/ticksDelta)*ticksDelta; 406 407 // Find the correct direction of center from point1 408 if (Math.abs(tickPosition)>JXG.Math.eps) { 409 dir = Math.abs(tickPosition)/tickPosition; 410 } 411 // From now on, we jump around center 412 center[1] = p1.coords.usrCoords[1] + deltaX*tickPosition; 413 center[2] = p1.coords.usrCoords[2] + deltaY*tickPosition; 414 startTick = tickPosition; 415 tickPosition = 0; 416 417 symbTickPosition = 0; 418 // this could be done more elaborate to prevent rounding errors 419 symbStartTick = startTick/this.visProp.scale; 420 421 nx = center[1]; 422 ny = center[2]; 423 i = 0; // counter for label ids 424 j = 0; 425 // Now, we jump around center 426 // until we are outside of the canvas. 427 // If this is the case we proceed in the other 428 // direction until we are out of the canvas in this direction, too. 429 // Then we are done. 430 do { 431 tickCoords = new JXG.Coords(JXG.COORDS_BY_USER, [nx, ny], this.board); 432 433 // Test if tick is a major tick. 434 // This is the case if (dir*tickPosition+startTick)/ticksDelta is 435 // a multiple of the number of minorticks+1 436 tickCoords.major = Math.round((dir * tickPosition + startTick) / ticksDelta) % (this.visProp.minorticks + 1) === 0; 437 438 // Compute the start position and the end position of a tick. 439 // If both positions are out of the canvas, ti is empty. 440 ti = this._tickEndings(tickCoords, dx, dy, dxMaj, dyMaj, dxMin, dyMin, tickCoords.major); 441 442 // The tick has an overlap with the board? 443 if (ti.length === 3) { 444 pos = dir*symbTickPosition+symbStartTick; 445 if ((Math.abs(pos) >= eps || this.visProp.drawzero) && (pos > lb && pos < ub)) { 446 this.ticks.push(ti); 447 448 if (tickCoords.major) { 449 this.labels.push(this._makeLabel(pos, tickCoords, this.board, this.visProp.drawlabels, this.id, i)); 450 } else { 451 this.labels.push(null); 452 } 453 i++; 454 } 455 456 // Toggle direction 457 if (dirs==2) { 458 dir *= (-1); 459 } 460 // Increase distance from center 461 if (j % 2 === 0 || dirs === 1) { 462 tickPosition += ticksDelta; 463 symbTickPosition += symbTicksDelta; 464 } 465 } else { 466 dir *= (-1); 467 dirs--; 468 } 469 470 j++; 471 472 nx = center[1] + dir*deltaX*tickPosition; 473 ny = center[2] + dir*deltaY*tickPosition; 474 } while (dirs>0); 475 476 this.needsUpdate = true; 477 this.updateRenderer(); 478 }, 479 480 /** 481 * @private 482 */ 483 _adjustTickDistance: function(ticksDelta, distScr, factor, p1c, deltaX, deltaY) { 484 var nx, ny, f = 1; 485 486 while (distScr > 4*this.minTicksDistance) { 487 f /= 10; 488 nx = p1c.usrCoords[1] + deltaX*ticksDelta*f; 489 ny = p1c.usrCoords[2] + deltaY*ticksDelta*f; 490 distScr = p1c.distance(JXG.COORDS_BY_SCREEN, new JXG.Coords(JXG.COORDS_BY_USER, [nx, ny], this.board)); 491 } 492 493 // If necessary, enlarge ticksDelta 494 while (distScr < this.minTicksDistance) { 495 f *= factor; 496 factor = (factor == 5 ? 2 : 5); 497 nx = p1c.usrCoords[1] + deltaX*ticksDelta*f; 498 ny = p1c.usrCoords[2] + deltaY*ticksDelta*f; 499 distScr = p1c.distance(JXG.COORDS_BY_SCREEN, new JXG.Coords(JXG.COORDS_BY_USER, [nx, ny], this.board)); 500 } 501 return f; 502 }, 503 504 /** 505 * @param {JXG.Coords} coords Coordinates of the tick on the line. 506 * @param {Number} dx horizontal tick extension in user coordinates. 507 * @param {Number} dy vertical tick extension in user coordinates. 508 * @param {Number} dxMaj horizontal tick direction in screen coordinates. 509 * @param {Number} dyMaj vertical tick direction in screen coordinates. 510 * @param {Number} dxMin horizontal tick direction in screen coordinates. 511 * @param {Number} dyMin vertical tick direction in screen coordinates. 512 * @param {Boolean} major True if tick is major tick. 513 * @return {Array} Array of length 3 containing start and end coordinates in screen coordinates 514 * of the tick (arrays of length 2). 3rd entry is true if major tick otherwise false. 515 * If the tick is outside of the canvas, the return array is empty. 516 * @private 517 */ 518 _tickEndings: function(coords, dx, dy, dxMaj, dyMaj, dxMin, dyMin, major) { 519 var i, c, 520 cw = this.board.canvasWidth, 521 ch = this.board.canvasHeight, 522 x = [-1000*cw, -1000*ch], 523 y = [-1000*cw, -1000*ch], 524 dxs, dys, 525 s, style, 526 count = 0, 527 isInsideCanvas = false; 528 529 c = coords.scrCoords; 530 if (major) { 531 dxs = dxMaj; 532 dys = dyMaj; 533 style = this.majStyle; 534 } else { 535 dxs = dxMin; 536 dys = dyMin; 537 style = this.minStyle; 538 } 539 540 // This is necessary to compute the correct direction of infinite grid lines 541 // if unitX!=unitY. 542 //dxs = dx; //*this.board.unitX; 543 //dys = dy; //*this.board.unitY; 544 545 // For all ticks regardless if of finite or infinite 546 // tick length the intersection with the canvas border is 547 // computed. 548 549 // horizontal line and vertical tick 550 if (Math.abs(dx)<JXG.Math.eps) { 551 x[0] = c[1]; 552 x[1] = c[1]; 553 y[0] = 0; 554 y[1] = ch; 555 // vertical line and horizontal tick 556 } else if (Math.abs(dy)<JXG.Math.eps) { 557 x[0] = 0; 558 x[1] = cw; 559 y[0] = c[2]; 560 y[1] = c[2]; 561 // other 562 } else { 563 count = 0; 564 s = JXG.Math.crossProduct([0,0,1], [-dys*c[1]-dxs*c[2], dys, dxs]); // intersect with top 565 s[1] /= s[0]; 566 if (s[1]>=0 && s[1]<=cw) { 567 x[count] = s[1]; 568 y[count] = 0; 569 count++; 570 } 571 s = JXG.Math.crossProduct([0,1,0], [-dys*c[1]-dxs*c[2], dys, dxs]); // intersect with left 572 s[2] /= s[0]; 573 if (s[2]>=0 && s[2]<=ch) { 574 x[count] = 0; 575 y[count] = s[2]; 576 count++; 577 } 578 if (count<2) { 579 s = JXG.Math.crossProduct([ch*ch,0,-ch], [-dys*c[1]-dxs*c[2], dys, dxs]); // intersect with bottom 580 s[1] /= s[0]; 581 if (s[1]>=0 && s[1]<=cw) { 582 x[count] = s[1]; 583 y[count] = ch; 584 count++; 585 } 586 } 587 if (count<2) { 588 s = JXG.Math.crossProduct([cw*cw, -cw, 0], [-dys*c[1]-dxs*c[2], dys, dxs]); // intersect with right 589 s[2] /= s[0]; 590 if (s[2]>=0 && s[2]<=ch) { 591 x[count] = cw; 592 y[count] = s[2]; 593 } 594 } 595 } 596 if ((x[0]>=0 && x[0]<=cw && y[0]>=0 && y[0]<=ch ) 597 || 598 (x[1]>=0 && x[1]<=cw && y[1]>=0 && y[1]<=ch) 599 ) { 600 isInsideCanvas = true; 601 } else { 602 isInsideCanvas = false; 603 } 604 // finite tick length 605 if (style=='finite') { 606 x[0] = c[1] + dxs; 607 y[0] = c[2] - dys; 608 x[1] = c[1] - dxs; 609 y[1] = c[2] + dys; 610 } 611 if (isInsideCanvas) { 612 return [x,y, major]; 613 } else { 614 return []; 615 } 616 }, 617 618 /** 619 * Create a tick label 620 * @private 621 **/ 622 _makeLabel: function(pos, newTick, board, drawLabels, id, i) { 623 var labelText, label, attr, num = typeof pos === 'number'; 624 625 if (!drawLabels) { 626 return null; 627 } 628 629 labelText = pos.toString(); 630 if (Math.abs(pos) < JXG.Math.eps) { 631 labelText = '0'; 632 } 633 634 if(num && (labelText.length > 5 || labelText.indexOf('e') != -1)) { 635 labelText = pos.toPrecision(3).toString(); 636 } 637 if (num && labelText.indexOf('.') > -1) { 638 // trim trailing zeros 639 labelText = labelText.replace(/0+$/, ''); 640 // trim trailing . 641 labelText = labelText.replace(/\.$/, ''); 642 } 643 644 if (this.visProp.scalesymbol.length > 0 && labelText === '1') { 645 labelText = this.visProp.scalesymbol; 646 } else if (this.visProp.scalesymbol.length > 0 && labelText === '0') { 647 labelText = '0'; 648 } else { 649 labelText = labelText + this.visProp.scalesymbol; 650 } 651 652 attr = { 653 id: id + i + 'Label', 654 isLabel: true, 655 layer: board.options.layer.line, 656 highlightStrokeColor: board.options.text.strokeColor, 657 highlightStrokeWidth: board.options.text.strokeWidth, 658 highlightStrokeOpacity: board.options.text.strokeOpacity, 659 visible: this.visProp.visible, 660 priv: this.visProp.priv 661 }; 662 attr = JXG.deepCopy(attr, this.visProp.label); 663 label = JXG.createText(board, [newTick.usrCoords[1], newTick.usrCoords[2], labelText], attr); 664 label.isDraggable = false; 665 label.dump = false; 666 667 /* 668 * Ticks have their own label handling which is done below and not 669 * in Text.update(). 670 * The reason is that there is no parent element for the labels 671 * which can determine the label position. 672 */ 673 //label.distanceX = 4; 674 //label.distanceY = -parseInt(label.visProp.fontsize)+3; //-9; 675 label.distanceX = this.visProp.label.offset[0]; 676 label.distanceY = this.visProp.label.offset[1]; 677 label.setCoords(newTick.usrCoords[1] + label.distanceX / (board.unitX), 678 newTick.usrCoords[2] + label.distanceY / (board.unitY)); 679 680 label.visProp.visible = drawLabels; 681 //label.prepareUpdate().update().updateRenderer(); 682 return label; 683 }, 684 685 /** 686 * Removes the HTML divs of the tick labels 687 * before repositioning 688 * @private 689 */ 690 removeTickLabels: function () { 691 var j; 692 693 // remove existing tick labels 694 if(this.labels != null) { 695 if ((this.board.needsFullUpdate||this.needsRegularUpdate) && 696 !(this.board.options.renderer=='canvas'&&this.board.options.text.display=='internal') 697 ) { 698 for(j=0; j<this.labels.length; j++) { 699 if(this.labels[j] != null) { 700 this.board.removeObject(this.labels[j]); 701 } 702 } 703 } 704 } 705 }, 706 707 /** 708 * Recalculate the tick positions and the labels. 709 */ 710 update: function () { 711 if (this.needsUpdate) { 712 this.calculateTicksCoordinates(); 713 } 714 return this; 715 }, 716 717 /** 718 * Uses the boards renderer to update the arc. 719 */ 720 updateRenderer: function () { 721 if (this.needsUpdate) { 722 if (this.ticks) { 723 this.board.renderer.updateTicks(this, this.dxMaj, this.dyMaj, this.dxMin, this.dyMin, this.minStyle, this.majStyle); 724 } 725 this.needsUpdate = false; 726 } 727 return this; 728 }, 729 730 hideElement: function () { 731 var i; 732 733 this.visProp.visible = false; 734 this.board.renderer.hide(this); 735 736 for (i = 0; i < this.labels.length; i++) { 737 if (JXG.exists(this.labels[i])) 738 this.labels[i].hideElement(); 739 } 740 741 return this; 742 }, 743 744 showElement: function () { 745 var i; 746 747 this.visProp.visible = true; 748 this.board.renderer.show(this); 749 750 for (i = 0; i < this.labels.length; i++) { 751 if (JXG.exists(this.labels[i])) 752 this.labels[i].showElement(); 753 } 754 755 return this; 756 } 757 }); 758 759 /** 760 * @class Ticks are used as distance markers on a line. 761 * @pseudo 762 * @description 763 * @name Ticks 764 * @augments JXG.Ticks 765 * @constructor 766 * @type JXG.Ticks 767 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 768 * @param {JXG.Line,Number} line,_distance The parents consist of the line the ticks are going to be attached to and optional the 769 * distance between two major ticks. If no distance is given the attribute {@link JXG.Ticks#ticksDistance} is used. 770 * @example 771 * // Create an axis providing two coord pairs. 772 * var p1 = board.create('point', [0, 3]); 773 * var p2 = board.create('point', [1, 3]); 774 * var l1 = board.create('line', [p1, p2]); 775 * var t = board.create('ticks', [l1], {ticksDistance: 2}); 776 * </pre><div id="ee7f2d68-75fc-4ec0-9931-c76918427e63" style="width: 300px; height: 300px;"></div> 777 * <script type="text/javascript"> 778 * (function () { 779 * var board = JXG.JSXGraph.initBoard('ee7f2d68-75fc-4ec0-9931-c76918427e63', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false}); 780 * var p1 = board.create('point', [0, 3]); 781 * var p2 = board.create('point', [1, 3]); 782 * var l1 = board.create('line', [p1, p2]); 783 * var t = board.create('ticks', [l1], {ticksDistance: 2}); 784 * })(); 785 * </script><pre> 786 */ 787 JXG.createTicks = function(board, parents, attributes) { 788 var el, dist, 789 attr = JXG.copyAttributes(attributes, board.options, 'ticks'); 790 791 if (parents.length < 2) { 792 dist = attributes.ticksDistance; 793 } else { 794 dist = parents[1]; 795 } 796 797 if ( (parents[0].elementClass == JXG.OBJECT_CLASS_LINE) && (JXG.isFunction(parents[1]) || JXG.isArray(parents[1]) || JXG.isNumber(parents[1]))) { 798 el = new JXG.Ticks(parents[0], dist, attr); 799 } else { 800 throw new Error("JSXGraph: Can't create Ticks with parent types '" + (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'."); 801 } 802 803 el.isDraggable = true; 804 805 return el; 806 }; 807 808 JXG.JSXGraph.registerElement('ticks', JXG.createTicks); 809