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 // Now, ticksdelta is the distance between two minor ticks 663 this.ticksDelta = ticksDelta; 664 665 if (ticksDelta < Mat.eps) { 666 return; 667 } 668 669 // Position ticks from zero to the positive side while not reaching the upper boundary 670 tickPosition = 0; 671 if (!Type.evaluate(this.visProp.drawzero)) { 672 tickPosition = ticksDelta; 673 } 674 while (tickPosition <= bounds.upper + eps2) { 675 // Only draw ticks when we are within bounds, ignore case where tickPosition < lower < upper 676 if (tickPosition >= bounds.lower - eps2) { 677 this.processTickPosition(coordsZero, tickPosition, ticksDelta, deltas); 678 } 679 tickPosition += ticksDelta; 680 681 // Emergency out 682 if ((bounds.upper - tickPosition) > ticksDelta * 10000) { 683 break; 684 } 685 } 686 687 // Position ticks from zero (not inclusive) to the negative side while not reaching the lower boundary 688 tickPosition = -ticksDelta; 689 while (tickPosition >= bounds.lower - eps2) { 690 // Only draw ticks when we are within bounds, ignore case where lower < upper < tickPosition 691 if (tickPosition <= bounds.upper + eps2) { 692 this.processTickPosition(coordsZero, tickPosition, ticksDelta, deltas); 693 } 694 tickPosition -= ticksDelta; 695 696 // Emergency out 697 if ((tickPosition - bounds.lower) > ticksDelta * 10000) { 698 break; 699 } 700 } 701 }, 702 703 /** 704 * Auxiliary method used by {@link JXG.Ticks#generateEquidistantTicks} to adjust the 705 * distance between two ticks depending on {@link JXG.Ticks#minTicksDistance} value 706 * 707 * @param {Number} ticksDelta distance between two major ticks in user coordinates 708 * @param {JXG.Coords} coordsZero coordinates of the point considered zero 709 * @param {Object} deltas x and y distance in pixel between two user units 710 * @param {Object} bounds upper and lower bound of the tick positions in user units. 711 * @private 712 */ 713 adjustTickDistance: function (ticksDelta, coordsZero, deltas) { 714 var nx, ny, bounds, 715 distScr, 716 sgn = 1, 717 ev_minti = Type.evaluate(this.visProp.minorticks); 718 719 if (this.line.elementClass === Const.OBJECT_CLASS_CURVE) { 720 return ticksDelta; 721 } 722 bounds = this.getLowerAndUpperBounds(coordsZero, 'ticksdistance'); 723 nx = coordsZero.usrCoords[1] + deltas.x * ticksDelta; 724 ny = coordsZero.usrCoords[2] + deltas.y * ticksDelta; 725 distScr = coordsZero.distance(Const.COORDS_BY_SCREEN, new Coords(Const.COORDS_BY_USER, [nx, ny], this.board)); 726 727 if (ticksDelta === 0.0) { 728 return 0.0; 729 } 730 731 while (distScr / (ev_minti + 1) < this.minTicksDistance) { 732 if (sgn === 1) { 733 ticksDelta *= 2; 734 } else { 735 ticksDelta *= 5; 736 } 737 sgn *= -1; 738 739 nx = coordsZero.usrCoords[1] + deltas.x * ticksDelta; 740 ny = coordsZero.usrCoords[2] + deltas.y * ticksDelta; 741 distScr = coordsZero.distance(Const.COORDS_BY_SCREEN, new Coords(Const.COORDS_BY_USER, [nx, ny], this.board)); 742 } 743 return ticksDelta; 744 }, 745 746 /** 747 * Auxiliary method used by {@link JXG.Ticks#generateEquidistantTicks} to create a tick 748 * in the line at the given tickPosition. 749 * 750 * @param {JXG.Coords} coordsZero coordinates of the point considered zero 751 * @param {Number} tickPosition current tick position relative to zero 752 * @param {Number} ticksDelta distance between two major ticks in user coordinates 753 * @param {Object} deltas x and y distance between two major ticks 754 * @private 755 */ 756 processTickPosition: function (coordsZero, tickPosition, ticksDelta, deltas) { 757 var x, y, tickCoords, ti, 758 isLabelPosition, 759 ticksPerLabel = Type.evaluate(this.visProp.ticksperlabel), 760 labelVal = null; 761 762 // Calculates tick coordinates 763 if (this.line.elementClass === Const.OBJECT_CLASS_LINE) { 764 x = coordsZero.usrCoords[1] + tickPosition * deltas.x; 765 y = coordsZero.usrCoords[2] + tickPosition * deltas.y; 766 } else { 767 x = this.line.X(coordsZero + tickPosition); 768 y = this.line.Y(coordsZero + tickPosition); 769 } 770 tickCoords = new Coords(Const.COORDS_BY_USER, [x, y], this.board); 771 if (this.line.elementClass === Const.OBJECT_CLASS_CURVE) { 772 labelVal = coordsZero + tickPosition; 773 this.setTicksSizeVariables(labelVal); 774 775 } 776 777 // Test if tick is a major tick. 778 // This is the case if tickPosition/ticksDelta is 779 // a multiple of the number of minorticks+1 780 tickCoords.major = Math.round(tickPosition / ticksDelta) % (Type.evaluate(this.visProp.minorticks) + 1) === 0; 781 782 if (!ticksPerLabel) { 783 // In case of null, 0 or false, majorTicks are labelled 784 ticksPerLabel = Type.evaluate(this.visProp.minorticks) + 1; 785 } 786 isLabelPosition = Math.round(tickPosition / ticksDelta) % (ticksPerLabel) === 0; 787 788 // Compute the start position and the end position of a tick. 789 // If both positions are out of the canvas, ti is empty. 790 ti = this.createTickPath(tickCoords, tickCoords.major); 791 if (ti.length === 3) { 792 this.ticks.push(ti); 793 if (isLabelPosition && Type.evaluate(this.visProp.drawlabels)) { 794 // Create a label at this position 795 this.labelsData.push( 796 this.generateLabelData( 797 this.generateLabelText(tickCoords, coordsZero, labelVal), 798 tickCoords, 799 this.ticks.length 800 ) 801 ); 802 } else { 803 // minor ticks have no labels 804 this.labelsData.push(null); 805 } 806 } 807 }, 808 809 /** 810 * Creates ticks coordinates and labels based on {@link JXG.Ticks#fixedTicks} and {@link JXG.Ticks#labels}. 811 * 812 * @param {JXG.Coords} coordsZero Coordinates of the point considered zero 813 * @param {Object} bounds contains the lower and upper boundaries for ticks placement 814 * @private 815 */ 816 generateFixedTicks: function (coordsZero, bounds) { 817 var tickCoords, labelText, i, ti, 818 x, y, 819 eps2 = Mat.eps, fixedTick, 820 hasLabelOverrides = Type.isArray(this.visProp.labels), 821 deltas, 822 ev_dl = Type.evaluate(this.visProp.drawlabels); 823 824 // Calculate X and Y distance between two major points in the line 825 if (this.line.elementClass === Const.OBJECT_CLASS_LINE) { 826 deltas = this.getXandYdeltas(); 827 } 828 for (i = 0; i < this.fixedTicks.length; i++) { 829 if (this.line.elementClass === Const.OBJECT_CLASS_LINE) { 830 fixedTick = this.fixedTicks[i]; 831 x = coordsZero.usrCoords[1] + fixedTick * deltas.x; 832 y = coordsZero.usrCoords[2] + fixedTick * deltas.y; 833 } else { 834 fixedTick = coordsZero + this.fixedTicks[i]; 835 x = this.line.X(fixedTick); 836 y = this.line.Y(fixedTick); 837 } 838 tickCoords = new Coords(Const.COORDS_BY_USER, [x, y], this.board); 839 840 if (this.line.elementClass === Const.OBJECT_CLASS_CURVE) { 841 this.setTicksSizeVariables(fixedTick); 842 } 843 844 // Compute the start position and the end position of a tick. 845 // If tick is out of the canvas, ti is empty. 846 ti = this.createTickPath(tickCoords, true); 847 if (ti.length === 3 && fixedTick >= bounds.lower - eps2 && 848 fixedTick <= bounds.upper + eps2) { 849 this.ticks.push(ti); 850 851 if (ev_dl && 852 (hasLabelOverrides || Type.exists(this.visProp.labels[i]))) { 853 labelText = hasLabelOverrides ? 854 Type.evaluate(this.visProp.labels[i]) : fixedTick; 855 this.labelsData.push( 856 this.generateLabelData( 857 this.generateLabelText(tickCoords, coordsZero, labelText), 858 tickCoords, 859 i 860 ) 861 ); 862 } else { 863 this.labelsData.push(null); 864 } 865 } 866 } 867 }, 868 869 /** 870 * Calculates the x and y distance in pixel between two units in user space. 871 * 872 * @returns {Object} 873 * @private 874 */ 875 getXandYdeltas: function () { 876 var 877 // Auxiliary points to store the start and end of the line according to its direction 878 point1UsrCoords, point2UsrCoords, 879 distP1P2 = this.line.point1.Dist(this.line.point2); 880 881 if (this.line.type === Const.OBJECT_TYPE_AXIS) { 882 // When line is an Axis, direction depends on Board Coordinates system 883 884 // assume line.point1 and line.point2 are in correct order 885 point1UsrCoords = this.line.point1.coords.usrCoords; 886 point2UsrCoords = this.line.point2.coords.usrCoords; 887 888 // Check if direction is incorrect, then swap 889 if (point1UsrCoords[1] > point2UsrCoords[1] || 890 (Math.abs(point1UsrCoords[1] - point2UsrCoords[1]) < Mat.eps && 891 point1UsrCoords[2] > point2UsrCoords[2])) { 892 point1UsrCoords = this.line.point2.coords.usrCoords; 893 point2UsrCoords = this.line.point1.coords.usrCoords; 894 } 895 } else /* if (this.line.elementClass === Const.OBJECT_CLASS_LINE)*/ { 896 // line direction is always from P1 to P2 for non Axis types 897 point1UsrCoords = this.line.point1.coords.usrCoords; 898 point2UsrCoords = this.line.point2.coords.usrCoords; 899 } 900 return { 901 x: (point2UsrCoords[1] - point1UsrCoords[1]) / distP1P2, 902 y: (point2UsrCoords[2] - point1UsrCoords[2]) / distP1P2 903 }; 904 }, 905 906 /** 907 * Check if (parts of) the tick is inside the canvas. The tick intersects the boundary 908 * at two positions: [x[0], y[0]] and [x[1], y[1]] in screen coordinates. 909 * @param {Array} x Array of length two 910 * @param {Array} y Array of length two 911 * @return {Boolean} true if parts of the tick are inside of the canvas or on the boundary. 912 */ 913 _isInsideCanvas: function(x, y, m) { 914 var cw = this.board.canvasWidth, 915 ch = this.board.canvasHeight; 916 917 if (m === undefined) { 918 m = 0; 919 } 920 return (x[0] >= m && x[0] <= cw - m && y[0] >= m && y[0] <= ch - m) || 921 (x[1] >= m && x[1] <= cw - m && y[1] >= m && y[1] <= ch - m); 922 }, 923 924 /** 925 * @param {JXG.Coords} coords Coordinates of the tick on the line. 926 * @param {Boolean} major True if tick is major tick. 927 * @returns {Array} Array of length 3 containing path coordinates in screen coordinates 928 * of the tick (arrays of length 2). 3rd entry is true if major tick otherwise false. 929 * If the tick is outside of the canvas, the return array is empty. 930 * @private 931 */ 932 createTickPath: function (coords, major) { 933 var c, lineStdForm, intersection, 934 dxs, dys, dxr, dyr, alpha, 935 style, 936 x = [-2000000, -2000000], 937 y = [-2000000, -2000000], 938 i, r, r_max, bb, full, delta; 939 940 c = coords.scrCoords; 941 if (major) { 942 dxs = this.dxMaj; 943 dys = this.dyMaj; 944 style = this.majStyle; 945 } else { 946 dxs = this.dxMin; 947 dys = this.dyMin; 948 style = this.minStyle; 949 } 950 lineStdForm = [-dys * c[1] - dxs * c[2], dys, dxs]; 951 952 // For all ticks regardless if of finite or infinite 953 // tick length the intersection with the canvas border is 954 // computed. 955 if (major && Type.evaluate(this.visProp.type) === 'polar') { 956 // polar style 957 bb = this.board.getBoundingBox(); 958 full = 2.0 * Math.PI; 959 delta = full / 180; 960 //ratio = this.board.unitY / this.board.X; 961 962 // usrCoords: Test if 'circle' is inside of the canvas 963 c = coords.usrCoords; 964 r = Math.sqrt(c[1] * c[1] + c[2] * c[2]); 965 r_max = Math.max(Math.sqrt(bb[0] * bb[0] + bb[1] * bb[1]), 966 Math.sqrt(bb[2] * bb[2] + bb[3] * bb[3])); 967 968 if (r < r_max) { 969 // Now, switch to screen coords 970 x = []; 971 y = []; 972 for (i = 0; i <= full; i += delta) { 973 x.push(this.board.origin.scrCoords[1] + r * Math.cos(i) * this.board.unitX); 974 y.push(this.board.origin.scrCoords[2] + r * Math.sin(i) * this.board.unitY); 975 } 976 return [x, y, major]; 977 } 978 979 } else { 980 // line style 981 if (style === 'infinite') { 982 intersection = Geometry.meetLineBoard(lineStdForm, this.board); 983 x[0] = intersection[0].scrCoords[1]; 984 x[1] = intersection[1].scrCoords[1]; 985 y[0] = intersection[0].scrCoords[2]; 986 y[1] = intersection[1].scrCoords[2]; 987 } else { 988 if (Type.evaluate(this.visProp.face) === '>') { 989 alpha = Math.PI/4; 990 } else if (Type.evaluate(this.visProp.face) === '<') { 991 alpha = -Math.PI/4; 992 } else { 993 alpha = 0; 994 } 995 dxr = Math.cos(alpha) * dxs - Math.sin(alpha) * dys; 996 dyr = Math.sin(alpha) * dxs + Math.cos(alpha) * dys; 997 998 x[0] = c[1] + dxr * Type.evaluate(this.visProp.tickendings[0]); 999 y[0] = c[2] - dyr * Type.evaluate(this.visProp.tickendings[0]); 1000 x[1] = c[1]; 1001 y[1] = c[2]; 1002 1003 alpha = -alpha; 1004 dxr = Math.cos(alpha) * dxs - Math.sin(alpha) * dys; 1005 dyr = Math.sin(alpha) * dxs + Math.cos(alpha) * dys; 1006 1007 x[2] = c[1] - dxr * Type.evaluate(this.visProp.tickendings[1]); 1008 y[2] = c[2] + dyr * Type.evaluate(this.visProp.tickendings[1]); 1009 } 1010 1011 // Check if (parts of) the tick is inside the canvas. 1012 if (this._isInsideCanvas(x, y)) { 1013 return [x, y, major]; 1014 } 1015 } 1016 1017 return []; 1018 }, 1019 1020 /** 1021 * Format label texts. Show the desired number of digits 1022 * and use utf-8 minus sign. 1023 * @param {Number} value Number to be displayed 1024 * @return {String} The value converted into a string. 1025 * @private 1026 */ 1027 formatLabelText: function(value) { 1028 var labelText, 1029 digits, 1030 ev_s = Type.evaluate(this.visProp.scalesymbol); 1031 1032 // if value is Number 1033 if (Type.isNumber(value)) { 1034 labelText = (Math.round(value * 1.e11) / 1.e11).toString(); 1035 if (labelText.length > Type.evaluate(this.visProp.maxlabellength) || 1036 labelText.indexOf('e') !== -1) { 1037 1038 digits = Type.evaluate(this.visProp.digits); 1039 if (Type.evaluate(this.visProp.precision) !== 3 && digits === 3) { 1040 // Use the deprecated attribute "precision" 1041 digits = Type.evaluate(this.visProp.precision); 1042 } 1043 1044 //labelText = value.toPrecision(digits).toString(); 1045 labelText = value.toExponential(digits).toString(); 1046 } 1047 1048 if (Type.evaluate(this.visProp.beautifulscientificticklabels)) { 1049 labelText = this.beautifyScientificNotationLabel(labelText); 1050 } 1051 1052 if (labelText.indexOf('.') > -1 && labelText.indexOf('e') === -1) { 1053 // trim trailing zeros 1054 labelText = labelText.replace(/0+$/, ''); 1055 // trim trailing . 1056 labelText = labelText.replace(/\.$/, ''); 1057 } 1058 } else { 1059 labelText = value.toString(); 1060 } 1061 1062 if (ev_s.length > 0) { 1063 if (labelText === '1') { 1064 labelText = ev_s; 1065 } else if (labelText === '-1') { 1066 labelText = '-' + ev_s; 1067 } else if (labelText !== '0') { 1068 labelText = labelText + ev_s; 1069 } 1070 } 1071 1072 if (Type.evaluate(this.visProp.useunicodeminus)) { 1073 labelText = labelText.replace(/-/g, '\u2212'); 1074 } 1075 return labelText; 1076 }, 1077 1078 /** 1079 * Formats label texts to make labels displayed in scientific notation look beautiful. 1080 * For example, label 5.00e+6 will become 5•10⁶, label -1.00e-7 will become into -1•10⁻⁷ 1081 * @param {String} labelText - The label that we want to convert 1082 * @returns {String} If labelText was not in scientific notation, return labelText without modifications. 1083 * Otherwise returns beautified labelText with proper superscript notation. 1084 */ 1085 beautifyScientificNotationLabel: function(labelText) { 1086 var returnString; 1087 1088 if (labelText.indexOf('e') === -1) { 1089 return labelText; 1090 } 1091 1092 // Clean up trailing 0's, so numbers like 5.00e+6.0 for example become into 5e+6 1093 returnString = parseFloat(labelText.substring(0, labelText.indexOf('e'))) + 1094 labelText.substring(labelText.indexOf('e')); 1095 1096 // Replace symbols like -,0,1,2,3,4,5,6,7,8,9 with their superscript version. 1097 // Gets rid of + symbol since there is no need for it anymore. 1098 returnString = returnString.replace(/e(.*)$/g, function(match,$1){ 1099 var temp = '\u2022' + '10'; 1100 // Note: Since board ticks do not support HTTP elements like <sub>, we need to replace 1101 // all the numbers with superscript Unicode characters. 1102 temp += $1 1103 .replace(/-/g, "\u207B") 1104 .replace(/\+/g, '') 1105 .replace(/0/g,'\u2070') 1106 .replace(/1/g,'\u00B9') 1107 .replace(/2/g,'\u00B2') 1108 .replace(/3/g,'\u00B3') 1109 .replace(/4/g,'\u2074') 1110 .replace(/5/g,'\u2075') 1111 .replace(/6/g,'\u2076') 1112 .replace(/7/g,'\u2077') 1113 .replace(/8/g,'\u2078') 1114 .replace(/9/g,'\u2079'); 1115 1116 return temp; 1117 }); 1118 1119 return returnString; 1120 }, 1121 1122 /** 1123 * Creates the label text for a given tick. A value for the text can be provided as a number or string 1124 * 1125 * @param {JXG.Coords} tick The Coords-object of the tick to create a label for 1126 * @param {JXG.Coords} zero The Coords-object of line's zero 1127 * @param {Number|String} value A predefined value for this tick 1128 * @returns {String} 1129 * @private 1130 */ 1131 generateLabelText: function (tick, zero, value) { 1132 var labelText, distance; 1133 1134 // No value provided, equidistant, so assign distance as value 1135 if (!Type.exists(value)) { // could be null or undefined 1136 distance = this.getDistanceFromZero(zero, tick); 1137 if (Math.abs(distance) < Mat.eps) { // Point is zero 1138 return '0'; 1139 } 1140 value = distance / Type.evaluate(this.visProp.scale); 1141 } 1142 labelText = this.formatLabelText(value); 1143 1144 return labelText; 1145 }, 1146 1147 /** 1148 * Create a tick label data, i.e. text and coordinates 1149 * @param {String} labelText 1150 * @param {JXG.Coords} tick 1151 * @param {Number} tickNumber 1152 * @returns {Object} with properties 'x', 'y', 't' (text), 'i' (tick number) or null in case of o label 1153 * @private 1154 */ 1155 generateLabelData: function (labelText, tick, tickNumber) { 1156 var xa, ya, m, fs; 1157 1158 // Test if large portions of the label are inside of the canvas 1159 // This is the last chance to abandon the creation of the label if it is mostly 1160 // outside of the canvas. 1161 fs = Type.evaluate(this.visProp.label.fontsize); 1162 xa = [tick.scrCoords[1], tick.scrCoords[1]]; 1163 ya = [tick.scrCoords[2], tick.scrCoords[2]]; 1164 m = (fs === undefined) ? 12 : fs; 1165 m *= 0.5; 1166 if (!this._isInsideCanvas(xa, ya, m)) { 1167 return null; 1168 } 1169 1170 xa = Type.evaluate(this.visProp.label.offset[0]); 1171 ya = Type.evaluate(this.visProp.label.offset[1]); 1172 1173 return { 1174 x: tick.usrCoords[1] + xa / (this.board.unitX), 1175 y: tick.usrCoords[2] + ya / (this.board.unitY), 1176 t: labelText, 1177 i: tickNumber 1178 }; 1179 }, 1180 1181 /** 1182 * Recalculate the tick positions and the labels. 1183 * @returns {JXG.Ticks} 1184 */ 1185 update: function () { 1186 if (this.needsUpdate) { 1187 //this.visPropCalc.visible = Type.evaluate(this.visProp.visible); 1188 // A canvas with no width or height will create an endless loop, so ignore it 1189 if (this.board.canvasWidth !== 0 && this.board.canvasHeight !== 0) { 1190 this.calculateTicksCoordinates(); 1191 } 1192 // this.updateVisibility(this.line.visPropCalc.visible); 1193 // 1194 // for (var i = 0; i < this.labels.length; i++) { 1195 // if (this.labels[i] !== null) { 1196 // this.labels[i].prepareUpdate() 1197 // .updateVisibility(this.line.visPropCalc.visible) 1198 // .updateRenderer(); 1199 // } 1200 // } 1201 } 1202 1203 return this; 1204 }, 1205 1206 /** 1207 * Uses the boards renderer to update the arc. 1208 * @returns {JXG.Ticks} Reference to the object. 1209 */ 1210 updateRenderer: function () { 1211 if (!this.needsUpdate) { 1212 return this; 1213 } 1214 1215 if (this.visPropCalc.visible) { 1216 this.board.renderer.updateTicks(this); 1217 } 1218 this.updateRendererLabels(); 1219 1220 this.setDisplayRendNode(); 1221 // if (this.visPropCalc.visible != this.visPropOld.visible) { 1222 // this.board.renderer.display(this, this.visPropCalc.visible); 1223 // this.visPropOld.visible = this.visPropCalc.visible; 1224 // } 1225 1226 this.needsUpdate = false; 1227 return this; 1228 }, 1229 1230 /** 1231 * Updates the label elements of the major ticks. 1232 * 1233 * @private 1234 * @returns {JXG.Ticks} Reference to the object. 1235 */ 1236 updateRendererLabels: function() { 1237 var i, j, 1238 lenData, lenLabels, 1239 attr, 1240 label, ld, 1241 visible; 1242 1243 // The number of labels needed 1244 lenData = this.labelsData.length; 1245 // The number of labels which already exist 1246 // The existing labels are stored in this.labels[] 1247 // The new label positions and label values are stored in this.labelsData[] 1248 lenLabels = this.labels.length; 1249 1250 for (i = 0, j = 0; i < lenData; i++) { 1251 if (this.labelsData[i] === null) { 1252 // This is a tick without label 1253 continue; 1254 } 1255 1256 ld = this.labelsData[i]; 1257 if (j < lenLabels) { 1258 // Take an already existing text element 1259 label = this.labels[j]; 1260 label.setText(ld.t); 1261 label.setCoords(ld.x, ld.y); 1262 j++; 1263 } else { 1264 // A new text element is needed 1265 this.labelCounter += 1; 1266 1267 attr = { 1268 isLabel: true, 1269 layer: this.board.options.layer.line, 1270 highlightStrokeColor: this.board.options.text.strokeColor, 1271 highlightStrokeWidth: this.board.options.text.strokeWidth, 1272 highlightStrokeOpacity: this.board.options.text.strokeOpacity, 1273 priv: this.visProp.priv 1274 }; 1275 attr = Type.deepCopy(attr, this.visProp.label); 1276 attr.id = this.id + ld.i + 'Label' + this.labelCounter; 1277 1278 label = Text.createText(this.board, [ld.x, ld.y, ld.t], attr); 1279 this.addChild(label); 1280 label.setParents(this); 1281 label.isDraggable = false; 1282 label.dump = false; 1283 this.labels.push(label); 1284 } 1285 1286 // Look-ahead if the label inherits visiblity. 1287 // If yes, update label. 1288 visible = Type.evaluate(this.visProp.label.visible); 1289 if (visible === 'inherit') { 1290 visible = this.visPropCalc.visible; 1291 } 1292 1293 label.prepareUpdate() 1294 .updateVisibility(visible) 1295 .updateRenderer(); 1296 1297 label.distanceX = Type.evaluate(this.visProp.label.offset[0]); 1298 label.distanceY = Type.evaluate(this.visProp.label.offset[1]); 1299 } 1300 1301 // Hide unused labels 1302 lenData = j; 1303 for (j = lenData; j < lenLabels; j++) { 1304 this.board.renderer.display(this.labels[j], false); 1305 // Tick labels have the attribute "visible: 'inherit'" 1306 // This must explicitely set to false, otherwise 1307 // this labels would be set to visible in the upcoming 1308 // update of the labels. 1309 this.labels[j].visProp.visible = this.labels[j].visPropCalc.visible = false; 1310 } 1311 1312 return this; 1313 }, 1314 1315 hideElement: function () { 1316 var i; 1317 1318 JXG.deprecated('Element.hideElement()', 'Element.setDisplayRendNode()'); 1319 1320 this.visPropCalc.visible = false; 1321 this.board.renderer.display(this, false); 1322 for (i = 0; i < this.labels.length; i++) { 1323 if (Type.exists(this.labels[i])) { 1324 this.labels[i].hideElement(); 1325 } 1326 } 1327 1328 return this; 1329 }, 1330 1331 showElement: function () { 1332 var i; 1333 1334 JXG.deprecated('Element.showElement()', 'Element.setDisplayRendNode()'); 1335 1336 this.visPropCalc.visible = true; 1337 this.board.renderer.display(this, false); 1338 1339 for (i = 0; i < this.labels.length; i++) { 1340 if (Type.exists(this.labels[i])) { 1341 this.labels[i].showElement(); 1342 } 1343 } 1344 1345 return this; 1346 } 1347 }); 1348 1349 /** 1350 * @class Ticks are used as distance markers on a line or curve. 1351 * They are 1352 * mainly used for axis elements and slider elements. Ticks may stretch infinitely 1353 * or finitely, which can be set with {@link Ticks#majorHeight} and {@link Ticks#minorHeight}. 1354 * 1355 * @pseudo 1356 * @description Ticks are markers on straight line elements or curves. 1357 * @name Ticks 1358 * @augments JXG.Ticks 1359 * @constructor 1360 * @type JXG.Ticks 1361 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1362 * @param {JXG.Line|JXG.Curve} line The parents consist of the line or curve the ticks are going to be attached to. 1363 * @param {Number|Array} distance Number defining the distance between two major ticks or an 1364 * array defining static ticks. In case a number is specified, the ticks are <i>equidistant</i>, 1365 * in case of an array, a fixed number of static ticks is created at user-supplied positions. 1366 * Alternatively, the distance can be specified with the attribute 1367 * "ticksDistance". For arbitrary lines (and not axes) a "zero coordinate" is determined 1368 * which defines where the first tick is positioned. This zero coordinate 1369 * can be altered with the attribute "anchor". Possible values are "left", "middle", "right" or a number. 1370 * The default value is "left". 1371 * 1372 * @example 1373 * // Create an axis providing two coordinate pairs. 1374 * var p1 = board.create('point', [0, 3]); 1375 * var p2 = board.create('point', [1, 3]); 1376 * var l1 = board.create('line', [p1, p2]); 1377 * var t = board.create('ticks', [l1], {ticksDistance: 2}); 1378 * </pre><div class="jxgbox" id="JXGee7f2d68-75fc-4ec0-9931-c76918427e63" style="width: 300px; height: 300px;"></div> 1379 * <script type="text/javascript"> 1380 * (function () { 1381 * var board = JXG.JSXGraph.initBoard('JXGee7f2d68-75fc-4ec0-9931-c76918427e63', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false}); 1382 * var p1 = board.create('point', [0, 3]); 1383 * var p2 = board.create('point', [1, 3]); 1384 * var l1 = board.create('line', [p1, p2]); 1385 * var t = board.create('ticks', [l1, 2], {ticksDistance: 2}); 1386 * })(); 1387 * </script><pre> 1388 */ 1389 JXG.createTicks = function (board, parents, attributes) { 1390 var el, dist, 1391 attr = Type.copyAttributes(attributes, board.options, 'ticks'); 1392 1393 if (parents.length < 2) { 1394 dist = attr.ticksdistance; 1395 } else { 1396 dist = parents[1]; 1397 } 1398 1399 if (parents[0].elementClass === Const.OBJECT_CLASS_LINE || 1400 parents[0].elementClass === Const.OBJECT_CLASS_CURVE) { 1401 el = new JXG.Ticks(parents[0], dist, attr); 1402 } else { 1403 throw new Error("JSXGraph: Can't create Ticks with parent types '" + (typeof parents[0]) + "'."); 1404 } 1405 1406 // deprecated 1407 if (Type.isFunction(attr.generatelabelvalue)) { 1408 el.generateLabelText = attr.generatelabelvalue; 1409 } 1410 if (Type.isFunction(attr.generatelabeltext)) { 1411 el.generateLabelText = attr.generatelabeltext; 1412 } 1413 1414 el.setParents(parents[0]); 1415 el.isDraggable = true; 1416 el.fullUpdate(parents[0].visPropCalc.visible); 1417 1418 return el; 1419 }; 1420 1421 /** 1422 * @class Hatches can be used to mark congruent lines or curves. 1423 * @pseudo 1424 * @description 1425 * @name Hatch 1426 * @augments JXG.Ticks 1427 * @constructor 1428 * @type JXG.Ticks 1429 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1430 * @param {JXG.Line|JXG.curve} line The line or curve the hatch marks are going to be attached to. 1431 * @param {Number} numberofhashes Number of dashes. 1432 * @example 1433 * // Create an axis providing two coords pairs. 1434 * var p1 = board.create('point', [0, 3]); 1435 * var p2 = board.create('point', [1, 3]); 1436 * var l1 = board.create('line', [p1, p2]); 1437 * var t = board.create('hatch', [l1, 3]); 1438 * </pre><div class="jxgbox" id="JXG4a20af06-4395-451c-b7d1-002757cf01be" style="width: 300px; height: 300px;"></div> 1439 * <script type="text/javascript"> 1440 * (function () { 1441 * var board = JXG.JSXGraph.initBoard('JXG4a20af06-4395-451c-b7d1-002757cf01be', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false}); 1442 * var p1 = board.create('point', [0, 3]); 1443 * var p2 = board.create('point', [1, 3]); 1444 * var l1 = board.create('line', [p1, p2]); 1445 * var t = board.create('hatch', [l1, 3]); 1446 * })(); 1447 * </script><pre> 1448 * 1449 * @example 1450 * // Alter the position of the hatch 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 * </pre><div id="JXG05d720ee-99c9-11e6-a9c7-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 1458 * <script type="text/javascript"> 1459 * (function() { 1460 * var board = JXG.JSXGraph.initBoard('JXG05d720ee-99c9-11e6-a9c7-901b0e1b8723', 1461 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1462 * 1463 * var p = board.create('point', [-5, 0]); 1464 * var q = board.create('point', [5, 0]); 1465 * var li = board.create('line', [p, q]); 1466 * var h = board.create('hatch', [li, 2], {anchor: 0.2}); 1467 * 1468 * })(); 1469 * 1470 * </script><pre> 1471 * 1472 * @example 1473 * // Alternative hatch faces 1474 * 1475 * var li = board.create('line', [[-6,0], [6,3]]); 1476 * var h1 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'|'}); 1477 * var h2 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'>', anchor: 0.3}); 1478 * var h3 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'<', anchor: 0.7}); 1479 * 1480 * </pre><div id="JXG974f7e89-eac8-4187-9aa3-fb8068e8384b" class="jxgbox" style="width: 300px; height: 300px;"></div> 1481 * <script type="text/javascript"> 1482 * (function() { 1483 * var board = JXG.JSXGraph.initBoard('JXG974f7e89-eac8-4187-9aa3-fb8068e8384b', 1484 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1485 * // Alternative hatch faces 1486 * 1487 * var li = board.create('line', [[-6,0], [6,3]]); 1488 * var h1 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'|'}); 1489 * var h2 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'>', anchor: 0.3}); 1490 * var h3 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'<', anchor: 0.7}); 1491 * 1492 * })(); 1493 * 1494 * </script><pre> 1495 * 1496 */ 1497 JXG.createHatchmark = function (board, parents, attributes) { 1498 var num, i, base, width, totalwidth, el, 1499 pos = [], 1500 attr = Type.copyAttributes(attributes, board.options, 'hatch'); 1501 1502 if ((parents[0].elementClass !== Const.OBJECT_CLASS_LINE && 1503 parents[0].elementClass !== Const.OBJECT_CLASS_CURVE) || typeof parents[1] !== 'number') { 1504 throw new Error("JSXGraph: Can't create Hatch mark with parent types '" + (typeof parents[0]) + "' and '" + (typeof parents[1]) + " and ''" + (typeof parents[2]) + "'."); 1505 } 1506 1507 num = parents[1]; 1508 width = attr.ticksdistance; 1509 totalwidth = (num - 1) * width; 1510 base = -totalwidth * 0.5; 1511 1512 for (i = 0; i < num; i++) { 1513 pos[i] = base + i * width; 1514 } 1515 1516 el = board.create('ticks', [parents[0], pos], attr); 1517 el.elType = 'hatch'; 1518 1519 return el; 1520 }; 1521 1522 JXG.registerElement('ticks', JXG.createTicks); 1523 JXG.registerElement('hash', JXG.createHatchmark); 1524 JXG.registerElement('hatch', JXG.createHatchmark); 1525 1526 return { 1527 Ticks: JXG.Ticks, 1528 createTicks: JXG.createTicks, 1529 createHashmark: JXG.createHatchmark, 1530 createHatchmark: JXG.createHatchmark 1531 }; 1532 }); 1533