      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