1 /* 2 Copyright 2008-2022 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', 'math/numerics', 'base/constants', 'base/element', 'base/coords', 'utils/type', 'base/text' 57 ], function (JXG, Mat, Geometry, Numerics, 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 this.labelsData = []; 106 107 if (Type.isFunction(ticks)) { 108 this.ticksFunction = ticks; 109 throw new Error("Function arguments are no longer supported."); 110 } 111 112 if (Type.isArray(ticks)) { 113 this.fixedTicks = ticks; 114 } else { 115 if (Math.abs(ticks) < Mat.eps || ticks < 0) { 116 ticks = attributes.defaultdistance; 117 } 118 119 /* 120 * Ticks function: 121 * determines the distance (in user units) of two major ticks 122 */ 123 this.ticksFunction = this.makeTicksFunction(ticks); 124 125 this.equidistant = true; 126 } 127 128 /** 129 * Least distance between two ticks, measured in pixels. 130 * @type int 131 */ 132 this.minTicksDistance = attributes.minticksdistance; 133 134 /** 135 * Stores the ticks coordinates 136 * @type Array 137 */ 138 this.ticks = []; 139 140 /** 141 * Distance between two major ticks in user coordinates 142 * @type Number 143 */ 144 this.ticksDelta = 1; 145 146 /** 147 * Array where the labels are saved. There is an array element for every tick, 148 * even for minor ticks which don't have labels. In this case the array element 149 * contains just <tt>null</tt>. 150 * @type Array 151 */ 152 this.labels = []; 153 154 /** 155 * A list of labels which have to be displayed in updateRenderer. 156 * @type Array 157 */ 158 this.labelData = []; 159 160 /** 161 * To ensure the uniqueness of label ids this counter is used. 162 * @type number 163 */ 164 this.labelCounter = 0; 165 166 this.id = this.line.addTicks(this); 167 this.elType = 'ticks'; 168 this.inherits.push(this.labels); 169 this.board.setId(this, 'Ti'); 170 }; 171 172 JXG.Ticks.prototype = new GeometryElement(); 173 174 JXG.extend(JXG.Ticks.prototype, /** @lends JXG.Ticks.prototype */ { 175 176 /** 177 * Ticks function: 178 * determines the distance (in user units) of two major ticks. 179 * See above in constructor and in @see JXG.GeometryElement#setAttribute 180 * 181 * @private 182 * @param {Number} ticks Distance between two major ticks 183 * @returns {Function} returns method ticksFunction 184 */ 185 makeTicksFunction: function (ticks) { 186 return function () { 187 var delta, b, dist; 188 189 if (Type.evaluate(this.visProp.insertticks)) { 190 b = this.getLowerAndUpperBounds(this.getZeroCoordinates(), 'ticksdistance'); 191 dist = b.upper - b.lower; 192 193 delta = Math.pow(10, Math.floor(Math.log(0.6 * dist) / Math.LN10)); 194 if (dist <= 6 * delta) { 195 delta *= 0.5; 196 } 197 return delta; 198 } 199 200 // upto 0.99.1: 201 return ticks; 202 }; 203 }, 204 205 /** 206 * Checks whether (x,y) is near the line. 207 * Only available for line elements, not for ticks on curves. 208 * @param {Number} x Coordinate in x direction, screen coordinates. 209 * @param {Number} y Coordinate in y direction, screen coordinates. 210 * @returns {Boolean} True if (x,y) is near the line, False otherwise. 211 */ 212 hasPoint: function (x, y) { 213 var i, t, 214 len = (this.ticks && this.ticks.length) || 0, 215 r, type; 216 217 if (Type.isObject(Type.evaluate(this.visProp.precision))) { 218 type = this.board._inputDevice; 219 r = Type.evaluate(this.visProp.precision[type]); 220 } else { 221 // 'inherit' 222 r = this.board.options.precision.hasPoint; 223 } 224 r += Type.evaluate(this.visProp.strokewidth) * 0.5; 225 if (!Type.evaluate(this.line.visProp.scalable) || 226 this.line.elementClass === Const.OBJECT_CLASS_CURVE) { 227 return false; 228 } 229 230 // Ignore non-axes and axes that are not horizontal or vertical 231 if (this.line.stdform[1] !== 0 && this.line.stdform[2] !== 0 && this.line.type !== Const.OBJECT_TYPE_AXIS) { 232 return false; 233 } 234 235 for (i = 0; i < len; i++) { 236 t = this.ticks[i]; 237 238 // Skip minor ticks 239 if (t[2]) { 240 // Ignore ticks at zero 241 if (!((this.line.stdform[1] === 0 && Math.abs(t[0][0] - this.line.point1.coords.scrCoords[1]) < Mat.eps) || 242 (this.line.stdform[2] === 0 && Math.abs(t[1][0] - this.line.point1.coords.scrCoords[2]) < Mat.eps))) { 243 // tick length is not zero, ie. at least one pixel 244 if (Math.abs(t[0][0] - t[0][1]) >= 1 || Math.abs(t[1][0] - t[1][1]) >= 1) { 245 if (this.line.stdform[1] === 0) { 246 // Allow dragging near axes only. 247 if (Math.abs(y - (t[1][0] + t[1][1]) * 0.5) < 2 * r && t[0][0] - r < x && x < t[0][1] + r) { 248 return true; 249 } 250 } else if (this.line.stdform[2] === 0) { 251 if (Math.abs(x - (t[0][0] + t[0][1]) * 0.5) < 2 * r && t[1][0] - r < y && y < t[1][1] + r) { 252 return true; 253 } 254 } 255 } 256 } 257 } 258 } 259 260 return false; 261 }, 262 263 /** 264 * Sets x and y coordinate of the tick. 265 * @param {number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 266 * @param {Array} coords coordinates in screen/user units 267 * @param {Array} oldcoords previous coordinates in screen/user units 268 * @returns {JXG.Ticks} this element 269 */ 270 setPositionDirectly: function (method, coords, oldcoords) { 271 var dx, dy, 272 c = new Coords(method, coords, this.board), 273 oldc = new Coords(method, oldcoords, this.board), 274 bb = this.board.getBoundingBox(); 275 276 if (this.line.type !== Const.OBJECT_TYPE_AXIS || 277 !Type.evaluate(this.line.visProp.scalable)) { 278 279 return this; 280 } 281 282 if (Math.abs(this.line.stdform[1]) < Mat.eps && 283 Math.abs(c.usrCoords[1] * oldc.usrCoords[1]) > Mat.eps) { 284 285 // Horizontal line 286 dx = oldc.usrCoords[1] / c.usrCoords[1]; 287 bb[0] *= dx; 288 bb[2] *= dx; 289 this.board.setBoundingBox(bb, this.board.keepaspectratio, 'update'); 290 291 } else if (Math.abs(this.line.stdform[2]) < Mat.eps && 292 Math.abs(c.usrCoords[2] * oldc.usrCoords[2]) > Mat.eps) { 293 294 // Vertical line 295 dy = oldc.usrCoords[2] / c.usrCoords[2]; 296 bb[3] *= dy; 297 bb[1] *= dy; 298 this.board.setBoundingBox(bb, this.board.keepaspectratio, 'update'); 299 } 300 301 return this; 302 }, 303 304 /** 305 * (Re-)calculates the ticks coordinates. 306 * @private 307 */ 308 calculateTicksCoordinates: function () { 309 var coordsZero, bounds, 310 r_max, bb; 311 312 if (this.line.elementClass === Const.OBJECT_CLASS_LINE) { 313 // Calculate Ticks width and height in Screen and User Coordinates 314 this.setTicksSizeVariables(); 315 316 // If the parent line is not finite, we can stop here. 317 if (Math.abs(this.dx) < Mat.eps && 318 Math.abs(this.dy) < Mat.eps) { 319 return; 320 } 321 } 322 323 // Get Zero (coords element for lines , number for curves) 324 coordsZero = this.getZeroCoordinates(); 325 326 // Calculate lower bound and upper bound limits based on distance 327 // between p1 and center and p2 and center 328 if (this.line.elementClass === Const.OBJECT_CLASS_LINE) { 329 bounds = this.getLowerAndUpperBounds(coordsZero); 330 } else { 331 bounds = { 332 lower: this.line.minX(), 333 upper: this.line.maxX() 334 }; 335 } 336 337 if (Type.evaluate(this.visProp.type) === 'polar') { 338 bb = this.board.getBoundingBox(); 339 r_max = Math.max(Math.sqrt(bb[0] * bb[0] + bb[1] * bb[1]), 340 Math.sqrt(bb[2] * bb[2] + bb[3] * bb[3])); 341 bounds.upper = r_max; 342 } 343 344 // Clean up 345 this.ticks = []; 346 this.labelsData = []; 347 // Create Ticks Coordinates and Labels 348 if (this.equidistant) { 349 this.generateEquidistantTicks(coordsZero, bounds); 350 } else { 351 this.generateFixedTicks(coordsZero, bounds); 352 } 353 354 return this; 355 }, 356 357 /** 358 * Sets the variables used to set the height and slope of each tick. 359 * 360 * @private 361 */ 362 setTicksSizeVariables: function (pos) { 363 var d, mi, ma, len, 364 distMaj = Type.evaluate(this.visProp.majorheight) * 0.5, 365 distMin = Type.evaluate(this.visProp.minorheight) * 0.5; 366 367 // For curves: 368 if (Type.exists(pos)) { 369 mi = this.line.minX(); 370 ma = this.line.maxX(); 371 len = this.line.points.length; 372 if (len < 2) { 373 this.dxMaj = 0; 374 this.dyMaj = 0; 375 } else if (Mat.relDif(pos, mi) < Mat.eps) { 376 this.dxMaj = this.line.points[0].usrCoords[2] - this.line.points[1].usrCoords[2]; 377 this.dyMaj = this.line.points[1].usrCoords[1] - this.line.points[0].usrCoords[1]; 378 } else if (Mat.relDif(pos, ma) < Mat.eps) { 379 this.dxMaj = this.line.points[len - 2].usrCoords[2] - this.line.points[len - 1].usrCoords[2]; 380 this.dyMaj = this.line.points[len - 1].usrCoords[1] - this.line.points[len - 2].usrCoords[1]; 381 } else { 382 this.dxMaj = -Numerics.D(this.line.Y)(pos); 383 this.dyMaj = Numerics.D(this.line.X)(pos); 384 } 385 } else { 386 // ticks width and height in screen units 387 this.dxMaj = this.line.stdform[1]; 388 this.dyMaj = this.line.stdform[2]; 389 } 390 this.dxMin = this.dxMaj; 391 this.dyMin = this.dyMaj; 392 393 // ticks width and height in user units 394 this.dx = this.dxMaj; 395 this.dy = this.dyMaj; 396 397 // After this, the length of the vector (dxMaj, dyMaj) in screen coordinates is equal to distMaj pixel. 398 d = Math.sqrt( 399 this.dxMaj * this.dxMaj * this.board.unitX * this.board.unitX + 400 this.dyMaj * this.dyMaj * this.board.unitY * this.board.unitY 401 ); 402 this.dxMaj *= distMaj / d * this.board.unitX; 403 this.dyMaj *= distMaj / d * this.board.unitY; 404 this.dxMin *= distMin / d * this.board.unitX; 405 this.dyMin *= distMin / d * this.board.unitY; 406 407 // Grid-like ticks? 408 this.minStyle= (Type.evaluate(this.visProp.minorheight) < 0) ? 'infinite' : 'finite'; 409 this.majStyle= (Type.evaluate(this.visProp.majorheight) < 0) ? 'infinite' : 'finite'; 410 }, 411 412 /** 413 * Returns the coordinates of the point zero of the line. 414 * 415 * If the line is an {@link Axis}, the coordinates of the projection of the board's zero point is returned 416 * 417 * Otherwise, the coordinates of the point that acts as zero are 418 * established depending on the value of {@link JXG.Ticks#anchor} 419 * 420 * @returns {JXG.Coords} Coords object for the zero point on the line 421 * @private 422 */ 423 getZeroCoordinates: function () { 424 var c1x, c1y, c1z, c2x, c2y, c2z, t, mi, ma, 425 ev_a = Type.evaluate(this.visProp.anchor); 426 427 if (this.line.elementClass === Const.OBJECT_CLASS_LINE) { 428 if (this.line.type === Const.OBJECT_TYPE_AXIS) { 429 return Geometry.projectPointToLine({ 430 coords: { 431 usrCoords: [1, 0, 0] 432 } 433 }, this.line, this.board); 434 } 435 c1z = this.line.point1.coords.usrCoords[0]; 436 c1x = this.line.point1.coords.usrCoords[1]; 437 c1y = this.line.point1.coords.usrCoords[2]; 438 c2z = this.line.point2.coords.usrCoords[0]; 439 c2x = this.line.point2.coords.usrCoords[1]; 440 c2y = this.line.point2.coords.usrCoords[2]; 441 442 if (ev_a === 'right') { 443 return this.line.point2.coords; 444 } 445 if (ev_a === 'middle') { 446 return new Coords(Const.COORDS_BY_USER, [ 447 (c1z + c2z) * 0.5, 448 (c1x + c2x) * 0.5, 449 (c1y + c2y) * 0.5 450 ], this.board); 451 } 452 if (Type.isNumber(ev_a)) { 453 return new Coords(Const.COORDS_BY_USER, [ 454 c1z + (c2z - c1z) * ev_a, 455 c1x + (c2x - c1x) * ev_a, 456 c1y + (c2y - c1y) * ev_a 457 ], this.board); 458 } 459 return this.line.point1.coords; 460 } 461 mi = this.line.minX(); 462 ma = this.line.maxX(); 463 if (ev_a === 'right') { 464 t = ma; 465 } else if (ev_a === 'middle') { 466 t = (mi + ma) * 0.5; 467 } else if (Type.isNumber(ev_a)) { 468 t = mi * (1 - ev_a) + ma * ev_a; 469 // t = ev_a; 470 } else { 471 t = mi; 472 } 473 return t; 474 }, 475 476 /** 477 * Calculate the lower and upper bounds for tick rendering 478 * If {@link JXG.Ticks#includeBoundaries} is false, the boundaries will exclude point1 and point2 479 * 480 * @param {JXG.Coords} coordsZero 481 * @returns {String} type (Optional) If type=='ticksdistance' the bounds are 482 * the intersection of the line with the bounding box of the board. 483 * Otherwise, it is the projection of the corners of the bounding box 484 * to the line. The first case is needed to automatically 485 * generate ticks. The second case is for drawing of the ticks. 486 * @returns {Object} contains the lower and upper bounds 487 * 488 * @private 489 */ 490 getLowerAndUpperBounds: function (coordsZero, type) { 491 var lowerBound, upperBound, 492 fA, lA, 493 point1, point2, isPoint1inBoard, isPoint2inBoard, 494 // We use the distance from zero to P1 and P2 to establish lower and higher points 495 dZeroPoint1, dZeroPoint2, 496 ev_sf = Type.evaluate(this.line.visProp.straightfirst), 497 ev_sl = Type.evaluate(this.line.visProp.straightlast), 498 ev_i = Type.evaluate(this.visProp.includeboundaries); 499 500 // The line's defining points that will be adjusted to be within the board limits 501 if (this.line.elementClass === Const.OBJECT_CLASS_CURVE) { 502 return { 503 lower: this.line.minX(), 504 upper: this.line.maxX() 505 }; 506 } 507 508 point1 = new Coords(Const.COORDS_BY_USER, this.line.point1.coords.usrCoords, this.board); 509 point2 = new Coords(Const.COORDS_BY_USER, this.line.point2.coords.usrCoords, this.board); 510 // Are the original defining points within the board? 511 isPoint1inBoard = (Math.abs(point1.usrCoords[0]) >= Mat.eps && 512 point1.scrCoords[1] >= 0.0 && point1.scrCoords[1] <= this.board.canvasWidth && 513 point1.scrCoords[2] >= 0.0 && point1.scrCoords[2] <= this.board.canvasHeight); 514 isPoint2inBoard = (Math.abs(point2.usrCoords[0]) >= Mat.eps && 515 point2.scrCoords[1] >= 0.0 && point2.scrCoords[1] <= this.board.canvasWidth && 516 point2.scrCoords[2] >= 0.0 && point2.scrCoords[2] <= this.board.canvasHeight); 517 518 // Adjust line limit points to be within the board 519 if (Type.exists(type) || type === 'tickdistance') { 520 // The good old calcStraight is needed for determining the distance between major ticks. 521 // Here, only the visual area is of importance 522 Geometry.calcStraight(this.line, point1, point2, Type.evaluate(this.line.visProp.margin)); 523 } else { 524 // This function projects the corners of the board to the line. 525 // This is important for diagonal lines with infinite tick lines. 526 Geometry.calcLineDelimitingPoints(this.line, point1, point2); 527 } 528 529 // Shorten ticks bounds such that ticks are not through arrow heads 530 fA = Type.evaluate(this.line.visProp.firstarrow); 531 lA = Type.evaluate(this.line.visProp.lastarrow); 532 if (fA || lA) { 533 this.board.renderer.getPositionArrowHead(this.line, point1, point2, 534 Type.evaluate(this.line.visProp.strokewidth)); 535 536 if (fA) { 537 point1.setCoordinates(Const.COORDS_BY_SCREEN, [ 538 point1.scrCoords[1], 539 point1.scrCoords[2] 540 ]); 541 } 542 if (lA) { 543 point2.setCoordinates(Const.COORDS_BY_SCREEN, [ 544 point2.scrCoords[1], 545 point2.scrCoords[2] 546 ]); 547 } 548 // if (fA) { 549 // point1.setCoordinates(Const.COORDS_BY_SCREEN, [ 550 // point1.scrCoords[1] - obj.d1x, 551 // point1.scrCoords[2] - obj.d1y 552 // ]); 553 // } 554 // if (lA) { 555 // point2.setCoordinates(Const.COORDS_BY_SCREEN, [ 556 // point2.scrCoords[1] - obj.d2x, 557 // point2.scrCoords[2] - obj.d2y 558 // ]); 559 // } 560 } 561 562 563 // Calculate (signed) distance from Zero to P1 and to P2 564 dZeroPoint1 = this.getDistanceFromZero(coordsZero, point1); 565 dZeroPoint2 = this.getDistanceFromZero(coordsZero, point2); 566 567 // We have to establish if the direction is P1->P2 or P2->P1 to set the lower and upper 568 // boundaries appropriately. As the distances contain also a sign to indicate direction, 569 // we can compare dZeroPoint1 and dZeroPoint2 to establish the line direction 570 if (dZeroPoint1 < dZeroPoint2) { // Line goes P1->P2 571 lowerBound = dZeroPoint1; 572 if (!ev_sf && isPoint1inBoard && !ev_i) { 573 lowerBound += Mat.eps; 574 } 575 upperBound = dZeroPoint2; 576 if (!ev_sl && isPoint2inBoard && !ev_i) { 577 upperBound -= Mat.eps; 578 } 579 } else if (dZeroPoint2 < dZeroPoint1) { // Line goes P2->P1 580 lowerBound = dZeroPoint2; 581 if (!ev_sl && isPoint2inBoard && !ev_i) { 582 lowerBound += Mat.eps; 583 } 584 upperBound = dZeroPoint1; 585 if (!ev_sf && isPoint1inBoard && !ev_i) { 586 upperBound -= Mat.eps; 587 } 588 } else { // P1 = P2 = Zero, we can't do a thing 589 lowerBound = 0; 590 upperBound = 0; 591 } 592 593 return { 594 lower: lowerBound, 595 upper: upperBound 596 }; 597 }, 598 599 /** 600 * Calculates the distance in user coordinates from zero to a given point including its sign. 601 * Sign is positive, if the direction from zero to point is the same as the direction 602 * zero to point2 of the line. 603 * 604 * @param {JXG.Coords} zero coordinates of the point considered zero 605 * @param {JXG.Coords} point coordinates of the point to find out the distance 606 * @returns {Number} distance between zero and point, including its sign 607 * @private 608 */ 609 getDistanceFromZero: function (zero, point) { 610 var p1, p2, 611 dirLine, dirPoint, 612 distance; 613 614 p1 = this.line.point1.coords; 615 p2 = this.line.point2.coords; 616 distance = zero.distance(Const.COORDS_BY_USER, point); 617 618 // Establish sign 619 dirLine = [p2.usrCoords[0] - p1.usrCoords[0], 620 p2.usrCoords[1] - p1.usrCoords[1], 621 p2.usrCoords[2] - p1.usrCoords[2]]; 622 dirPoint = [point.usrCoords[0] - zero.usrCoords[0], 623 point.usrCoords[1] - zero.usrCoords[1], 624 point.usrCoords[2] - zero.usrCoords[2]]; 625 if (Mat.innerProduct(dirLine, dirPoint, 3) < 0) { 626 distance *= -1; 627 } 628 629 return distance; 630 }, 631 632 /** 633 * Creates ticks coordinates and labels automatically. 634 * The frequency of ticks is affected by the values of {@link JXG.Ticks#insertTicks} and {@link JXG.Ticks#ticksDistance} 635 * 636 * @param {JXG.Coords} coordsZero coordinates of the point considered zero 637 * @param {Object} bounds contains the lower and upper boundaries for ticks placement 638 * @private 639 */ 640 generateEquidistantTicks: function (coordsZero, bounds) { 641 var tickPosition, 642 eps2 = Mat.eps, 643 deltas, 644 // Distance between two major ticks in user coordinates 645 ticksDelta = (this.equidistant ? this.ticksFunction(1) : this.ticksDelta), 646 ev_it = Type.evaluate(this.visProp.insertticks), 647 ev_mt = Type.evaluate(this.visProp.minorticks); 648 649 if (this.line.elementClass === Const.OBJECT_CLASS_LINE) { 650 // Calculate X and Y distance between two major ticks 651 deltas = this.getXandYdeltas(); 652 } 653 654 // adjust ticks distance 655 ticksDelta *= Type.evaluate(this.visProp.scale); 656 if (ev_it && this.minTicksDistance > Mat.eps) { 657 ticksDelta = this.adjustTickDistance(ticksDelta, coordsZero, deltas); 658 ticksDelta /= (ev_mt + 1); 659 } else if (!ev_it) { 660 ticksDelta /= (ev_mt + 1); 661 } 662 this.ticksDelta = ticksDelta; 663 664 if (ticksDelta < Mat.eps) { 665 return; 666 } 667 668 // Position ticks from zero to the positive side while not reaching the upper boundary 669 tickPosition = 0; 670 if (!Type.evaluate(this.visProp.drawzero)) { 671 tickPosition = ticksDelta; 672 } 673 while (tickPosition <= bounds.upper + eps2) { 674 // Only draw ticks when we are within bounds, ignore case where tickPosition < lower < upper 675 if (tickPosition >= bounds.lower - eps2) { 676 this.processTickPosition(coordsZero, tickPosition, ticksDelta, deltas); 677 } 678 tickPosition += ticksDelta; 679 680 // Emergency out 681 if ((bounds.upper - tickPosition) > ticksDelta * 10000) { 682 break; 683 } 684 } 685 686 // Position ticks from zero (not inclusive) to the negative side while not reaching the lower boundary 687 tickPosition = -ticksDelta; 688 while (tickPosition >= bounds.lower - eps2) { 689 // Only draw ticks when we are within bounds, ignore case where lower < upper < tickPosition 690 if (tickPosition <= bounds.upper + eps2) { 691 this.processTickPosition(coordsZero, tickPosition, ticksDelta, deltas); 692 } 693 tickPosition -= ticksDelta; 694 695 // Emergency out 696 if ((tickPosition - bounds.lower) > ticksDelta * 10000) { 697 break; 698 } 699 } 700 }, 701 702 /** 703 * Auxiliary method used by {@link JXG.Ticks#generateEquidistantTicks} to adjust the 704 * distance between two ticks depending on {@link JXG.Ticks#minTicksDistance} value 705 * 706 * @param {Number} ticksDelta distance between two major ticks in user coordinates 707 * @param {JXG.Coords} coordsZero coordinates of the point considered zero 708 * @param {Object} deltas x and y distance in pixel between two user units 709 * @param {Object} bounds upper and lower bound of the tick positions in user units. 710 * @private 711 */ 712 adjustTickDistance: function (ticksDelta, coordsZero, deltas) { 713 var nx, ny, bounds, 714 distScr, 715 sgn = 1, 716 ev_minti = Type.evaluate(this.visProp.minorticks); 717 718 if (this.line.elementClass === Const.OBJECT_CLASS_CURVE) { 719 return ticksDelta; 720 } 721 bounds = this.getLowerAndUpperBounds(coordsZero, 'ticksdistance'); 722 nx = coordsZero.usrCoords[1] + deltas.x * ticksDelta; 723 ny = coordsZero.usrCoords[2] + deltas.y * ticksDelta; 724 distScr = coordsZero.distance(Const.COORDS_BY_SCREEN, new Coords(Const.COORDS_BY_USER, [nx, ny], this.board)); 725 726 if (ticksDelta === 0.0) { 727 return 0.0; 728 } 729 730 while (distScr / (ev_minti + 1) < this.minTicksDistance) { 731 if (sgn === 1) { 732 ticksDelta *= 2; 733 } else { 734 ticksDelta *= 5; 735 } 736 sgn *= -1; 737 738 nx = coordsZero.usrCoords[1] + deltas.x * ticksDelta; 739 ny = coordsZero.usrCoords[2] + deltas.y * ticksDelta; 740 distScr = coordsZero.distance(Const.COORDS_BY_SCREEN, new Coords(Const.COORDS_BY_USER, [nx, ny], this.board)); 741 } 742 return ticksDelta; 743 }, 744 745 /** 746 * Auxiliary method used by {@link JXG.Ticks#generateEquidistantTicks} to create a tick 747 * in the line at the given tickPosition. 748 * 749 * @param {JXG.Coords} coordsZero coordinates of the point considered zero 750 * @param {Number} tickPosition current tick position relative to zero 751 * @param {Number} ticksDelta distance between two major ticks in user coordinates 752 * @param {Object} deltas x and y distance between two major ticks 753 * @private 754 */ 755 processTickPosition: function (coordsZero, tickPosition, ticksDelta, deltas) { 756 var x, y, tickCoords, ti, 757 labelVal = null; 758 759 // Calculates tick coordinates 760 if (this.line.elementClass === Const.OBJECT_CLASS_LINE) { 761 x = coordsZero.usrCoords[1] + tickPosition * deltas.x; 762 y = coordsZero.usrCoords[2] + tickPosition * deltas.y; 763 } else { 764 x = this.line.X(coordsZero + tickPosition); 765 y = this.line.Y(coordsZero + tickPosition); 766 } 767 tickCoords = new Coords(Const.COORDS_BY_USER, [x, y], this.board); 768 if (this.line.elementClass === Const.OBJECT_CLASS_CURVE) { 769 labelVal = coordsZero + tickPosition; 770 this.setTicksSizeVariables(labelVal); 771 772 } 773 774 // Test if tick is a major tick. 775 // This is the case if tickPosition/ticksDelta is 776 // a multiple of the number of minorticks+1 777 tickCoords.major = Math.round(tickPosition / ticksDelta) % (Type.evaluate(this.visProp.minorticks) + 1) === 0; 778 779 // Compute the start position and the end position of a tick. 780 // If both positions are out of the canvas, ti is empty. 781 ti = this.createTickPath(tickCoords, tickCoords.major); 782 if (ti.length === 3) { 783 this.ticks.push(ti); 784 if (tickCoords.major && Type.evaluate(this.visProp.drawlabels)) { 785 // major tick label 786 this.labelsData.push( 787 this.generateLabelData( 788 this.generateLabelText(tickCoords, coordsZero, labelVal), 789 tickCoords, 790 this.ticks.length 791 ) 792 ); 793 } else { 794 // minor ticks have no labels 795 this.labelsData.push(null); 796 } 797 } 798 }, 799 800 /** 801 * Creates ticks coordinates and labels based on {@link JXG.Ticks#fixedTicks} and {@link JXG.Ticks#labels}. 802 * 803 * @param {JXG.Coords} coordsZero Coordinates of the point considered zero 804 * @param {Object} bounds contains the lower and upper boundaries for ticks placement 805 * @private 806 */ 807 generateFixedTicks: function (coordsZero, bounds) { 808 var tickCoords, labelText, i, ti, 809 x, y, 810 eps2 = Mat.eps, fixedTick, 811 hasLabelOverrides = Type.isArray(this.visProp.labels), 812 deltas, 813 ev_dl = Type.evaluate(this.visProp.drawlabels); 814 815 // Calculate X and Y distance between two major points in the line 816 if (this.line.elementClass === Const.OBJECT_CLASS_LINE) { 817 deltas = this.getXandYdeltas(); 818 } 819 for (i = 0; i < this.fixedTicks.length; i++) { 820 if (this.line.elementClass === Const.OBJECT_CLASS_LINE) { 821 fixedTick = this.fixedTicks[i]; 822 x = coordsZero.usrCoords[1] + fixedTick * deltas.x; 823 y = coordsZero.usrCoords[2] + fixedTick * deltas.y; 824 } else { 825 fixedTick = coordsZero + this.fixedTicks[i]; 826 x = this.line.X(fixedTick); 827 y = this.line.Y(fixedTick); 828 } 829 tickCoords = new Coords(Const.COORDS_BY_USER, [x, y], this.board); 830 831 if (this.line.elementClass === Const.OBJECT_CLASS_CURVE) { 832 this.setTicksSizeVariables(fixedTick); 833 } 834 835 // Compute the start position and the end position of a tick. 836 // If tick is out of the canvas, ti is empty. 837 ti = this.createTickPath(tickCoords, true); 838 if (ti.length === 3 && fixedTick >= bounds.lower - eps2 && 839 fixedTick <= bounds.upper + eps2) { 840 this.ticks.push(ti); 841 842 if (ev_dl && 843 (hasLabelOverrides || Type.exists(this.visProp.labels[i]))) { 844 labelText = hasLabelOverrides ? 845 Type.evaluate(this.visProp.labels[i]) : fixedTick; 846 this.labelsData.push( 847 this.generateLabelData( 848 this.generateLabelText(tickCoords, coordsZero, labelText), 849 tickCoords, 850 i 851 ) 852 ); 853 } else { 854 this.labelsData.push(null); 855 } 856 } 857 } 858 }, 859 860 /** 861 * Calculates the x and y distance in pixel between two units in user space. 862 * 863 * @returns {Object} 864 * @private 865 */ 866 getXandYdeltas: function () { 867 var 868 // Auxiliary points to store the start and end of the line according to its direction 869 point1UsrCoords, point2UsrCoords, 870 distP1P2 = this.line.point1.Dist(this.line.point2); 871 872 if (this.line.type === Const.OBJECT_TYPE_AXIS) { 873 // When line is an Axis, direction depends on Board Coordinates system 874 875 // assume line.point1 and line.point2 are in correct order 876 point1UsrCoords = this.line.point1.coords.usrCoords; 877 point2UsrCoords = this.line.point2.coords.usrCoords; 878 879 // Check if direction is incorrect, then swap 880 if (point1UsrCoords[1] > point2UsrCoords[1] || 881 (Math.abs(point1UsrCoords[1] - point2UsrCoords[1]) < Mat.eps && 882 point1UsrCoords[2] > point2UsrCoords[2])) { 883 point1UsrCoords = this.line.point2.coords.usrCoords; 884 point2UsrCoords = this.line.point1.coords.usrCoords; 885 } 886 } else /* if (this.line.elementClass === Const.OBJECT_CLASS_LINE)*/ { 887 // line direction is always from P1 to P2 for non Axis types 888 point1UsrCoords = this.line.point1.coords.usrCoords; 889 point2UsrCoords = this.line.point2.coords.usrCoords; 890 } 891 return { 892 x: (point2UsrCoords[1] - point1UsrCoords[1]) / distP1P2, 893 y: (point2UsrCoords[2] - point1UsrCoords[2]) / distP1P2 894 }; 895 }, 896 897 /** 898 * Check if (parts of) the tick is inside the canvas. The tick intersects the boundary 899 * at two positions: [x[0], y[0]] and [x[1], y[1]] in screen coordinates. 900 * @param {Array} x Array of length two 901 * @param {Array} y Array of length two 902 * @return {Boolean} true if parts of the tick are inside of the canvas or on the boundary. 903 */ 904 _isInsideCanvas: function(x, y, m) { 905 var cw = this.board.canvasWidth, 906 ch = this.board.canvasHeight; 907 908 if (m === undefined) { 909 m = 0; 910 } 911 return (x[0] >= m && x[0] <= cw - m && y[0] >= m && y[0] <= ch - m) || 912 (x[1] >= m && x[1] <= cw - m && y[1] >= m && y[1] <= ch - m); 913 }, 914 915 /** 916 * @param {JXG.Coords} coords Coordinates of the tick on the line. 917 * @param {Boolean} major True if tick is major tick. 918 * @returns {Array} Array of length 3 containing path coordinates in screen coordinates 919 * of the tick (arrays of length 2). 3rd entry is true if major tick otherwise false. 920 * If the tick is outside of the canvas, the return array is empty. 921 * @private 922 */ 923 createTickPath: function (coords, major) { 924 var c, lineStdForm, intersection, 925 dxs, dys, dxr, dyr, alpha, 926 style, 927 x = [-2000000, -2000000], 928 y = [-2000000, -2000000], 929 i, r, r_max, bb, full, delta; 930 931 c = coords.scrCoords; 932 if (major) { 933 dxs = this.dxMaj; 934 dys = this.dyMaj; 935 style = this.majStyle; 936 } else { 937 dxs = this.dxMin; 938 dys = this.dyMin; 939 style = this.minStyle; 940 } 941 lineStdForm = [-dys * c[1] - dxs * c[2], dys, dxs]; 942 943 // For all ticks regardless if of finite or infinite 944 // tick length the intersection with the canvas border is 945 // computed. 946 if (major && Type.evaluate(this.visProp.type) === 'polar') { 947 // polar style 948 bb = this.board.getBoundingBox(); 949 full = 2.0 * Math.PI; 950 delta = full / 180; 951 //ratio = this.board.unitY / this.board.X; 952 953 // usrCoords: Test if 'circle' is inside of the canvas 954 c = coords.usrCoords; 955 r = Math.sqrt(c[1] * c[1] + c[2] * c[2]); 956 r_max = Math.max(Math.sqrt(bb[0] * bb[0] + bb[1] * bb[1]), 957 Math.sqrt(bb[2] * bb[2] + bb[3] * bb[3])); 958 959 if (r < r_max) { 960 // Now, switch to screen coords 961 x = []; 962 y = []; 963 for (i = 0; i <= full; i += delta) { 964 x.push(this.board.origin.scrCoords[1] + r * Math.cos(i) * this.board.unitX); 965 y.push(this.board.origin.scrCoords[2] + r * Math.sin(i) * this.board.unitY); 966 } 967 return [x, y, major]; 968 } 969 970 } else { 971 // line style 972 if (style === 'infinite') { 973 intersection = Geometry.meetLineBoard(lineStdForm, this.board); 974 x[0] = intersection[0].scrCoords[1]; 975 x[1] = intersection[1].scrCoords[1]; 976 y[0] = intersection[0].scrCoords[2]; 977 y[1] = intersection[1].scrCoords[2]; 978 } else { 979 if (Type.evaluate(this.visProp.face) === '>') { 980 alpha = Math.PI/4; 981 } else if (Type.evaluate(this.visProp.face) === '<') { 982 alpha = -Math.PI/4; 983 } else { 984 alpha = 0; 985 } 986 dxr = Math.cos(alpha) * dxs - Math.sin(alpha) * dys; 987 dyr = Math.sin(alpha) * dxs + Math.cos(alpha) * dys; 988 989 x[0] = c[1] + dxr * Type.evaluate(this.visProp.tickendings[0]); 990 y[0] = c[2] - dyr * Type.evaluate(this.visProp.tickendings[0]); 991 x[1] = c[1]; 992 y[1] = c[2]; 993 994 alpha = -alpha; 995 dxr = Math.cos(alpha) * dxs - Math.sin(alpha) * dys; 996 dyr = Math.sin(alpha) * dxs + Math.cos(alpha) * dys; 997 998 x[2] = c[1] - dxr * Type.evaluate(this.visProp.tickendings[1]); 999 y[2] = c[2] + dyr * Type.evaluate(this.visProp.tickendings[1]); 1000 } 1001 1002 // Check if (parts of) the tick is inside the canvas. 1003 if (this._isInsideCanvas(x, y)) { 1004 return [x, y, major]; 1005 } 1006 } 1007 1008 return []; 1009 }, 1010 1011 /** 1012 * Format label texts. Show the desired number of digits 1013 * and use utf-8 minus sign. 1014 * @param {Number} value Number to be displayed 1015 * @return {String} The value converted into a string. 1016 * @private 1017 */ 1018 formatLabelText: function(value) { 1019 var labelText, 1020 digits, 1021 ev_s = Type.evaluate(this.visProp.scalesymbol); 1022 1023 // if value is Number 1024 if (Type.isNumber(value)) { 1025 labelText = (Math.round(value * 1.e13) / 1.e13).toString(); 1026 if (labelText.length > Type.evaluate(this.visProp.maxlabellength) || 1027 labelText.indexOf('e') !== -1) { 1028 1029 digits = Type.evaluate(this.visProp.digits); 1030 if (Type.evaluate(this.visProp.precision) !== 3 && digits === 3) { 1031 // Use the deprecated attribute "precision" 1032 digits = Type.evaluate(this.visProp.precision); 1033 } 1034 1035 //labelText = value.toPrecision(digits).toString(); 1036 labelText = value.toExponential(digits).toString(); 1037 } 1038 1039 if (Type.evaluate(this.visProp.beautifulscientificticklabels)) { 1040 labelText = this.beautifyScientificNotationLabel(labelText); 1041 } 1042 1043 if (labelText.indexOf('.') > -1 && labelText.indexOf('e') === -1) { 1044 // trim trailing zeros 1045 labelText = labelText.replace(/0+$/, ''); 1046 // trim trailing . 1047 labelText = labelText.replace(/\.$/, ''); 1048 } 1049 } else { 1050 labelText = value.toString(); 1051 } 1052 1053 if (ev_s.length > 0) { 1054 if (labelText === '1') { 1055 labelText = ev_s; 1056 } else if (labelText === '-1') { 1057 labelText = '-' + ev_s; 1058 } else if (labelText !== '0') { 1059 labelText = labelText + ev_s; 1060 } 1061 } 1062 1063 if (Type.evaluate(this.visProp.useunicodeminus)) { 1064 labelText = labelText.replace(/-/g, '\u2212'); 1065 } 1066 return labelText; 1067 }, 1068 1069 /** 1070 * Formats label texts to make labels displayed in scientific notation look beautiful. 1071 * For example, label 5.00e+6 will become 5•10⁶, label -1.00e-7 will become into -1•10⁻⁷ 1072 * @param {String} labelText - The label that we want to convert 1073 * @returns {String} If labelText was not in scientific notation, return labelText without modifications. 1074 * Otherwise returns beautified labelText with proper superscript notation. 1075 */ 1076 beautifyScientificNotationLabel: function(labelText) { 1077 var returnString; 1078 1079 if (labelText.indexOf('e') === -1) { 1080 return labelText; 1081 } 1082 1083 // Clean up trailing 0's, so numbers like 5.00e+6.0 for example become into 5e+6 1084 returnString = parseFloat(labelText.substring(0, labelText.indexOf('e'))) + 1085 labelText.substring(labelText.indexOf('e')); 1086 1087 // Replace symbols like -,0,1,2,3,4,5,6,7,8,9 with their superscript version. 1088 // Gets rid of + symbol since there is no need for it anymore. 1089 returnString = returnString.replace(/e(.*)$/g, function(match,$1){ 1090 var temp = '\u2022' + '10'; 1091 // Note: Since board ticks do not support HTTP elements like <sub>, we need to replace 1092 // all the numbers with superscript Unicode characters. 1093 temp += $1 1094 .replace(/-/g, "\u207B") 1095 .replace(/\+/g, '') 1096 .replace(/0/g,'\u2070') 1097 .replace(/1/g,'\u00B9') 1098 .replace(/2/g,'\u00B2') 1099 .replace(/3/g,'\u00B3') 1100 .replace(/4/g,'\u2074') 1101 .replace(/5/g,'\u2075') 1102 .replace(/6/g,'\u2076') 1103 .replace(/7/g,'\u2077') 1104 .replace(/8/g,'\u2078') 1105 .replace(/9/g,'\u2079'); 1106 1107 return temp; 1108 }); 1109 1110 return returnString; 1111 }, 1112 1113 /** 1114 * Creates the label text for a given tick. A value for the text can be provided as a number or string 1115 * 1116 * @param {JXG.Coords} tick The Coords-object of the tick to create a label for 1117 * @param {JXG.Coords} zero The Coords-object of line's zero 1118 * @param {Number|String} value A predefined value for this tick 1119 * @returns {String} 1120 * @private 1121 */ 1122 generateLabelText: function (tick, zero, value) { 1123 var labelText, distance; 1124 1125 // No value provided, equidistant, so assign distance as value 1126 if (!Type.exists(value)) { // could be null or undefined 1127 distance = this.getDistanceFromZero(zero, tick); 1128 if (Math.abs(distance) < Mat.eps) { // Point is zero 1129 return '0'; 1130 } 1131 value = distance / Type.evaluate(this.visProp.scale); 1132 } 1133 labelText = this.formatLabelText(value); 1134 1135 return labelText; 1136 }, 1137 1138 /** 1139 * Create a tick label data, i.e. text and coordinates 1140 * @param {String} labelText 1141 * @param {JXG.Coords} tick 1142 * @param {Number} tickNumber 1143 * @returns {Object} with properties 'x', 'y', 't' (text), 'i' (tick number) or null in case of o label 1144 * @private 1145 */ 1146 generateLabelData: function (labelText, tick, tickNumber) { 1147 var xa, ya, m, fs; 1148 1149 // Test if large portions of the label are inside of the canvas 1150 // This is the last chance to abandon the creation of the label if it is mostly 1151 // outside of the canvas. 1152 fs = Type.evaluate(this.visProp.label.fontsize); 1153 xa = [tick.scrCoords[1], tick.scrCoords[1]]; 1154 ya = [tick.scrCoords[2], tick.scrCoords[2]]; 1155 m = (fs === undefined) ? 12 : fs; 1156 m *= 0.5; 1157 if (!this._isInsideCanvas(xa, ya, m)) { 1158 return null; 1159 } 1160 1161 xa = Type.evaluate(this.visProp.label.offset[0]); 1162 ya = Type.evaluate(this.visProp.label.offset[1]); 1163 1164 return { 1165 x: tick.usrCoords[1] + xa / (this.board.unitX), 1166 y: tick.usrCoords[2] + ya / (this.board.unitY), 1167 t: labelText, 1168 i: tickNumber 1169 }; 1170 }, 1171 1172 /** 1173 * Recalculate the tick positions and the labels. 1174 * @returns {JXG.Ticks} 1175 */ 1176 update: function () { 1177 if (this.needsUpdate) { 1178 //this.visPropCalc.visible = Type.evaluate(this.visProp.visible); 1179 // A canvas with no width or height will create an endless loop, so ignore it 1180 if (this.board.canvasWidth !== 0 && this.board.canvasHeight !== 0) { 1181 this.calculateTicksCoordinates(); 1182 } 1183 // this.updateVisibility(this.line.visPropCalc.visible); 1184 // 1185 // for (var i = 0; i < this.labels.length; i++) { 1186 // if (this.labels[i] !== null) { 1187 // this.labels[i].prepareUpdate() 1188 // .updateVisibility(this.line.visPropCalc.visible) 1189 // .updateRenderer(); 1190 // } 1191 // } 1192 } 1193 1194 return this; 1195 }, 1196 1197 /** 1198 * Uses the boards renderer to update the arc. 1199 * @returns {JXG.Ticks} Reference to the object. 1200 */ 1201 updateRenderer: function () { 1202 if (!this.needsUpdate) { 1203 return this; 1204 } 1205 1206 if (this.visPropCalc.visible) { 1207 this.board.renderer.updateTicks(this); 1208 } 1209 this.updateRendererLabels(); 1210 1211 this.setDisplayRendNode(); 1212 // if (this.visPropCalc.visible != this.visPropOld.visible) { 1213 // this.board.renderer.display(this, this.visPropCalc.visible); 1214 // this.visPropOld.visible = this.visPropCalc.visible; 1215 // } 1216 1217 this.needsUpdate = false; 1218 return this; 1219 }, 1220 1221 /** 1222 * Updates the label elements of the major ticks. 1223 * 1224 * @private 1225 * @returns {JXG.Ticks} Reference to the object. 1226 */ 1227 updateRendererLabels: function() { 1228 var i, j, 1229 lenData, lenLabels, 1230 attr, 1231 label, ld, 1232 visible; 1233 1234 // The number of labels needed 1235 lenData = this.labelsData.length; 1236 // The number of labels which already exist 1237 // The existing labels are stored in this.labels[] 1238 // The new label positions and label values are stored in this.labelsData[] 1239 lenLabels = this.labels.length; 1240 1241 for (i = 0, j = 0; i < lenData; i++) { 1242 if (this.labelsData[i] === null) { 1243 // This is a tick without label 1244 continue; 1245 } 1246 1247 ld = this.labelsData[i]; 1248 if (j < lenLabels) { 1249 // Take an already existing text element 1250 label = this.labels[j]; 1251 label.setText(ld.t); 1252 label.setCoords(ld.x, ld.y); 1253 j++; 1254 } else { 1255 // A new text element is needed 1256 this.labelCounter += 1; 1257 1258 attr = { 1259 isLabel: true, 1260 layer: this.board.options.layer.line, 1261 highlightStrokeColor: this.board.options.text.strokeColor, 1262 highlightStrokeWidth: this.board.options.text.strokeWidth, 1263 highlightStrokeOpacity: this.board.options.text.strokeOpacity, 1264 priv: this.visProp.priv 1265 }; 1266 attr = Type.deepCopy(attr, this.visProp.label); 1267 attr.id = this.id + ld.i + 'Label' + this.labelCounter; 1268 1269 label = Text.createText(this.board, [ld.x, ld.y, ld.t], attr); 1270 label.isDraggable = false; 1271 label.dump = false; 1272 this.labels.push(label); 1273 } 1274 1275 // Look-ahead if the label inherits visiblity. 1276 // If yes, update label. 1277 visible = Type.evaluate(this.visProp.label.visible); 1278 if (visible === 'inherit') { 1279 visible = this.visPropCalc.visible; 1280 } 1281 1282 label.prepareUpdate() 1283 .updateVisibility(visible) 1284 .updateRenderer(); 1285 1286 label.distanceX = Type.evaluate(this.visProp.label.offset[0]); 1287 label.distanceY = Type.evaluate(this.visProp.label.offset[1]); 1288 } 1289 1290 // Hide unused labels 1291 lenData = j; 1292 for (j = lenData; j < lenLabels; j++) { 1293 this.board.renderer.display(this.labels[j], false); 1294 // Tick labels have the attribute "visible: 'inherit'" 1295 // This must explicitely set to false, otherwise 1296 // this labels would be set to visible in the upcoming 1297 // update of the labels. 1298 this.labels[j].visProp.visible = this.labels[j].visPropCalc.visible = false; 1299 } 1300 1301 return this; 1302 }, 1303 1304 hideElement: function () { 1305 var i; 1306 1307 JXG.deprecated('Element.hideElement()', 'Element.setDisplayRendNode()'); 1308 1309 this.visPropCalc.visible = false; 1310 this.board.renderer.display(this, false); 1311 for (i = 0; i < this.labels.length; i++) { 1312 if (Type.exists(this.labels[i])) { 1313 this.labels[i].hideElement(); 1314 } 1315 } 1316 1317 return this; 1318 }, 1319 1320 showElement: function () { 1321 var i; 1322 1323 JXG.deprecated('Element.showElement()', 'Element.setDisplayRendNode()'); 1324 1325 this.visPropCalc.visible = true; 1326 this.board.renderer.display(this, false); 1327 1328 for (i = 0; i < this.labels.length; i++) { 1329 if (Type.exists(this.labels[i])) { 1330 this.labels[i].showElement(); 1331 } 1332 } 1333 1334 return this; 1335 } 1336 }); 1337 1338 /** 1339 * @class Ticks are used as distance markers on a line or curve. 1340 * They are 1341 * mainly used for axis elements and slider elements. Ticks may stretch infinitely 1342 * or finitely, which can be set with {@link Ticks#majorHeight} and {@link Ticks#minorHeight}. 1343 * 1344 * @pseudo 1345 * @description Ticks are markers on straight line elements or curves. 1346 * @name Ticks 1347 * @augments JXG.Ticks 1348 * @constructor 1349 * @type JXG.Ticks 1350 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1351 * @param {JXG.Line|JXG.Curve} line The parents consist of the line or curve the ticks are going to be attached to. 1352 * @param {Number|Array} distance Number defining the distance between two major ticks or an 1353 * array defining static ticks. In case a number is specified, the ticks are <i>equidistant</i>, 1354 * in case of an array, a fixed number of static ticks is created at user-supplied positions. 1355 * Alternatively, the distance can be specified with the attribute 1356 * "ticksDistance". For arbitrary lines (and not axes) a "zero coordinate" is determined 1357 * which defines where the first tick is positioned. This zero coordinate 1358 * can be altered with the attribute "anchor". Possible values are "left", "middle", "right" or a number. 1359 * The default value is "left". 1360 * 1361 * @example 1362 * // Create an axis providing two coordinate pairs. 1363 * var p1 = board.create('point', [0, 3]); 1364 * var p2 = board.create('point', [1, 3]); 1365 * var l1 = board.create('line', [p1, p2]); 1366 * var t = board.create('ticks', [l1], {ticksDistance: 2}); 1367 * </pre><div class="jxgbox" id="JXGee7f2d68-75fc-4ec0-9931-c76918427e63" style="width: 300px; height: 300px;"></div> 1368 * <script type="text/javascript"> 1369 * (function () { 1370 * var board = JXG.JSXGraph.initBoard('JXGee7f2d68-75fc-4ec0-9931-c76918427e63', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false}); 1371 * var p1 = board.create('point', [0, 3]); 1372 * var p2 = board.create('point', [1, 3]); 1373 * var l1 = board.create('line', [p1, p2]); 1374 * var t = board.create('ticks', [l1, 2], {ticksDistance: 2}); 1375 * })(); 1376 * </script><pre> 1377 */ 1378 JXG.createTicks = function (board, parents, attributes) { 1379 var el, dist, 1380 attr = Type.copyAttributes(attributes, board.options, 'ticks'); 1381 1382 if (parents.length < 2) { 1383 dist = attr.ticksdistance; 1384 } else { 1385 dist = parents[1]; 1386 } 1387 1388 if (parents[0].elementClass === Const.OBJECT_CLASS_LINE || 1389 parents[0].elementClass === Const.OBJECT_CLASS_CURVE) { 1390 el = new JXG.Ticks(parents[0], dist, attr); 1391 } else { 1392 throw new Error("JSXGraph: Can't create Ticks with parent types '" + (typeof parents[0]) + "'."); 1393 } 1394 1395 // deprecated 1396 if (Type.isFunction(attr.generatelabelvalue)) { 1397 el.generateLabelText = attr.generatelabelvalue; 1398 } 1399 if (Type.isFunction(attr.generatelabeltext)) { 1400 el.generateLabelText = attr.generatelabeltext; 1401 } 1402 1403 el.setParents(parents[0]); 1404 el.isDraggable = true; 1405 el.fullUpdate(parents[0].visPropCalc.visible); 1406 1407 return el; 1408 }; 1409 1410 /** 1411 * @class Hatches can be used to mark congruent lines or curves. 1412 * @pseudo 1413 * @description 1414 * @name Hatch 1415 * @augments JXG.Ticks 1416 * @constructor 1417 * @type JXG.Ticks 1418 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1419 * @param {JXG.Line|JXG.curve} line The line or curve the hatch marks are going to be attached to. 1420 * @param {Number} numberofhashes Number of dashes. 1421 * @example 1422 * // Create an axis providing two coords pairs. 1423 * var p1 = board.create('point', [0, 3]); 1424 * var p2 = board.create('point', [1, 3]); 1425 * var l1 = board.create('line', [p1, p2]); 1426 * var t = board.create('hatch', [l1, 3]); 1427 * </pre><div class="jxgbox" id="JXG4a20af06-4395-451c-b7d1-002757cf01be" style="width: 300px; height: 300px;"></div> 1428 * <script type="text/javascript"> 1429 * (function () { 1430 * var board = JXG.JSXGraph.initBoard('JXG4a20af06-4395-451c-b7d1-002757cf01be', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false}); 1431 * var p1 = board.create('point', [0, 3]); 1432 * var p2 = board.create('point', [1, 3]); 1433 * var l1 = board.create('line', [p1, p2]); 1434 * var t = board.create('hatch', [l1, 3]); 1435 * })(); 1436 * </script><pre> 1437 * 1438 * @example 1439 * // Alter the position of the hatch 1440 * 1441 * var p = board.create('point', [-5, 0]); 1442 * var q = board.create('point', [5, 0]); 1443 * var li = board.create('line', [p, q]); 1444 * var h = board.create('hatch', [li, 2], {anchor: 0.2}); 1445 * 1446 * </pre><div id="JXG05d720ee-99c9-11e6-a9c7-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 1447 * <script type="text/javascript"> 1448 * (function() { 1449 * var board = JXG.JSXGraph.initBoard('JXG05d720ee-99c9-11e6-a9c7-901b0e1b8723', 1450 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1451 * 1452 * var p = board.create('point', [-5, 0]); 1453 * var q = board.create('point', [5, 0]); 1454 * var li = board.create('line', [p, q]); 1455 * var h = board.create('hatch', [li, 2], {anchor: 0.2}); 1456 * 1457 * })(); 1458 * 1459 * </script><pre> 1460 * 1461 * @example 1462 * // Alternative hatch faces 1463 * 1464 * var li = board.create('line', [[-6,0], [6,3]]); 1465 * var h1 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'|'}); 1466 * var h2 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'>', anchor: 0.3}); 1467 * var h3 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'<', anchor: 0.7}); 1468 * 1469 * </pre><div id="JXG974f7e89-eac8-4187-9aa3-fb8068e8384b" class="jxgbox" style="width: 300px; height: 300px;"></div> 1470 * <script type="text/javascript"> 1471 * (function() { 1472 * var board = JXG.JSXGraph.initBoard('JXG974f7e89-eac8-4187-9aa3-fb8068e8384b', 1473 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1474 * // Alternative hatch faces 1475 * 1476 * var li = board.create('line', [[-6,0], [6,3]]); 1477 * var h1 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'|'}); 1478 * var h2 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'>', anchor: 0.3}); 1479 * var h3 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'<', anchor: 0.7}); 1480 * 1481 * })(); 1482 * 1483 * </script><pre> 1484 * 1485 */ 1486 JXG.createHatchmark = function (board, parents, attributes) { 1487 var num, i, base, width, totalwidth, el, 1488 pos = [], 1489 attr = Type.copyAttributes(attributes, board.options, 'hatch'); 1490 1491 if ((parents[0].elementClass !== Const.OBJECT_CLASS_LINE && 1492 parents[0].elementClass !== Const.OBJECT_CLASS_CURVE) || typeof parents[1] !== 'number') { 1493 throw new Error("JSXGraph: Can't create Hatch mark with parent types '" + (typeof parents[0]) + "' and '" + (typeof parents[1]) + " and ''" + (typeof parents[2]) + "'."); 1494 } 1495 1496 num = parents[1]; 1497 width = attr.ticksdistance; 1498 totalwidth = (num - 1) * width; 1499 base = -totalwidth * 0.5; 1500 1501 for (i = 0; i < num; i++) { 1502 pos[i] = base + i * width; 1503 } 1504 1505 el = board.create('ticks', [parents[0], pos], attr); 1506 el.elType = 'hatch'; 1507 1508 return el; 1509 }; 1510 1511 JXG.registerElement('ticks', JXG.createTicks); 1512 JXG.registerElement('hash', JXG.createHatchmark); 1513 JXG.registerElement('hatch', JXG.createHatchmark); 1514 1515 return { 1516 Ticks: JXG.Ticks, 1517 createTicks: JXG.createTicks, 1518 createHashmark: JXG.createHatchmark, 1519 createHatchmark: JXG.createHatchmark 1520 }; 1521 }); 1522