1 /* 2 Copyright 2008-2013 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/> 29 and <http://opensource.org/licenses/MIT/>. 30 */ 31 32 33 /*global JXG: true, define: true*/ 34 /*jslint nomen: true, plusplus: true*/ 35 36 /* depends: 37 jxg 38 math/math 39 math/geometry 40 base/constants 41 base/element 42 base/coords 43 utils/type 44 elements: 45 text 46 */ 47 48 /** 49 * @fileoverview In this file the geometry object Ticks is defined. Ticks provides 50 * methods for creation and management of ticks on an axis. 51 * @author graphjs 52 * @version 0.1 53 */ 54 55 define([ 56 'jxg', 'math/math', 'math/geometry', 'base/constants', 'base/element', 'base/coords', 'utils/type', 'base/text' 57 ], function (JXG, Mat, Geometry, Const, GeometryElement, Coords, Type, Text) { 58 59 "use strict"; 60 61 /** 62 * Creates ticks for an axis. 63 * @class Ticks provides methods for creation and management 64 * of ticks on an axis. 65 * @param {JXG.Line} line Reference to the axis the ticks are drawn on. 66 * @param {Number|Array} ticks Number defining the distance between two major ticks or an array defining static ticks. 67 * @param {Object} attributes Properties 68 * @see JXG.Line#addTicks 69 * @constructor 70 * @extends JXG.GeometryElement 71 */ 72 JXG.Ticks = function (line, ticks, attributes) { 73 this.constructor(line.board, attributes, Const.OBJECT_TYPE_TICKS, Const.OBJECT_CLASS_OTHER); 74 75 /** 76 * The line the ticks belong to. 77 * @type JXG.Line 78 */ 79 this.line = line; 80 81 /** 82 * The board the ticks line is drawn on. 83 * @type JXG.Board 84 */ 85 this.board = this.line.board; 86 87 /** 88 * A function calculating ticks delta depending on the ticks number. 89 * @type Function 90 */ 91 this.ticksFunction = null; 92 93 /** 94 * Array of fixed ticks. 95 * @type Array 96 */ 97 this.fixedTicks = null; 98 99 /** 100 * Equidistant ticks. Distance is defined by ticksFunction 101 * @type Boolean 102 */ 103 this.equidistant = false; 104 105 if (Type.isFunction(ticks)) { 106 this.ticksFunction = ticks; 107 throw new Error("Function arguments are no longer supported."); 108 } else if (Type.isArray(ticks)) { 109 this.fixedTicks = ticks; 110 } else { 111 if (Math.abs(ticks) < Mat.eps) { 112 ticks = attributes.defaultdistance; 113 } 114 115 this.ticksFunction = function () { 116 return ticks; 117 }; 118 this.equidistant = true; 119 } 120 121 /** 122 * Least distance between two ticks, measured in pixels. 123 * @type int 124 */ 125 this.minTicksDistance = attributes.minticksdistance; 126 127 /** 128 * Maximum distance between two ticks, measured in pixels. Is used only when insertTicks 129 * is set to true. 130 * @type int 131 * @see #insertTicks 132 * @deprecated This value will be ignored. 133 */ 134 this.maxTicksDistance = attributes.maxticksdistance; 135 136 /** 137 * Array where the labels are saved. There is an array element for every tick, 138 * even for minor ticks which don't have labels. In this case the array element 139 * contains just <tt>null</tt>. 140 * @type Array 141 */ 142 this.labels = []; 143 144 /** 145 * A list of labels that are currently unused and ready for reassignment. 146 * @type {Array} 147 */ 148 this.labelsRepo = []; 149 150 /** 151 * To ensure the uniqueness of label ids this counter is used. 152 * @type {number} 153 */ 154 this.labelCounter = 0; 155 156 this.id = this.line.addTicks(this); 157 this.board.setId(this, 'Ti'); 158 }; 159 160 JXG.Ticks.prototype = new GeometryElement(); 161 162 JXG.extend(JXG.Ticks.prototype, /** @lends JXG.Ticks.prototype */ { 163 /** 164 * Checks whether (x,y) is near the line. 165 * @param {Number} x Coordinate in x direction, screen coordinates. 166 * @param {Number} y Coordinate in y direction, screen coordinates. 167 * @return {Boolean} True if (x,y) is near the line, False otherwise. 168 */ 169 hasPoint: function (x, y) { 170 var i, t, 171 len = (this.ticks && this.ticks.length) || 0, 172 r = this.board.options.precision.hasPoint; 173 174 if (!this.line.visProp.scalable) { 175 return false; 176 } 177 178 // Ignore non-axes and axes that are not horizontal or vertical 179 if (this.line.stdform[1] !== 0 && this.line.stdform[2] !== 0 && this.line.type !== Const.OBJECT_TYPE_AXIS) { 180 return false; 181 } 182 183 for (i = 0; i < len; i++) { 184 t = this.ticks[i]; 185 186 // Skip minor ticks 187 if (t[2]) { 188 // Ignore ticks at zero 189 if (!((this.line.stdform[1] === 0 && Math.abs(t[0][0] - this.line.point1.coords.scrCoords[1]) < Mat.eps) || 190 (this.line.stdform[2] === 0 && Math.abs(t[1][0] - this.line.point1.coords.scrCoords[2]) < Mat.eps))) { 191 // tick length is not zero, ie. at least one pixel 192 if (Math.abs(t[0][0] - t[0][1]) >= 1 || Math.abs(t[1][0] - t[1][1]) >= 1) { 193 if (this.line.stdform[1] === 0) { 194 // Allow dragging near axes only. 195 if (Math.abs(y - (t[1][0] + t[1][1]) * 0.5) < 2 * r && t[0][0] - r < x && x < t[0][1] + r) { 196 return true; 197 } 198 } else if (this.line.stdform[2] === 0) { 199 if (Math.abs(x - (t[0][0] + t[0][1]) * 0.5) < 2 * r && t[1][0] - r < y && y < t[1][1] + r) { 200 return true; 201 } 202 } 203 } 204 } 205 } 206 } 207 208 return false; 209 }, 210 211 generateLabelValue: function (tick, center) { 212 var anchor = this.visProp.anchor, 213 f = -1, 214 p1 = this.line.point1, 215 p2 = this.line.point2; 216 217 // horizontal axis 218 if (anchor === 'left' && Math.abs(p1.coords.usrCoords[2] - p2.coords.usrCoords[2]) < Mat.eps) { 219 return tick.usrCoords[1]; 220 } 221 222 // vertical axis 223 if (anchor === 'left' && Math.abs(p1.coords.usrCoords[1] - p2.coords.usrCoords[1]) < Mat.eps) { 224 return tick.usrCoords[2]; 225 } 226 227 if ((this.visProp.anchor === 'right' && !Geometry.isSameDirection(p2.coords, p1.coords, tick)) || 228 (this.visProp.anchor !== 'right' && Geometry.isSameDirection(p1.coords, p2.coords, tick))) { 229 f = 1; 230 } 231 232 return f * center.distance(Const.COORDS_BY_USER, tick); 233 }, 234 235 /** 236 * Sets x and y coordinate of the tick. 237 * @param {number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 238 * @param {Array} coords coordinates in screen/user units 239 * @param {Array} oldcoords previous coordinates in screen/user units 240 * @returns {JXG.Ticks} this element 241 */ 242 setPositionDirectly: function (method, coords, oldcoords) { 243 var dx, dy, i, 244 c = new Coords(method, coords, this.board), 245 oldc = new Coords(method, oldcoords, this.board), 246 bb = this.board.getBoundingBox(); 247 248 249 if (!this.line.visProp.scalable) { 250 return this; 251 } 252 253 // horizontal line 254 if (Math.abs(this.line.stdform[1]) < Mat.eps && Math.abs(c.usrCoords[1] * oldc.usrCoords[1]) > Mat.eps) { 255 dx = oldc.usrCoords[1] / c.usrCoords[1]; 256 bb[0] *= dx; 257 bb[2] *= dx; 258 this.board.setBoundingBox(bb, false); 259 // vertical line 260 } else if (Math.abs(this.line.stdform[2]) < Mat.eps && Math.abs(c.usrCoords[2] * oldc.usrCoords[2]) > Mat.eps) { 261 dy = oldc.usrCoords[2] / c.usrCoords[2]; 262 bb[3] *= dy; 263 bb[1] *= dy; 264 this.board.setBoundingBox(bb, false); 265 } 266 267 return this; 268 }, 269 270 /** 271 * (Re-)calculates the ticks coordinates. 272 */ 273 calculateTicksCoordinates: function () { 274 var center, d, bb, perp, coordsZero, 275 symbTicksDelta, f, 276 // Point 1 of the line 277 p1 = this.line.point1, 278 // Point 2 of the line 279 p2 = this.line.point2, 280 // Distance between the two points from above 281 distP1P2 = p1.Dist(p2), 282 // Distance of X coordinates of two major ticks 283 // Initialized with the distance of Point 1 to a point between Point 1 and Point 2 on the line and with distance 1 284 // this equals always 1 for lines parallel to x = 0 or y = 0. It's only important for lines other than that. 285 deltaX = (p2.coords.usrCoords[1] - p1.coords.usrCoords[1]) / distP1P2, 286 // The same thing for Y coordinates 287 deltaY = (p2.coords.usrCoords[2] - p1.coords.usrCoords[2]) / distP1P2, 288 // Distance of p1 to the unit point in screen coordinates 289 distScr = p1.coords.distance(Const.COORDS_BY_SCREEN, new Coords(Const.COORDS_BY_USER, [p1.coords.usrCoords[1] + deltaX, p1.coords.usrCoords[2] + deltaY], this.board)), 290 // Distance between two major ticks in user coordinates 291 ticksDelta = (this.equidistant ? this.ticksFunction(1) : 1), 292 // This factor is for enlarging ticksDelta and it switches between 5 and 2 293 // Hence, if two major ticks are too close together they'll be expanded to a distance of 5 294 // if they're still too close together, they'll be expanded to a distance of 10 etc 295 factor = 5, 296 // Coordinates of the current tick 297 tickCoords, 298 // Coordinates of the first drawn tick 299 startTick, symbStartTick, 300 // two counters 301 i, j, 302 // the distance of the tick to p1. Is displayed on the board using a label 303 // for majorTicks 304 tickPosition, 305 symbTickPosition, 306 // infinite or finite tick length 307 style, 308 // new position 309 nx = 0, 310 ny = 0, 311 ti, 312 dirs = 2, 313 dir = -1, 314 315 // the following variables are used to define ticks height and slope 316 eps = Mat.eps, 317 pos, lb, ub, 318 distMaj = this.visProp.majorheight * 0.5, 319 distMin = this.visProp.minorheight * 0.5, 320 // ticks width and height in screen units 321 dxMaj, dyMaj, 322 dxMin, dyMin, 323 // ticks width and height in user units 324 dx, dy, 325 oldRepoLength = this.labelsRepo.length; 326 // END OF variable declaration 327 328 // This will trap this update routine in an endless loop. Besides, there's not much we can show 329 // on such a tiny board, so we just get out of here immediately. 330 if (this.board.canvasWidth === 0 || this.board.canvasHeight === 0) { 331 return; 332 } 333 334 // Grid-like ticks 335 if (this.visProp.minorheight < 0) { 336 this.minStyle = 'infinite'; 337 } else { 338 this.minStyle = 'finite'; 339 } 340 341 if (this.visProp.majorheight < 0) { 342 this.majStyle = 'infinite'; 343 } else { 344 this.majStyle = 'finite'; 345 } 346 347 if (this.visProp.anchor === 'right') { 348 coordsZero = p2.coords; 349 } else if (this.visProp.anchor === 'middle') { 350 coordsZero = new Coords(JXG.COORDS_BY_USER, [ 351 (p1.coords.usrCoords[1] + p2.coords.usrCoords[1]) / 2, 352 (p1.coords.usrCoords[2] + p2.coords.usrCoords[2]) / 2 353 ], this.board); 354 } else { 355 coordsZero = p1.coords; 356 } 357 358 // Set lower and upper bound for the tick distance. 359 // This is necessary for segments. 360 if (this.line.visProp.straightfirst) { 361 lb = Number.NEGATIVE_INFINITY; 362 } else { 363 if (this.visProp.anchor === 'middle') { 364 lb = -distP1P2 / 2 + eps; 365 } else if (this.visProp.anchor === 'right') { 366 lb = -distP1P2 + eps; 367 } else { 368 lb = eps; 369 } 370 } 371 372 if (this.line.visProp.straightlast) { 373 ub = Number.POSITIVE_INFINITY; 374 } else { 375 if (this.visProp.anchor === 'middle') { 376 ub = distP1P2 / 2 - eps; 377 } else if (this.visProp.anchor === 'right') { 378 ub = -eps; 379 } else { 380 ub = distP1P2 - eps; 381 } 382 } 383 384 // This piece of code used to be in AbstractRenderer.updateAxisTicksInnerLoop 385 // and has been moved in here to clean up the renderers code. 386 // 387 // The code above only calculates the position of the ticks. The following code parts 388 // calculate the dx and dy values which make ticks out of this positions, i.e. from the 389 // position (p_x, p_y) calculated above we have to draw a line from 390 // (p_x - dx, py - dy) to (p_x + dx, p_y + dy) to get a tick. 391 dxMaj = this.line.stdform[1]; 392 dyMaj = this.line.stdform[2]; 393 dxMin = dxMaj; 394 dyMin = dyMaj; 395 dx = dxMaj; 396 dy = dyMaj; 397 398 // After this, the length of the vector (dxMaj, dyMaj) in screen coordinates is equal to distMaj pixel. 399 d = Math.sqrt(dxMaj * dxMaj * this.board.unitX * this.board.unitX + dyMaj * dyMaj * this.board.unitY * this.board.unitY); 400 dxMaj *= distMaj / d * this.board.unitX; 401 dyMaj *= distMaj / d * this.board.unitY; 402 dxMin *= distMin / d * this.board.unitX; 403 dyMin *= distMin / d * this.board.unitY; 404 405 // Begin cleanup 406 this.removeTickLabels(); 407 408 // If the parent line is not finite, we can stop here. 409 if (Math.abs(dx) < Mat.eps && Math.abs(dy) < Mat.eps) { 410 return; 411 } 412 413 // initialize storage arrays 414 // ticks stores the ticks coordinates 415 this.ticks = []; 416 417 // labels stores the text to display beside the ticks 418 this.labels = []; 419 // END cleanup 420 421 // we have an array of fixed ticks we have to draw 422 if (!this.equidistant) { 423 for (i = 0; i < this.fixedTicks.length; i++) { 424 nx = coordsZero.usrCoords[1] + this.fixedTicks[i] * deltaX; 425 ny = coordsZero.usrCoords[2] + this.fixedTicks[i] * deltaY; 426 tickCoords = new Coords(Const.COORDS_BY_USER, [nx, ny], this.board); 427 ti = this._tickEndings(tickCoords, dx, dy, dxMaj, dyMaj, dxMin, dyMin, /*major:*/ true); 428 429 // Compute the start position and the end position of a tick. 430 // If both positions are out of the canvas, ti is empty. 431 if (ti.length === 3 && this.fixedTicks[i] >= lb && this.fixedTicks[i] < ub) { 432 this.ticks.push(ti); 433 } 434 435 this.labels.push(this._makeLabel(this.visProp.labels[i] || this.fixedTicks[i], tickCoords, this.board, this.visProp.drawlabels, this.id, i, coordsZero)); 436 // visibility test missing 437 } 438 return; 439 } 440 441 // ok, we have equidistant ticks and not special ticks, so we continue here with generating them: 442 443 symbTicksDelta = ticksDelta; 444 ticksDelta *= this.visProp.scale; 445 446 // adjust distances 447 if (this.visProp.insertticks && this.minTicksDistance > Mat.eps) { 448 f = this._adjustTickDistance(ticksDelta, distScr, factor, coordsZero, deltaX, deltaY); 449 ticksDelta *= f; 450 symbTicksDelta *= f; 451 } 452 453 if (!this.visProp.insertticks) { 454 ticksDelta /= this.visProp.minorticks + 1; 455 symbTicksDelta /= this.visProp.minorticks + 1; 456 } 457 this.ticksDelta = ticksDelta; 458 459 460 461 // We shoot into the middle of the canvas 462 // to the tick position which is closest to the center 463 // of the canvas. We do this by an orthogonal projection 464 // of the canvas center to the line and by rounding of the 465 // distance of the projected point to point1 of the line. 466 // This position is saved in 467 // center and startTick. 468 bb = this.board.getBoundingBox(); 469 nx = (bb[0] + bb[2]) * 0.5; 470 ny = (bb[1] + bb[3]) * 0.5; 471 472 // Project the center of the canvas to the line. 473 perp = [ 474 nx * this.line.stdform[2] - ny * this.line.stdform[1], 475 -this.line.stdform[2], 476 this.line.stdform[1] 477 ]; 478 center = Mat.crossProduct(this.line.stdform, perp); 479 center[1] /= center[0]; 480 center[2] /= center[0]; 481 center[0] = 1; 482 483 // Round the distance of center to point1 484 tickCoords = new Coords(Const.COORDS_BY_USER, center, this.board); 485 d = coordsZero.distance(Const.COORDS_BY_USER, tickCoords); 486 487 if ((p2.X() - p1.X()) * (center[1] - p1.X()) < 0 || (p2.Y() - p1.Y()) * (center[2] - p1.Y()) < 0) { 488 d *= -1; 489 } 490 tickPosition = Math.round(d / ticksDelta) * ticksDelta; 491 492 // Find the correct direction of center from point1 493 if (Math.abs(tickPosition) > Mat.eps) { 494 dir = Math.abs(tickPosition) / tickPosition; 495 } 496 497 // From now on, we jump around center 498 center[1] = coordsZero.usrCoords[1] + deltaX * tickPosition; 499 center[2] = coordsZero.usrCoords[2] + deltaY * tickPosition; 500 startTick = tickPosition; 501 tickPosition = 0; 502 503 symbTickPosition = 0; 504 // this could be done more elaborate to prevent rounding errors 505 symbStartTick = startTick / this.visProp.scale; 506 507 nx = center[1]; 508 ny = center[2]; 509 510 // counter for label ids 511 i = 0; 512 j = 0; 513 514 // Now, we jump around the center 515 // until we are outside of the canvas. 516 // If this is the case we proceed in the other 517 // direction until we are out of the canvas in this direction, too. 518 // Then we are done. 519 do { 520 tickCoords = new Coords(Const.COORDS_BY_USER, [nx, ny], this.board); 521 522 // Test if tick is a major tick. 523 // This is the case if (dir*tickPosition+startTick)/ticksDelta is 524 // a multiple of the number of minorticks+1 525 tickCoords.major = Math.round((dir * tickPosition + startTick) / ticksDelta) % (this.visProp.minorticks + 1) === 0; 526 527 // Compute the start position and the end position of a tick. 528 // If both positions are out of the canvas, ti is empty. 529 ti = this._tickEndings(tickCoords, dx, dy, dxMaj, dyMaj, dxMin, dyMin, tickCoords.major); 530 531 // The tick has an overlap with the board? 532 if (ti.length === 3) { 533 pos = dir * symbTickPosition + symbStartTick; 534 if ((Math.abs(pos) >= eps || this.visProp.drawzero) && (pos > lb && pos < ub)) { 535 this.ticks.push(ti); 536 537 if (tickCoords.major) { 538 this.labels.push(this._makeLabel(pos, tickCoords, this.board, this.visProp.drawlabels, this.id, i, coordsZero)); 539 } else { 540 this.labels.push(null); 541 } 542 i++; 543 } 544 545 // Toggle direction 546 if (dirs === 2) { 547 dir *= (-1); 548 } 549 550 // Increase distance from center 551 if (j % 2 === 0 || dirs === 1) { 552 tickPosition += ticksDelta; 553 symbTickPosition += symbTicksDelta; 554 } 555 } else { 556 dir *= (-1); 557 dirs -= 1; 558 } 559 560 j++; 561 562 nx = center[1] + dir * deltaX * tickPosition; 563 ny = center[2] + dir * deltaY * tickPosition; 564 } while (dirs > 0); 565 566 for (i = oldRepoLength; i < this.labelsRepo.length; i++) { 567 this.labelsRepo[i].setAttribute({visible: false}); 568 } 569 570 this.needsUpdate = true; 571 this.updateRenderer(); 572 }, 573 574 /** 575 * @private 576 */ 577 _adjustTickDistance: function (ticksDelta, distScr, factor, p1c, deltaX, deltaY) { 578 var nx, ny, f = 1; 579 580 while (distScr > 4 * this.minTicksDistance) { 581 f /= 10; 582 nx = p1c.usrCoords[1] + deltaX * ticksDelta * f; 583 ny = p1c.usrCoords[2] + deltaY * ticksDelta * f; 584 distScr = p1c.distance(Const.COORDS_BY_SCREEN, new Coords(Const.COORDS_BY_USER, [nx, ny], this.board)); 585 } 586 587 // If necessary, enlarge ticksDelta 588 while (distScr <= this.minTicksDistance) { 589 f *= factor; 590 factor = (factor === 5 ? 2 : 5); 591 nx = p1c.usrCoords[1] + deltaX * ticksDelta * f; 592 ny = p1c.usrCoords[2] + deltaY * ticksDelta * f; 593 distScr = p1c.distance(Const.COORDS_BY_SCREEN, new Coords(Const.COORDS_BY_USER, [nx, ny], this.board)); 594 } 595 596 return f; 597 }, 598 599 /** 600 * @param {JXG.Coords} coords Coordinates of the tick on the line. 601 * @param {Number} dx horizontal tick extension in user coordinates. 602 * @param {Number} dy vertical tick extension in user coordinates. 603 * @param {Number} dxMaj horizontal tick direction in screen coordinates. 604 * @param {Number} dyMaj vertical tick direction in screen coordinates. 605 * @param {Number} dxMin horizontal tick direction in screen coordinates. 606 * @param {Number} dyMin vertical tick direction in screen coordinates. 607 * @param {Boolean} major True if tick is major tick. 608 * @return {Array} Array of length 3 containing start and end coordinates in screen coordinates 609 * of the tick (arrays of length 2). 3rd entry is true if major tick otherwise false. 610 * If the tick is outside of the canvas, the return array is empty. 611 * @private 612 */ 613 _tickEndings: function (coords, dx, dy, dxMaj, dyMaj, dxMin, dyMin, major) { 614 var i, c, 615 cw = this.board.canvasWidth, 616 ch = this.board.canvasHeight, 617 x = [-1000 * cw, -1000 * ch], 618 y = [-1000 * cw, -1000 * ch], 619 dxs, dys, 620 s, style, 621 count = 0, 622 isInsideCanvas = false; 623 624 c = coords.scrCoords; 625 if (major) { 626 dxs = dxMaj; 627 dys = dyMaj; 628 style = this.majStyle; 629 } else { 630 dxs = dxMin; 631 dys = dyMin; 632 style = this.minStyle; 633 } 634 635 // For all ticks regardless if of finite or infinite 636 // tick length the intersection with the canvas border is 637 // computed. 638 639 // horizontal line and vertical tick 640 if (Math.abs(dx) < Mat.eps) { 641 x[0] = c[1]; 642 x[1] = c[1]; 643 y[0] = 0; 644 y[1] = ch; 645 // vertical line and horizontal tick 646 } else if (Math.abs(dy) < Mat.eps) { 647 x[0] = 0; 648 x[1] = cw; 649 y[0] = c[2]; 650 y[1] = c[2]; 651 // other 652 } else { 653 count = 0; 654 655 // intersect with top 656 s = Mat.crossProduct([0, 0, 1], [-dys * c[1] - dxs * c[2], dys, dxs]); 657 s[1] /= s[0]; 658 if (s[1] >= 0 && s[1] <= cw) { 659 x[count] = s[1]; 660 y[count] = 0; 661 count++; 662 } 663 664 // intersect with left 665 s = Mat.crossProduct([0, 1, 0], [-dys * c[1] - dxs * c[2], dys, dxs]); 666 s[2] /= s[0]; 667 if (s[2] >= 0 && s[2] <= ch) { 668 x[count] = 0; 669 y[count] = s[2]; 670 count++; 671 } 672 673 if (count < 2) { 674 // intersect with bottom 675 s = Mat.crossProduct([ch * ch, 0, -ch], [-dys * c[1] - dxs * c[2], dys, dxs]); 676 s[1] /= s[0]; 677 if (s[1] >= 0 && s[1] <= cw) { 678 x[count] = s[1]; 679 y[count] = ch; 680 count++; 681 } 682 } 683 if (count < 2) { 684 // intersect with right 685 s = Mat.crossProduct([cw * cw, -cw, 0], [-dys * c[1] - dxs * c[2], dys, dxs]); 686 s[2] /= s[0]; 687 if (s[2] >= 0 && s[2] <= ch) { 688 x[count] = cw; 689 y[count] = s[2]; 690 } 691 } 692 } 693 694 isInsideCanvas = (x[0] >= 0 && x[0] <= cw && y[0] >= 0 && y[0] <= ch) || 695 (x[1] >= 0 && x[1] <= cw && y[1] >= 0 && y[1] <= ch); 696 697 // finite tick length 698 if (style === 'finite') { 699 x[0] = c[1] + dxs * this.visProp.tickendings[0]; 700 y[0] = c[2] - dys * this.visProp.tickendings[0]; 701 x[1] = c[1] - dxs * this.visProp.tickendings[1]; 702 y[1] = c[2] + dys * this.visProp.tickendings[1]; 703 } 704 705 if (isInsideCanvas) { 706 return [x, y, major]; 707 } 708 709 return []; 710 }, 711 712 /** 713 * Create a tick label 714 * @param {Number} pos 715 * @param {JXG.Coords} newTick 716 * @param {JXG.Board} board 717 * @param {Boolean} drawLabels 718 * @param {String} id Id of the ticks object 719 * @param {Number} i 720 * @param {JXG.Coords} center 721 * @returns {JXG.Text} 722 * @private 723 */ 724 _makeLabel: function (pos, newTick, board, drawLabels, id, i, center) { 725 var labelText, label, attr, 726 num = (typeof pos === 'number'); 727 728 if (!drawLabels) { 729 return null; 730 } 731 732 // Correct label also for frozen tick lines. 733 if (this.equidistant) { 734 pos = this.generateLabelValue(newTick, center) / this.visProp.scale; 735 } 736 737 labelText = pos.toString(); 738 if (newTick.distance(Const.COORDS_BY_USER, center) < Mat.eps) { 739 labelText = '0'; 740 } 741 742 if (num && (labelText.length > this.visProp.maxlabellength || labelText.indexOf('e') !== -1)) { 743 labelText = pos.toPrecision(this.visProp.precision).toString(); 744 } 745 if (num && labelText.indexOf('.') > -1 && labelText.indexOf('e') === -1) { 746 // trim trailing zeros 747 labelText = labelText.replace(/0+$/, ''); 748 // trim trailing . 749 labelText = labelText.replace(/\.$/, ''); 750 } 751 752 if (this.visProp.scalesymbol.length > 0 && labelText === '1') { 753 labelText = this.visProp.scalesymbol; 754 } else if (this.visProp.scalesymbol.length > 0 && labelText === '0') { 755 labelText = '0'; 756 } else { 757 labelText = labelText + this.visProp.scalesymbol; 758 } 759 760 attr = { 761 isLabel: true, 762 layer: board.options.layer.line, 763 highlightStrokeColor: board.options.text.strokeColor, 764 highlightStrokeWidth: board.options.text.strokeWidth, 765 highlightStrokeOpacity: board.options.text.strokeOpacity, 766 visible: this.visProp.visible, 767 priv: this.visProp.priv 768 }; 769 attr = Type.deepCopy(attr, this.visProp.label); 770 771 if (this.labelsRepo.length > 0) { 772 label = this.labelsRepo.splice(this.labelsRepo.length - 1, 1)[0]; 773 // this is done later on anyways 774 //label.setCoords(newTick.usrCoords[1], newTick.usrCoords[2]); 775 label.setText(labelText); 776 label.setAttribute(attr); 777 } else { 778 this.labelCounter += 1; 779 attr.id = id + i + 'Label' + this.labelCounter; 780 label = Text.createText(board, [newTick.usrCoords[1], newTick.usrCoords[2], labelText], attr); 781 } 782 label.isDraggable = false; 783 label.dump = false; 784 785 /* 786 * Ticks have their own label handling which is done below and not 787 * in Text.update(). 788 * The reason is that there is no parent element for the labels 789 * which can determine the label position. 790 */ 791 //label.distanceX = 4; 792 //label.distanceY = -parseInt(label.visProp.fontsize)+3; //-9; 793 label.distanceX = this.visProp.label.offset[0]; 794 label.distanceY = this.visProp.label.offset[1]; 795 label.setCoords(newTick.usrCoords[1] + label.distanceX / (board.unitX), 796 newTick.usrCoords[2] + label.distanceY / (board.unitY)); 797 798 label.visProp.visible = drawLabels; 799 //label.prepareUpdate().update().updateRenderer(); 800 return label; 801 }, 802 803 /** 804 * Removes the HTML divs of the tick labels 805 * before repositioning 806 * @private 807 */ 808 removeTickLabels: function () { 809 var j; 810 811 // remove existing tick labels 812 if (Type.exists(this.labels)) { 813 if ((this.board.needsFullUpdate || this.needsRegularUpdate || this.needsUpdate) && 814 !(this.board.renderer.type === 'canvas' && this.board.options.text.display === 'internal')) { 815 for (j = 0; j < this.labels.length; j++) { 816 if (Type.exists(this.labels[j])) { 817 //this.board.removeObject(this.labels[j]); 818 this.labelsRepo.push(this.labels[j]); 819 } 820 } 821 } 822 } 823 }, 824 825 /** 826 * Recalculate the tick positions and the labels. 827 * @returns {JXG.Ticks} 828 */ 829 update: function () { 830 if (this.needsUpdate) { 831 this.calculateTicksCoordinates(); 832 } 833 834 return this; 835 }, 836 837 /** 838 * Uses the boards renderer to update the arc. 839 * @returns {JXG.Ticks} 840 */ 841 updateRenderer: function () { 842 if (this.needsUpdate) { 843 if (this.ticks) { 844 this.board.renderer.updateTicks(this, this.dxMaj, this.dyMaj, this.dxMin, this.dyMin, this.minStyle, this.majStyle); 845 } 846 this.needsUpdate = false; 847 } 848 849 return this; 850 }, 851 852 hideElement: function () { 853 var i; 854 855 this.visProp.visible = false; 856 this.board.renderer.hide(this); 857 858 for (i = 0; i < this.labels.length; i++) { 859 if (Type.exists(this.labels[i])) { 860 this.labels[i].hideElement(); 861 } 862 } 863 864 return this; 865 }, 866 867 showElement: function () { 868 var i; 869 870 this.visProp.visible = true; 871 this.board.renderer.show(this); 872 873 for (i = 0; i < this.labels.length; i++) { 874 if (Type.exists(this.labels[i])) { 875 this.labels[i].showElement(); 876 } 877 } 878 879 return this; 880 } 881 }); 882 883 /** 884 * @class Ticks are used as distance markers on a line. 885 * @pseudo 886 * @description 887 * @name Ticks 888 * @augments JXG.Ticks 889 * @constructor 890 * @type JXG.Ticks 891 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 892 * @param {JXG.Line,Number,Function} line,_distance,_generateLabelFunc The parents consist of the line the ticks are going to be attached to and the 893 * distance between two major ticks. 894 * The third parameter (optional) is a function which determines the tick label. It has as parameter a coords object containing the coordinates of the new tick. 895 * @example 896 * // Create an axis providing two coord pairs. 897 * var p1 = board.create('point', [0, 3]); 898 * var p2 = board.create('point', [1, 3]); 899 * var l1 = board.create('line', [p1, p2]); 900 * var t = board.create('ticks', [l1], {ticksDistance: 2}); 901 * </pre><div id="ee7f2d68-75fc-4ec0-9931-c76918427e63" style="width: 300px; height: 300px;"></div> 902 * <script type="text/javascript"> 903 * (function () { 904 * var board = JXG.JSXGraph.initBoard('ee7f2d68-75fc-4ec0-9931-c76918427e63', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false}); 905 * var p1 = board.create('point', [0, 3]); 906 * var p2 = board.create('point', [1, 3]); 907 * var l1 = board.create('line', [p1, p2]); 908 * var t = board.create('ticks', [l1, 2], {ticksDistance: 2}); 909 * })(); 910 * </script><pre> 911 */ 912 JXG.createTicks = function (board, parents, attributes) { 913 var el, dist, 914 attr = Type.copyAttributes(attributes, board.options, 'ticks'); 915 916 if (parents.length < 2) { 917 dist = attr.ticksdistance; 918 } else { 919 dist = parents[1]; 920 } 921 922 if (parents[0].elementClass === Const.OBJECT_CLASS_LINE) { 923 el = new JXG.Ticks(parents[0], dist, attr); 924 } else { 925 throw new Error("JSXGraph: Can't create Ticks with parent types '" + (typeof parents[0]) + "'."); 926 } 927 928 if (typeof attr.generatelabelvalue === 'function') { 929 el.generateLabelValue = attr.generatelabelvalue; 930 } 931 932 el.isDraggable = true; 933 934 return el; 935 }; 936 937 /** 938 * @class Hashes can be used to mark congruent lines. 939 * @pseudo 940 * @description 941 * @name Hatch 942 * @augments JXG.Ticks 943 * @constructor 944 * @type JXG.Ticks 945 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 946 * @param {JXG.Line,Number} line,numberofhashes The parents consist of the line the hatch marks are going to be attached to and the 947 * number of dashes. 948 * @example 949 * // Create an axis providing two coord pairs. 950 * var p1 = board.create('point', [0, 3]); 951 * var p2 = board.create('point', [1, 3]); 952 * var l1 = board.create('line', [p1, p2]); 953 * var t = board.create('hatch', [l1, 3]); 954 * </pre><div id="4a20af06-4395-451c-b7d1-002757cf01be" style="width: 300px; height: 300px;"></div> 955 * <script type="text/javascript"> 956 * (function () { 957 * var board = JXG.JSXGraph.initBoard('4a20af06-4395-451c-b7d1-002757cf01be', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false}); 958 * var p1 = board.create('point', [0, 3]); 959 * var p2 = board.create('point', [1, 3]); 960 * var l1 = board.create('line', [p1, p2]); 961 * var t = board.create('hatch', [l1, 3]); 962 * })(); 963 * </script><pre> 964 */ 965 JXG.createHatchmark = function (board, parents, attributes) { 966 var num, i, base, width, totalwidth, el, 967 pos = [], 968 attr = Type.copyAttributes(attributes, board.options, 'hatch'); 969 970 if (parents[0].elementClass !== Const.OBJECT_CLASS_LINE || typeof parents[1] !== 'number') { 971 throw new Error("JSXGraph: Can't create Hatch mark with parent types '" + (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'."); 972 } 973 974 num = parents[1]; 975 width = attr.ticksdistance; 976 totalwidth = (num - 1) * width; 977 base = -totalwidth / 2; 978 979 for (i = 0; i < num; i++) { 980 pos[i] = base + i * width; 981 } 982 983 el = board.create('ticks', [parents[0], pos], attr); 984 el.elType = 'hatch'; 985 }; 986 987 JXG.registerElement('ticks', JXG.createTicks); 988 JXG.registerElement('hash', JXG.createHatchmark); 989 JXG.registerElement('hatch', JXG.createHatchmark); 990 991 return { 992 Ticks: JXG.Ticks, 993 createTicks: JXG.createTicks, 994 createHashmark: JXG.createHatchmark, 995 createHatchmark: JXG.createHatchmark 996 }; 997 }); 998