      1 /*
      2     Copyright 2008-2024
      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 <https://www.gnu.org/licenses/>
     29     and <https://opensource.org/licenses/MIT/>.
     30  */
     31
     32 /*global JXG: true, define: true*/
     33 /*jslint nomen: true, plusplus: true*/
     34
     35 /**
     36  * @fileoverview In this file the geometry object Ticks is defined. Ticks provides
     37  * methods for creation and management of ticks on an axis.
     38  * @author graphjs
     39  * @version 0.1
     40  */
     41
     42 import JXG from "../jxg.js";
     43 import Mat from "../math/math.js";
     44 import Geometry from "../math/geometry.js";
     45 import Numerics from "../math/numerics.js";
     46 import Const from "./constants.js";
     47 import GeometryElement from "./element.js";
     48 import Coords from "./coords.js";
     49 import Type from "../utils/type.js";
     50
     51 /**
     52  * Creates ticks for an axis.
     53  * @class Ticks provides methods for creation and management
     54  * of ticks on an axis.
     55  * @param {JXG.Line} line Reference to the axis the ticks are drawn on.
     56  * @param {Number|Array} ticks Number defining the distance between two major ticks or an array defining static ticks.
     57  * @param {Object} attributes Properties
     58  * @see JXG.Line#addTicks
     59  * @constructor
     60  * @augments JXG.GeometryElement
     61  */
     62 JXG.Ticks = function (line, ticks, attributes) {
     63     this.constructor(line.board, attributes, Const.OBJECT_TYPE_TICKS, Const.OBJECT_CLASS_OTHER);
     64
     65     /**
     66      * The line the ticks belong to.
     67      * @type JXG.Line
     68      * @private
     69      */
     70     this.line = line;
     71
     72     /**
     73      * The board the ticks line is drawn on.
     74      * @type JXG.Board
     75      * @private
     76      */
     77     this.board = this.line.board;
     78
     79     // /**
     80     //  * A function calculating ticks delta depending on the ticks number.
     81     //  * @type Function
     82     //  */
     83     // // this.ticksFunction = null;
     84
     85     /**
     86      * Array of fixed ticks.
     87      * @type Array
     88      * @private
     89      */
     90     this.fixedTicks = null;
     91
     92     /**
     93      * Flag if the ticks are equidistant. If true, their distance is defined by ticksFunction.
     94      * @type Boolean
     95      * @private
     96      */
     97     this.equidistant = false;
     98
     99     /**
    100      * A list of labels which have to be displayed in updateRenderer.
    101      * @type Array
    102      * @private
    103      */
    104     this.labelsData = [];
    105
    106     if (Type.isFunction(ticks)) {
    107         this.ticksFunction = ticks;
    108         throw new Error("Function arguments are no longer supported.");
    109     }
    110
    111     if (Type.isArray(ticks)) {
    112         this.fixedTicks = ticks;
    113     } else {
    114         // Obsolete:
    115         // if (Math.abs(ticks) < Mat.eps || ticks < 0) {
    116         //     ticks = attributes.defaultdistance;
    117         // }
    118         this.equidistant = true;
    119     }
    120
    121     // /**
    122     //  * Least distance between two ticks, measured in pixels.
    123     //  * @type int
    124     //  */
    125     // // this.minTicksDistance = attributes.minticksdistance;
    126
    127     /**
    128      * Stores the ticks coordinates
    129      * @type Array
    130      * @private
    131      */
    132     this.ticks = [];
    133
    134     // /**
    135     //  * Distance between two major ticks in user coordinates
    136     //  * @type Number
    137     //  */
    138     // this.ticksDelta = 1;
    139
    140     /**
    141      * Array where the labels are saved. There is an array element for every tick,
    142      * even for minor ticks which don't have labels. In this case the array element
    143      * contains just <tt>null</tt>.
    144      * @type Array
    145      * @private
    146      */
    147     this.labels = [];
    148
    149     /**
    150      * Used to ensure the uniqueness of label ids this counter is used.
    151      * @type number
    152      * @private
    153      */
    154     this.labelCounter = 0;
    155
    156     this.id = this.line.addTicks(this);
    157     this.elType = "ticks";
    158     this.inherits.push(this.labels);
    159     this.board.setId(this, "Ti");
    160 };
    161
    162 JXG.Ticks.prototype = new GeometryElement();
    163
    164 JXG.extend(
    165     JXG.Ticks.prototype,
    166     /** @lends JXG.Ticks.prototype */ {
    167         // /**
    168         //  * Ticks function:
    169         //  * determines the distance (in user units) of two major ticks.
    170         //  * See above in constructor and in @see JXG.GeometryElement#setAttribute
    171         //  *
    172         //  * @private
    173         //  * @param {Number} ticks Distance between two major ticks
    174         //  * @returns {Function} returns method ticksFunction
    175         //  */
    176         // // makeTicksFunction: function (ticks) {
    177         //     // return function () {
    178         //         ticksFunction: function () {
    179         //                     var delta, b, dist,
    180         //                     number_major_tick_intervals = 5;
    181
    182         //                 if (Type.evaluate(this.visProp.insertticks)) {
    183         //                     b = this.getLowerAndUpperBounds(this.getZeroCoordinates(), 'ticksdistance');
    184         //                     dist = b.upper - b.lower;
    185
    186         //                     // delta: Proposed distance in user units between two major ticks
    187         //                     delta = Math.pow(10, Math.floor(Math.log(dist / number_major_tick_intervals) / Math.LN10));
    188         // console.log("delta", delta,  b.upper, b.lower, dist, dist / number_major_tick_intervals * 1.1)
    189         //                     if (5 * delta < dist / number_major_tick_intervals * 1.1) {
    190         //                         return 5 * delta;
    191         //                     }
    192         //                     if (2 * delta < dist / number_major_tick_intervals * 1.1) {
    193         //                         return 2 * delta;
    194         //                     }
    195
    196         //                     // < v1.6.0:
    197         //                     // delta = Math.pow(10, Math.floor(Math.log(0.6 * dist) / Math.LN10));
    198         //                     if (false && dist <= 6 * delta) {
    199         //                         delta *= 0.5;
    200         //                     }
    201         //                     return delta;
    202         //                 }
    203
    204         //                 // In case of insertTicks==false
    205         //                 return Type.evaluate(this.visProp.ticksdistance);
    206         //                 // return ticks;
    207         //             // };
    208         //         },
    209
    210         /**
    211          * Checks whether (x,y) is near the line.
    212          * Only available for line elements,  not for ticks on curves.
    213          * @param {Number} x Coordinate in x direction, screen coordinates.
    214          * @param {Number} y Coordinate in y direction, screen coordinates.
    215          * @returns {Boolean} True if (x,y) is near the line, False otherwise.
    216          */
    217         hasPoint: function (x, y) {
    218             var i, t, r, type,
    219                 len = (this.ticks && this.ticks.length) || 0;
    220
    221             if (Type.isObject(Type.evaluate(this.visProp.precision))) {
    222                 type = this.board._inputDevice;
    223                 r = Type.evaluate(this.visProp.precision[type]);
    224             } else {
    225                 // 'inherit'
    226                 r = this.board.options.precision.hasPoint;
    227             }
    228             r += Type.evaluate(this.visProp.strokewidth) * 0.5;
    229             if (
    230                 !Type.evaluate(this.line.visProp.scalable) ||
    231                 this.line.elementClass === Const.OBJECT_CLASS_CURVE
    232             ) {
    233                 return false;
    234             }
    235
    236             // Ignore non-axes and axes that are not horizontal or vertical
    237             if (
    238                 this.line.stdform[1] !== 0 &&
    239                 this.line.stdform[2] !== 0 &&
    240                 this.line.type !== Const.OBJECT_TYPE_AXIS
    241             ) {
    242                 return false;
    243             }
    244
    245             for (i = 0; i < len; i++) {
    246                 t = this.ticks[i];
    247
    248                 // Skip minor ticks
    249                 if (t[2]) {
    250                     // Ignore ticks at zero
    251                     if (
    252                         !(
    253                             (this.line.stdform[1] === 0 &&
    254                                 Math.abs(t[0][0] - this.line.point1.coords.scrCoords[1]) <
    255                                 Mat.eps) ||
    256                             (this.line.stdform[2] === 0 &&
    257                                 Math.abs(t[1][0] - this.line.point1.coords.scrCoords[2]) <
    258                                 Mat.eps)
    259                         )
    260                     ) {
    261                         // tick length is not zero, ie. at least one pixel
    262                         if (
    263                             Math.abs(t[0][0] - t[0][1]) >= 1 ||
    264                             Math.abs(t[1][0] - t[1][1]) >= 1
    265                         ) {
    266                             if (this.line.stdform[1] === 0) {
    267                                 // Allow dragging near axes only.
    268                                 if (
    269                                     Math.abs(y - this.line.point1.coords.scrCoords[2]) < 2 * r &&
    270                                     t[0][0] - r < x && x < t[0][1] + r
    271                                 ) {
    272                                     return true;
    273                                 }
    274                             } else if (this.line.stdform[2] === 0) {
    275                                 if (
    276                                     Math.abs(x - this.line.point1.coords.scrCoords[1]) < 2 * r &&
    277                                     t[1][0] - r < y && y < t[1][1] + r
    278                                 ) {
    279                                     return true;
    280                                 }
    281                             }
    282                         }
    283                     }
    284                 }
    285             }
    286
    287             return false;
    288         },
    289
    290         /**
    291          * Sets x and y coordinate of the tick.
    292          * @param {number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
    293          * @param {Array} coords coordinates in screen/user units
    294          * @param {Array} oldcoords previous coordinates in screen/user units
    295          * @returns {JXG.Ticks} this element
    296          */
    297         setPositionDirectly: function (method, coords, oldcoords) {
    298             var dx, dy,
    299                 c = new Coords(method, coords, this.board),
    300                 oldc = new Coords(method, oldcoords, this.board),
    301                 bb = this.board.getBoundingBox();
    302
    303             if (
    304                 this.line.type !== Const.OBJECT_TYPE_AXIS ||
    305                 !Type.evaluate(this.line.visProp.scalable)
    306             ) {
    307                 return this;
    308             }
    309
    310             if (
    311                 Math.abs(this.line.stdform[1]) < Mat.eps &&
    312                 Math.abs(c.usrCoords[1] * oldc.usrCoords[1]) > Mat.eps
    313             ) {
    314                 // Horizontal line
    315                 dx = oldc.usrCoords[1] / c.usrCoords[1];
    316                 bb[0] *= dx;
    317                 bb[2] *= dx;
    318                 this.board.setBoundingBox(bb, this.board.keepaspectratio, "update");
    319             } else if (
    320                 Math.abs(this.line.stdform[2]) < Mat.eps &&
    321                 Math.abs(c.usrCoords[2] * oldc.usrCoords[2]) > Mat.eps
    322             ) {
    323                 // Vertical line
    324                 dy = oldc.usrCoords[2] / c.usrCoords[2];
    325                 bb[3] *= dy;
    326                 bb[1] *= dy;
    327                 this.board.setBoundingBox(bb, this.board.keepaspectratio, "update");
    328             }
    329
    330             return this;
    331         },
    332
    333         /**
    334          * (Re-)calculates the ticks coordinates.
    335          * @private
    336          */
    337         calculateTicksCoordinates: function () {
    338             var coordsZero, b, r_max, bb;
    339
    340             if (this.line.elementClass === Const.OBJECT_CLASS_LINE) {
    341                 // Calculate Ticks width and height in Screen and User Coordinates
    342                 this.setTicksSizeVariables();
    343
    344                 // If the parent line is not finite, we can stop here.
    345                 if (Math.abs(this.dx) < Mat.eps && Math.abs(this.dy) < Mat.eps) {
    346                     return;
    347                 }
    348             }
    349
    350             // Get Zero (coords element for lines, number for curves)
    351             coordsZero = this.getZeroCoordinates();
    352
    353             // Calculate lower bound and upper bound limits based on distance
    354             // between p1 and center and p2 and center
    355             if (this.line.elementClass === Const.OBJECT_CLASS_LINE) {
    356                 b = this.getLowerAndUpperBounds(coordsZero, 'ticksdistance');
    357             } else {
    358                 b = {
    359                     lower: this.line.minX(),
    360                     upper: this.line.maxX(),
    361                     a1: 0,
    362                     a2: 0,
    363                     m1: 0,
    364                     m2: 0
    365                 };
    366             }
    367
    368             if (Type.evaluate(this.visProp.type) === "polar") {
    369                 bb = this.board.getBoundingBox();
    370                 r_max = Math.max(
    371                     Mat.hypot(bb[0], bb[1]),
    372                     Mat.hypot(bb[2], bb[3])
    373                 );
    374                 b.upper = r_max;
    375             }
    376
    377             // Clean up
    378             this.ticks = [];
    379             this.labelsData = [];
    380             // Create Ticks Coordinates and Labels
    381             if (this.equidistant) {
    382                 this.generateEquidistantTicks(coordsZero, b);
    383             } else {
    384                 this.generateFixedTicks(coordsZero, b);
    385             }
    386
    387             return this;
    388         },
    389
    390         /**
    391          * Sets the variables used to set the height and slope of each tick.
    392          *
    393          * @private
    394          */
    395         setTicksSizeVariables: function (pos) {
    396             var d,
    397                 mi,
    398                 ma,
    399                 len,
    400                 distMaj = Type.evaluate(this.visProp.majorheight) * 0.5,
    401                 distMin = Type.evaluate(this.visProp.minorheight) * 0.5;
    402
    403             // For curves:
    404             if (Type.exists(pos)) {
    405                 mi = this.line.minX();
    406                 ma = this.line.maxX();
    407                 len = this.line.points.length;
    408                 if (len < 2) {
    409                     this.dxMaj = 0;
    410                     this.dyMaj = 0;
    411                 } else if (Mat.relDif(pos, mi) < Mat.eps) {
    412                     this.dxMaj =
    413                         this.line.points[0].usrCoords[2] - this.line.points[1].usrCoords[2];
    414                     this.dyMaj =
    415                         this.line.points[1].usrCoords[1] - this.line.points[0].usrCoords[1];
    416                 } else if (Mat.relDif(pos, ma) < Mat.eps) {
    417                     this.dxMaj =
    418                         this.line.points[len - 2].usrCoords[2] -
    419                         this.line.points[len - 1].usrCoords[2];
    420                     this.dyMaj =
    421                         this.line.points[len - 1].usrCoords[1] -
    422                         this.line.points[len - 2].usrCoords[1];
    423                 } else {
    424                     this.dxMaj = -Numerics.D(this.line.Y)(pos);
    425                     this.dyMaj = Numerics.D(this.line.X)(pos);
    426                 }
    427             } else {
    428                 // ticks width and height in screen units
    429                 this.dxMaj = this.line.stdform[1];
    430                 this.dyMaj = this.line.stdform[2];
    431             }
    432             this.dxMin = this.dxMaj;
    433             this.dyMin = this.dyMaj;
    434
    435             // ticks width and height in user units
    436             this.dx = this.dxMaj;
    437             this.dy = this.dyMaj;
    438
    439             // After this, the length of the vector (dxMaj, dyMaj) in screen coordinates is equal to distMaj pixel.
    440             d = Mat.hypot(this.dxMaj * this.board.unitX, this.dyMaj * this.board.unitY);
    441             this.dxMaj *= (distMaj / d) * this.board.unitX;
    442             this.dyMaj *= (distMaj / d) * this.board.unitY;
    443             this.dxMin *= (distMin / d) * this.board.unitX;
    444             this.dyMin *= (distMin / d) * this.board.unitY;
    445
    446             // Grid-like ticks?
    447             this.minStyle = Type.evaluate(this.visProp.minorheight) < 0 ? "infinite" : "finite";
    448             this.majStyle = Type.evaluate(this.visProp.majorheight) < 0 ? "infinite" : "finite";
    449         },
    450
    451         /**
    452          * Returns the coordinates of the point zero of the line.
    453          *
    454          * If the line is an {@link Axis}, the coordinates of the projection of the board's zero point is returned
    455          *
    456          * Otherwise, the coordinates of the point that acts as zero are
    457          * established depending on the value of {@link JXG.Ticks#anchor}
    458          *
    459          * @returns {JXG.Coords} Coords object for the zero point on the line
    460          * @private
    461          */
    462         getZeroCoordinates: function () {
    463             var c1x, c1y, c1z, c2x, c2y, c2z,
    464                 t, mi, ma,
    465                 ev_a = Type.evaluate(this.visProp.anchor);
    466
    467             if (this.line.elementClass === Const.OBJECT_CLASS_LINE) {
    468                 if (this.line.type === Const.OBJECT_TYPE_AXIS) {
    469                     return Geometry.projectPointToLine(
    470                         {
    471                             coords: {
    472                                 usrCoords: [1, 0, 0]
    473                             }
    474                         },
    475                         this.line,
    476                         this.board
    477                     );
    478                 }
    479                 c1z = this.line.point1.coords.usrCoords[0];
    480                 c1x = this.line.point1.coords.usrCoords[1];
    481                 c1y = this.line.point1.coords.usrCoords[2];
    482                 c2z = this.line.point2.coords.usrCoords[0];
    483                 c2x = this.line.point2.coords.usrCoords[1];
    484                 c2y = this.line.point2.coords.usrCoords[2];
    485
    486                 if (ev_a === "right") {
    487                     return this.line.point2.coords;
    488                 }
    489                 if (ev_a === "middle") {
    490                     return new Coords(
    491                         Const.COORDS_BY_USER,
    492                         [(c1z + c2z) * 0.5, (c1x + c2x) * 0.5, (c1y + c2y) * 0.5],
    493                         this.board
    494                     );
    495                 }
    496                 if (Type.isNumber(ev_a)) {
    497                     return new Coords(
    498                         Const.COORDS_BY_USER,
    499                         [
    500                             c1z + (c2z - c1z) * ev_a,
    501                             c1x + (c2x - c1x) * ev_a,
    502                             c1y + (c2y - c1y) * ev_a
    503                         ],
    504                         this.board
    505                     );
    506                 }
    507                 return this.line.point1.coords;
    508             }
    509             mi = this.line.minX();
    510             ma = this.line.maxX();
    511             if (ev_a === "right") {
    512                 t = ma;
    513             } else if (ev_a === "middle") {
    514                 t = (mi + ma) * 0.5;
    515             } else if (Type.isNumber(ev_a)) {
    516                 t = mi * (1 - ev_a) + ma * ev_a;
    517                 // t = ev_a;
    518             } else {
    519                 t = mi;
    520             }
    521             return t;
    522         },
    523
    524         /**
    525          * Calculate the lower and upper bounds for tick rendering.
    526          * If {@link JXG.Ticks#includeBoundaries} is false, the boundaries will exclude point1 and point2.
    527          *
    528          * @param  {JXG.Coords} coordsZero
    529          * @returns {String} [type] If type=='ticksdistance', the bounds are
    530          *                         the intersection of the line with the bounding box of the board, respecting
    531          *                         the value of the line attribute 'margin' and the width of arrow heads.
    532          *                         Otherwise, it is the projection of the corners of the bounding box
    533          *                         to the line - without the attribute 'margin' and width of arrow heads.
    534          *  <br>
    535          *                         The first case is needed to determine which ticks are displayed, i.e. where to stop.
    536          *                         The second case is to determine the distance between ticks in case of 'insertTicks:true'.
    537          * @returns {Object}     {lower: Number, upper: Number } containing the lower and upper bounds in user units.
    538          *
    539          * @private
    540          */
    541         getLowerAndUpperBounds: function (coordsZero, type) {
    542             var lowerBound, upperBound,
    543                 fA, lA,
    544                 point1, point2,
    545                 isPoint1inBoard, isPoint2inBoard,
    546                 // We use the distance from zero to P1 and P2 to establish lower and higher points
    547                 dZeroPoint1, dZeroPoint2,
    548                 arrowData,
    549                 // angle,
    550                 a1, a2, m1, m2,
    551                 eps = Mat.eps * 10,
    552                 ev_sf = Type.evaluate(this.line.visProp.straightfirst),
    553                 ev_sl = Type.evaluate(this.line.visProp.straightlast),
    554                 ev_i = Type.evaluate(this.visProp.includeboundaries);
    555
    556             // The line's defining points that will be adjusted to be within the board limits
    557             if (this.line.elementClass === Const.OBJECT_CLASS_CURVE) {
    558                 return {
    559                     lower: this.line.minX(),
    560                     upper: this.line.maxX()
    561                 };
    562             }
    563
    564             point1 = new Coords(Const.COORDS_BY_USER, this.line.point1.coords.usrCoords, this.board);
    565             point2 = new Coords(Const.COORDS_BY_USER, this.line.point2.coords.usrCoords, this.board);
    566
    567             // Are the original defining points within the board?
    568             isPoint1inBoard =
    569                 Math.abs(point1.usrCoords[0]) >= Mat.eps &&
    570                 point1.scrCoords[1] >= 0.0 &&
    571                 point1.scrCoords[1] <= this.board.canvasWidth &&
    572                 point1.scrCoords[2] >= 0.0 &&
    573                 point1.scrCoords[2] <= this.board.canvasHeight;
    574             isPoint2inBoard =
    575                 Math.abs(point2.usrCoords[0]) >= Mat.eps &&
    576                 point2.scrCoords[1] >= 0.0 &&
    577                 point2.scrCoords[1] <= this.board.canvasWidth &&
    578                 point2.scrCoords[2] >= 0.0 &&
    579                 point2.scrCoords[2] <= this.board.canvasHeight;
    580
    581             // Adjust line limit points to be within the board
    582             if (Type.exists(type) && type === 'ticksdistance') {
    583                 // The good old calcStraight is needed for determining the distance between major ticks.
    584                 // Here, only the visual area is of importance
    585                 Geometry.calcStraight(this.line, point1, point2, 0);
    586                 m1 = this.getDistanceFromZero(coordsZero, point1);
    587                 m2 = this.getDistanceFromZero(coordsZero, point2);
    588                 Geometry.calcStraight(this.line, point1, point2, Type.evaluate(this.line.visProp.margin));
    589                 m1 = this.getDistanceFromZero(coordsZero, point1) - m1;
    590                 m2 = this.getDistanceFromZero(coordsZero, point2).m2;
    591             } else {
    592                 // This function projects the corners of the board to the line.
    593                 // This is important for diagonal lines with infinite tick lines.
    594                 Geometry.calcLineDelimitingPoints(this.line, point1, point2);
    595             }
    596
    597             // Shorten ticks bounds such that ticks are not through arrow heads
    598             fA = Type.evaluate(this.line.visProp.firstarrow);
    599             lA = Type.evaluate(this.line.visProp.lastarrow);
    600
    601             a1 = this.getDistanceFromZero(coordsZero, point1);
    602             a2 = this.getDistanceFromZero(coordsZero, point2);
    603             if (fA || lA) {
    604                 // Do not display ticks at through arrow heads.
    605                 // In arrowData we ignore the highlighting status.
    606                 // Ticks would appear to be too nervous.
    607                 arrowData = this.board.renderer.getArrowHeadData(
    608                     this.line,
    609                     Type.evaluate(this.line.visProp.strokewidth),
    610                     ''
    611                 );
    612
    613                 this.board.renderer.getPositionArrowHead(
    614                     this.line,
    615                     point1,
    616                     point2,
    617                     arrowData
    618                 );
    619             }
    620             // Calculate (signed) distance from Zero to P1 and to P2
    621             dZeroPoint1 = this.getDistanceFromZero(coordsZero, point1);
    622             dZeroPoint2 = this.getDistanceFromZero(coordsZero, point2);
    623
    624             // Recompute lengths of arrow heads
    625             a1 = dZeroPoint1 - a1;
    626             a2 = dZeroPoint1 - a2;
    627
    628             // We have to establish if the direction is P1->P2 or P2->P1 to set the lower and upper
    629             // bounds appropriately. As the distances contain also a sign to indicate direction,
    630             // we can compare dZeroPoint1 and dZeroPoint2 to establish the line direction
    631             if (dZeroPoint1 < dZeroPoint2) {
    632                 // Line goes P1->P2
    633                 lowerBound = dZeroPoint1;
    634                 upperBound = dZeroPoint2;
    635
    636                 if (!ev_sf && isPoint1inBoard && !ev_i) {
    637                     lowerBound += eps;
    638                 }
    639                 if (!ev_sl && isPoint2inBoard && !ev_i) {
    640                     upperBound -= eps;
    641                 }
    642             } else if (dZeroPoint2 < dZeroPoint1) {
    643                 // Line goes P2->P1
    644                 // Does this happen at all?
    645                 lowerBound = dZeroPoint2;
    646                 upperBound = dZeroPoint1;
    647
    648                 if (!ev_sl && isPoint2inBoard && !ev_i) {
    649                     lowerBound += eps;
    650                 }
    651                 if (!ev_sf && isPoint1inBoard && !ev_i) {
    652                     upperBound -= eps;
    653                 }
    654             } else {
    655                 // P1 = P2 = Zero, we can't do a thing
    656                 lowerBound = 0;
    657                 upperBound = 0;
    658             }
    659
    660             return {
    661                 lower: lowerBound,
    662                 upper: upperBound,
    663                 a1: a1,
    664                 a2: a2,
    665                 m1: m1,
    666                 m2: m2
    667             };
    668         },
    669
    670         /**
    671          * Calculates the signed distance in user coordinates from zero to a given point.
    672          * Sign is positive, if the direction from zero to point is the same as the direction
    673          * zero to point2 of the line.
    674          *
    675          * @param  {JXG.Coords} zero  coordinates of the point considered zero
    676          * @param  {JXG.Coords} point coordinates of the point to find out the distance
    677          * @returns {Number}           distance between zero and point, including its sign
    678          * @private
    679          */
    680         getDistanceFromZero: function (zero, point) {
    681             var p1, p2, dirLine, dirPoint, distance;
    682
    683             p1 = this.line.point1.coords;
    684             p2 = this.line.point2.coords;
    685             distance = zero.distance(Const.COORDS_BY_USER, point);
    686
    687             // Establish sign
    688             dirLine = [
    689                 p2.usrCoords[0] - p1.usrCoords[0],
    690                 p2.usrCoords[1] - p1.usrCoords[1],
    691                 p2.usrCoords[2] - p1.usrCoords[2]
    692             ];
    693             dirPoint = [
    694                 point.usrCoords[0] - zero.usrCoords[0],
    695                 point.usrCoords[1] - zero.usrCoords[1],
    696                 point.usrCoords[2] - zero.usrCoords[2]
    697             ];
    698             if (Mat.innerProduct(dirLine, dirPoint, 3) < 0) {
    699                 distance *= -1;
    700             }
    701
    702             return distance;
    703         },
    704
    705         /**
    706          * Creates ticks coordinates and labels automatically.
    707          * The frequency of ticks is affected by the values of {@link JXG.Ticks#insertTicks}, {@link JXG.Ticks#minTicksDistance},
    708          * and {@link JXG.Ticks#ticksDistance}
    709          *
    710          * @param  {JXG.Coords} coordsZero coordinates of the point considered zero
    711          * @param  {Object}     bounds     contains the lower and upper bounds for ticks placement
    712          * @private
    713          */
    714         generateEquidistantTicks: function (coordsZero, bounds) {
    715             var tickPosition,
    716                 eps = Mat.eps,
    717                 deltas, ticksDelta,
    718                 // ev_mia = Type.evaluate(this.visProp.minorticksinarrow),
    719                 // ev_maa = Type.evaluate(this.visProp.minorticksinarrow),
    720                 // ev_mla = Type.evaluate(this.visProp.minorticksinarrow),
    721                 ev_mt = Type.evaluate(this.visProp.minorticks);
    722
    723             // Determine a proposed distance between major ticks in user units
    724             ticksDelta = this.getDistanceMajorTicks();
    725
    726             // Obsolete, since this.equidistant is always true at this point
    727             // ticksDelta = this.equidistant ? this.ticksFunction(1) : this.ticksDelta;
    728
    729             if (this.line.elementClass === Const.OBJECT_CLASS_LINE) {
    730                 // Calculate x and y distances between two points on the line which are 1 unit apart
    731                 // In essence, these are cosine and sine.
    732                 deltas = this.getXandYdeltas();
    733             }
    734
    735             ticksDelta *= Type.evaluate(this.visProp.scale);
    736
    737             // In case of insertTicks, adjust ticks distance to satisfy the minTicksDistance restriction.
    738             // if (ev_it) { // } && this.minTicksDistance > Mat.eps) {
    739             //     ticksDelta = this.adjustTickDistance(ticksDelta, coordsZero, deltas);
    740             // }
    741
    742             // Convert ticksdelta to the distance between two minor ticks
    743             ticksDelta /= (ev_mt + 1);
    744             this.ticksDelta = ticksDelta;
    745
    746             if (ticksDelta < Mat.eps) {
    747                 return;
    748             }
    749
    750             // Position ticks from zero to the positive side while not reaching the upper boundary
    751             tickPosition = 0;
    752             if (!Type.evaluate(this.visProp.drawzero)) {
    753                 tickPosition = ticksDelta;
    754             }
    755             while (tickPosition <= bounds.upper + eps) {
    756                 // Only draw ticks when we are within bounds, ignore case where tickPosition < lower < upper
    757                 if (tickPosition >= bounds.lower - eps) {
    758                     this.processTickPosition(coordsZero, tickPosition, ticksDelta, deltas);
    759                 }
    760                 tickPosition += ticksDelta;
    761
    762                 // Emergency out
    763                 if (bounds.upper - tickPosition > ticksDelta * 10000) {
    764                     break;
    765                 }
    766             }
    767
    768             // Position ticks from zero (not inclusive) to the negative side while not reaching the lower boundary
    769             tickPosition = -ticksDelta;
    770             while (tickPosition >= bounds.lower - eps) {
    771                 // Only draw ticks when we are within bounds, ignore case where lower < upper < tickPosition
    772                 if (tickPosition <= bounds.upper + eps) {
    773                     this.processTickPosition(coordsZero, tickPosition, ticksDelta, deltas);
    774                 }
    775                 tickPosition -= ticksDelta;
    776
    777                 // Emergency out
    778                 if (tickPosition - bounds.lower > ticksDelta * 10000) {
    779                     break;
    780                 }
    781             }
    782         },
    783
    784         /**
    785          * Calculates the distance between two major ticks in user units.
    786          * <ul>
    787          * <li> If the attribute "insertTicks" is false, the value of the attribute
    788          * "ticksDistance" is returned. The attribute "minTicksDistance" is ignored in this case.
    789          * <li> If the attribute "insertTicks" is true, the attribute "ticksDistance" is ignored.
    790          * The distance between two major ticks is computed
    791          * as <i>a 10<sup>i</sup></i>, where <i>a</i> is one of <i>{1, 2, 5}</i> and
    792          * the number <i>a 10<sup>i</sup></i> is maximized such that there are approximately
    793          * 6 major ticks and there are at least "minTicksDistance" pixel between minor ticks.
    794          * The latter restriction has priority over the number of major ticks.
    795          * </ul>
    796          * @returns Number
    797          * @private
    798          */
    799         getDistanceMajorTicks: function () {
    800             var delta, delta2,
    801                 b, d, dist,
    802                 scale,
    803                 numberMajorTicks = 5,
    804                 maxDist, minDist, ev_minti;
    805
    806             if (Type.evaluate(this.visProp.insertticks)) {
    807                 // Case of insertTicks==true:
    808                 // Here, we ignore the attribute 'margin'
    809                 b = this.getLowerAndUpperBounds(this.getZeroCoordinates(), '');
    810
    811                 dist = (b.upper - b.lower);
    812                 scale = Type.evaluate(this.visProp.scale);
    813
    814                 maxDist = dist / (numberMajorTicks + 1) / scale;
    815                 minDist = Type.evaluate(this.visProp.minticksdistance) / scale;
    816                 ev_minti = Type.evaluate(this.visProp.minorticks);
    817
    818                 d = this.getXandYdeltas();
    819                 d.x *= this.board.unitX;
    820                 d.y *= this.board.unitY;
    821                 minDist /= Mat.hypot(d.x, d.y);
    822                 minDist *= (ev_minti + 1);
    823
    824                 // Determine minimal delta to fulfill the minTicksDistance constraint
    825                 delta = Math.pow(10, Math.floor(Math.log(minDist) / Math.LN10));
    826                 if (2 * delta >= minDist) {
    827                     delta *= 2;
    828                 } else if (5 * delta >= minDist) {
    829                     delta *= 5;
    830                 }
    831
    832                 // Determine maximal delta to fulfill the constraint to have approx. "numberMajorTicks" majorTicks
    833                 delta2 = Math.pow(10, Math.floor(Math.log(maxDist) / Math.LN10));
    834                 if (5 * delta2 < maxDist) {
    835                     delta2 *= 5;
    836                 } else if (2 * delta2 < maxDist) {
    837                     delta2 *= 2;
    838                 }
    839                 // Take the larger value of the two delta's, that is
    840                 // minTicksDistance has priority over numberMajorTicks
    841                 delta = Math.max(delta, delta2);
    842
    843                 // < v1.6.0:
    844                 // delta = Math.pow(10, Math.floor(Math.log(0.6 * dist) / Math.LN10));
    845                 // if (false && dist <= 6 * delta) {
    846                 //     delta *= 0.5;
    847                 // }
    848                 return delta;
    849             }
    850
    851             // Case of insertTicks==false
    852             return Type.evaluate(this.visProp.ticksdistance);
    853         },
    854
    855         //         /**
    856         //          * Auxiliary method used by {@link JXG.Ticks#generateEquidistantTicks} to adjust the
    857         //          * distance between two ticks depending on {@link JXG.Ticks#minTicksDistance} value
    858         //          *
    859         //          * @param  {Number}     ticksDelta  distance between two major ticks in user coordinates
    860         //          * @param  {JXG.Coords} coordsZero  coordinates of the point considered zero
    861         //          * @param  {Object}     deltas      x and y distance in pixel between two user units
    862         //          * @param  {Object}     bounds      upper and lower bound of the tick positions in user units.
    863         //          * @private
    864         //          */
    865         //         adjustTickDistance: function (ticksDelta, coordsZero, deltas) {
    866         //             var nx,
    867         //                 ny,
    868         //                 // bounds,
    869         //                 distScr,
    870         //                 sgn = 1,
    871         //                 ev_mintd = Type.evaluate(this.visProp.minticksdistance),
    872         //                 ev_minti = Type.evaluate(this.visProp.minorticks);
    873
    874         //             if (this.line.elementClass === Const.OBJECT_CLASS_CURVE) {
    875         //                 return ticksDelta;
    876         //             }
    877         //             // Seems to be ignored:
    878         //             // bounds = this.getLowerAndUpperBounds(coordsZero, "ticksdistance");
    879
    880         //             // distScr is the distance between two major Ticks in pixel
    881         //             nx = coordsZero.usrCoords[1] + deltas.x * ticksDelta;
    882         //             ny = coordsZero.usrCoords[2] + deltas.y * ticksDelta;
    883         //             distScr = coordsZero.distance(
    884         //                 Const.COORDS_BY_SCREEN,
    885         //                 new Coords(Const.COORDS_BY_USER, [nx, ny], this.board)
    886         //             );
    887         // // console.log(deltas, distScr, this.board.unitX, this.board.unitY, "ticksDelta:", ticksDelta);
    888
    889         //             if (ticksDelta === 0.0) {
    890         //                 return 0.0;
    891         //             }
    892
    893         // // console.log(":", distScr, ev_minti + 1, distScr / (ev_minti + 1), ev_mintd)
    894         //             while (false && distScr / (ev_minti + 1) < ev_mintd) {
    895         //                 if (sgn === 1) {
    896         //                     ticksDelta *= 2;
    897         //                 } else {
    898         //                     ticksDelta *= 5;
    899         //                 }
    900         //                 sgn *= -1;
    901
    902         //                 nx = coordsZero.usrCoords[1] + deltas.x * ticksDelta;
    903         //                 ny = coordsZero.usrCoords[2] + deltas.y * ticksDelta;
    904         //                 distScr = coordsZero.distance(
    905         //                     Const.COORDS_BY_SCREEN,
    906         //                     new Coords(Const.COORDS_BY_USER, [nx, ny], this.board)
    907         //                 );
    908         //             }
    909
    910         //             return ticksDelta;
    911         //         },
    912
    913         /**
    914          * Auxiliary method used by {@link JXG.Ticks#generateEquidistantTicks} to create a tick
    915          * in the line at the given tickPosition.
    916          *
    917          * @param  {JXG.Coords} coordsZero    coordinates of the point considered zero
    918          * @param  {Number}     tickPosition  current tick position relative to zero
    919          * @param  {Number}     ticksDelta    distance between two major ticks in user coordinates
    920          * @param  {Object}     deltas      x and y distance between two major ticks
    921          * @private
    922          */
    923         processTickPosition: function (coordsZero, tickPosition, ticksDelta, deltas) {
    924             var x,
    925                 y,
    926                 tickCoords,
    927                 ti,
    928                 isLabelPosition,
    929                 ticksPerLabel = Type.evaluate(this.visProp.ticksperlabel),
    930                 labelVal = null;
    931
    932             // Calculates tick coordinates
    933             if (this.line.elementClass === Const.OBJECT_CLASS_LINE) {
    934                 x = coordsZero.usrCoords[1] + tickPosition * deltas.x;
    935                 y = coordsZero.usrCoords[2] + tickPosition * deltas.y;
    936             } else {
    937                 x = this.line.X(coordsZero + tickPosition);
    938                 y = this.line.Y(coordsZero + tickPosition);
    939             }
    940             tickCoords = new Coords(Const.COORDS_BY_USER, [x, y], this.board);
    941             if (this.line.elementClass === Const.OBJECT_CLASS_CURVE) {
    942                 labelVal = coordsZero + tickPosition;
    943                 this.setTicksSizeVariables(labelVal);
    944             }
    945
    946             // Test if tick is a major tick.
    947             // This is the case if tickPosition/ticksDelta is
    948             // a multiple of the number of minorticks+1
    949             tickCoords.major =
    950                 Math.round(tickPosition / ticksDelta) %
    951                 (Type.evaluate(this.visProp.minorticks) + 1) ===
    952                 0;
    953
    954             if (!ticksPerLabel) {
    955                 // In case of null, 0 or false, majorTicks are labelled
    956                 ticksPerLabel = Type.evaluate(this.visProp.minorticks) + 1;
    957             }
    958             isLabelPosition = Math.round(tickPosition / ticksDelta) % ticksPerLabel === 0;
    959
    960             // Compute the start position and the end position of a tick.
    961             // If both positions are out of the canvas, ti is empty.
    962             ti = this.createTickPath(tickCoords, tickCoords.major);
    963             if (ti.length === 3) {
    964                 this.ticks.push(ti);
    965                 if (isLabelPosition && Type.evaluate(this.visProp.drawlabels)) {
    966                     // Create a label at this position
    967                     this.labelsData.push(
    968                         this.generateLabelData(
    969                             this.generateLabelText(tickCoords, coordsZero, labelVal),
    970                             tickCoords,
    971                             this.ticks.length
    972                         )
    973                     );
    974                 } else {
    975                     // minor ticks have no labels
    976                     this.labelsData.push(null);
    977                 }
    978             }
    979         },
    980
    981         /**
    982          * Creates ticks coordinates and labels based on {@link JXG.Ticks#fixedTicks} and {@link JXG.Ticks#labels}.
    983          *
    984          * @param  {JXG.Coords} coordsZero Coordinates of the point considered zero
    985          * @param  {Object}     bounds     contains the lower and upper bounds for ticks placement
    986          * @private
    987          */
    988         generateFixedTicks: function (coordsZero, bounds) {
    989             var tickCoords,
    990                 labelText,
    991                 i,
    992                 ti,
    993                 x,
    994                 y,
    995                 eps2 = Mat.eps,
    996                 fixedTick,
    997                 hasLabelOverrides = Type.isArray(this.visProp.labels),
    998                 deltas,
    999                 ev_dl = Type.evaluate(this.visProp.drawlabels);
    1000
    1001             if (this.line.elementClass === Const.OBJECT_CLASS_LINE) {
    1002                 // Calculate x and y distances between two points on the line which are 1 unit apart
    1003                 // In essence, these are cosine and sine.
    1004                 deltas = this.getXandYdeltas();
    1005             }
    1006             for (i = 0; i < this.fixedTicks.length; i++) {
    1007                 if (this.line.elementClass === Const.OBJECT_CLASS_LINE) {
    1008                     fixedTick = this.fixedTicks[i];
    1009                     x = coordsZero.usrCoords[1] + fixedTick * deltas.x;
    1010                     y = coordsZero.usrCoords[2] + fixedTick * deltas.y;
    1011                 } else {
    1012                     fixedTick = coordsZero + this.fixedTicks[i];
    1013                     x = this.line.X(fixedTick);
    1014                     y = this.line.Y(fixedTick);
    1015                 }
    1016                 tickCoords = new Coords(Const.COORDS_BY_USER, [x, y], this.board);
    1017
    1018                 if (this.line.elementClass === Const.OBJECT_CLASS_CURVE) {
    1019                     this.setTicksSizeVariables(fixedTick);
    1020                 }
    1021
    1022                 // Compute the start position and the end position of a tick.
    1023                 // If tick is out of the canvas, ti is empty.
    1024                 ti = this.createTickPath(tickCoords, true);
    1025                 if (
    1026                     ti.length === 3 &&
    1027                     fixedTick >= bounds.lower - eps2 &&
    1028                     fixedTick <= bounds.upper + eps2
    1029                 ) {
    1030                     this.ticks.push(ti);
    1031
    1032                     if (ev_dl && (hasLabelOverrides || Type.exists(this.visProp.labels[i]))) {
    1033                         labelText = hasLabelOverrides
    1034                             ? Type.evaluate(this.visProp.labels[i])
    1035                             : fixedTick;
    1036                         this.labelsData.push(
    1037                             this.generateLabelData(
    1038                                 this.generateLabelText(tickCoords, coordsZero, labelText),
    1039                                 tickCoords,
    1040                                 i
    1041                             )
    1042                         );
    1043                     } else {
    1044                         this.labelsData.push(null);
    1045                     }
    1046                 }
    1047             }
    1048         },
    1049
    1050         /**
    1051          * Calculates the x and y distances in user coordinates between two units in user space.
    1052          * In essence, these are cosine and sine. The only work to be done is to determine
    1053          * the direction of the line.
    1054          *
    1055          * @returns {Object}
    1056          * @private
    1057          */
    1058         getXandYdeltas: function () {
    1059             var // Auxiliary points to store the start and end of the line according to its direction
    1060                 point1UsrCoords,
    1061                 point2UsrCoords,
    1062                 distP1P2 = this.line.point1.Dist(this.line.point2);
    1063
    1064             // if (this.line.type === Const.OBJECT_TYPE_AXIS) {
    1065             //     // When line is an Axis, direction depends on board coordinates system
    1066             //     // Assume line.point1 and line.point2 are in correct order
    1067             //     point1UsrCoords = this.line.point1.coords.usrCoords;
    1068             //     point2UsrCoords = this.line.point2.coords.usrCoords;
    1069             //     // Check if direction is incorrect, then swap
    1070             //     if (
    1071             //         point1UsrCoords[1] > point2UsrCoords[1] ||
    1072             //         (Math.abs(point1UsrCoords[1] - point2UsrCoords[1]) < Mat.eps &&
    1073             //             point1UsrCoords[2] > point2UsrCoords[2])
    1074             //     ) {
    1075             //         point1UsrCoords = this.line.point2.coords.usrCoords;
    1076             //         point2UsrCoords = this.line.point1.coords.usrCoords;
    1077             //     }
    1078             // } /* if (this.line.elementClass === Const.OBJECT_CLASS_LINE)*/ else {
    1079                 // Line direction is always from P1 to P2 for non axis types
    1080                 point1UsrCoords = this.line.point1.coords.usrCoords;
    1081                 point2UsrCoords = this.line.point2.coords.usrCoords;
    1082             // }
    1083             return {
    1084                 x: (point2UsrCoords[1] - point1UsrCoords[1]) / distP1P2,
    1085                 y: (point2UsrCoords[2] - point1UsrCoords[2]) / distP1P2
    1086             };
    1087         },
    1088
    1089         /**
    1090          * Check if (parts of) the tick is inside the canvas. The tick intersects the boundary
    1091          * at two positions: [x[0], y[0]] and [x[1], y[1]] in screen coordinates.
    1092          * @param  {Array}  x Array of length two
    1093          * @param  {Array}  y Array of length two
    1094          * @return {Boolean}   true if parts of the tick are inside of the canvas or on the boundary.
    1095          */
    1096         _isInsideCanvas: function (x, y, m) {
    1097             var cw = this.board.canvasWidth,
    1098                 ch = this.board.canvasHeight;
    1099
    1100             if (m === undefined) {
    1101                 m = 0;
    1102             }
    1103             return (
    1104                 (x[0] >= m && x[0] <= cw - m && y[0] >= m && y[0] <= ch - m) ||
    1105                 (x[1] >= m && x[1] <= cw - m && y[1] >= m && y[1] <= ch - m)
    1106             );
    1107         },
    1108
    1109         /**
    1110          * @param {JXG.Coords} coords Coordinates of the tick on the line.
    1111          * @param {Boolean} major True if tick is major tick.
    1112          * @returns {Array} Array of length 3 containing path coordinates in screen coordinates
    1113          *                 of the tick (arrays of length 2). 3rd entry is true if major tick otherwise false.
    1114          *                 If the tick is outside of the canvas, the return array is empty.
    1115          * @private
    1116          */
    1117         createTickPath: function (coords, major) {
    1118             var c,
    1119                 lineStdForm,
    1120                 intersection,
    1121                 dxs,
    1122                 dys,
    1123                 dxr,
    1124                 dyr,
    1125                 alpha,
    1126                 style,
    1127                 x = [-2000000, -2000000],
    1128                 y = [-2000000, -2000000],
    1129                 i, r, r_max, bb, full, delta,
    1130                 // Used for infinite ticks
    1131                 te0, te1, // Tick ending visProps
    1132                 dists; // 'signed' distances of intersections to the parent line
    1133
    1134             c = coords.scrCoords;
    1135             if (major) {
    1136                 dxs = this.dxMaj;
    1137                 dys = this.dyMaj;
    1138                 style = this.majStyle;
    1139                 te0 = Type.evaluate(this.visProp.majortickendings[0]) > 0;
    1140                 te1 = Type.evaluate(this.visProp.majortickendings[1]) > 0;
    1141             } else {
    1142                 dxs = this.dxMin;
    1143                 dys = this.dyMin;
    1144                 style = this.minStyle;
    1145                 te0 = Type.evaluate(this.visProp.tickendings[0]) > 0;
    1146                 te1 = Type.evaluate(this.visProp.tickendings[1]) > 0;
    1147             }
    1148             lineStdForm = [-dys * c[1] - dxs * c[2], dys, dxs];
    1149
    1150             // For all ticks regardless if of finite or infinite
    1151             // tick length the intersection with the canvas border is
    1152             // computed.
    1153             if (major && Type.evaluate(this.visProp.type) === "polar") {
    1154                 // polar style
    1155                 bb = this.board.getBoundingBox();
    1156                 full = 2.0 * Math.PI;
    1157                 delta = full / 180;
    1158                 //ratio = this.board.unitY / this.board.X;
    1159
    1160                 // usrCoords: Test if 'circle' is inside of the canvas
    1161                 c = coords.usrCoords;
    1162                 r = Mat.hypot(c[1], c[2]);
    1163                 r_max = Math.max(
    1164                     Mat.hypot(bb[0], bb[1]),
    1165                     Mat.hypot(bb[2], bb[3])
    1166                 );
    1167
    1168                 if (r < r_max) {
    1169                     // Now, switch to screen coords
    1170                     x = [];
    1171                     y = [];
    1172                     for (i = 0; i <= full; i += delta) {
    1173                         x.push(
    1174                             this.board.origin.scrCoords[1] + r * Math.cos(i) * this.board.unitX
    1175                         );
    1176                         y.push(
    1177                             this.board.origin.scrCoords[2] + r * Math.sin(i) * this.board.unitY
    1178                         );
    1179                     }
    1180                     return [x, y, major];
    1181                 }
    1182             } else {
    1183                 // line style
    1184                 if (style === 'infinite') {
    1185                     // Problematic are infinite ticks which have set tickendings:[0,1].
    1186                     // For example, this is the default setting for minor ticks
    1187                     if (Type.evaluate(this.visProp.ignoreinfinitetickendings)) {
    1188                         te0 = te1 = true;
    1189                     }
    1190                     intersection = Geometry.meetLineBoard(lineStdForm, this.board);
    1191
    1192                     if (te0 && te1) {
    1193                         x[0] = intersection[0].scrCoords[1];
    1194                         x[1] = intersection[1].scrCoords[1];
    1195                         y[0] = intersection[0].scrCoords[2];
    1196                         y[1] = intersection[1].scrCoords[2];
    1197                     } else {
    1198                         // Assuming the usrCoords of both intersections are normalized, a 'signed distance'
    1199                         // with respect to the parent line is computed for the intersections. The sign is
    1200                         // used to conclude whether the point is either at the left or right side of the
    1201                         // line. The magnitude can be used to compare the points and determine which point
    1202                         // is closest to the line.
    1203                         dists = [
    1204                             Mat.innerProduct(
    1205                                 intersection[0].usrCoords.slice(1, 3),
    1206                                 this.line.stdform.slice(1, 3)
    1207                             ) + this.line.stdform[0],
    1208                             Mat.innerProduct(
    1209                                 intersection[1].usrCoords.slice(1, 3),
    1210                                 this.line.stdform.slice(1, 3)
    1211                             ) + this.line.stdform[0]
    1212                         ];
    1213
    1214                         // Reverse intersection array order if first intersection is not the leftmost one.
    1215                         if (dists[0] < dists[1]) {
    1216                             intersection.reverse();
    1217                             dists.reverse();
    1218                         }
    1219
    1220                         if (te0) { // Left-infinite tick
    1221                             if (dists[0] < 0) { // intersections at the wrong side of line
    1222                                 return [];
    1223                             } else if (dists[1] < 0) { // 'default' case, tick drawn from line to board bounds
    1224                                 x[0] = intersection[0].scrCoords[1];
    1225                                 y[0] = intersection[0].scrCoords[2];
    1226                                 x[1] = c[1];
    1227                                 y[1] = c[2];
    1228                             } else { // tick visible, but coords of tick on line are outside the visible area
    1229                                 x[0] = intersection[0].scrCoords[1];
    1230                                 y[0] = intersection[0].scrCoords[2];
    1231                                 x[1] = intersection[1].scrCoords[1];
    1232                                 y[1] = intersection[1].scrCoords[2];
    1233                             }
    1234                         } else if (te1) { // Right-infinite tick
    1235                             if (dists[1] > 0) { // intersections at the wrong side of line
    1236                                 return [];
    1237                             } else if (dists[0] > 0) { // 'default' case, tick drawn from line to board bounds
    1238                                 x[0] = c[1];
    1239                                 y[0] = c[2];
    1240                                 x[1] = intersection[1].scrCoords[1];
    1241                                 y[1] = intersection[1].scrCoords[2];
    1242                             } else { // tick visible, but coords of tick on line are outside the visible area
    1243                                 x[0] = intersection[0].scrCoords[1];
    1244                                 y[0] = intersection[0].scrCoords[2];
    1245                                 x[1] = intersection[1].scrCoords[1];
    1246                                 y[1] = intersection[1].scrCoords[2];
    1247                             }
    1248                         }
    1249                     }
    1250                 } else {
    1251                     if (Type.evaluate(this.visProp.face) === ">") {
    1252                         alpha = Math.PI / 4;
    1253                     } else if (Type.evaluate(this.visProp.face) === "<") {
    1254                         alpha = -Math.PI / 4;
    1255                     } else {
    1256                         alpha = 0;
    1257                     }
    1258                     dxr = Math.cos(alpha) * dxs - Math.sin(alpha) * dys;
    1259                     dyr = Math.sin(alpha) * dxs + Math.cos(alpha) * dys;
    1260
    1261                     x[0] = c[1] + dxr * te0; // Type.evaluate(this.visProp.tickendings[0]);
    1262                     y[0] = c[2] - dyr * te0; // Type.evaluate(this.visProp.tickendings[0]);
    1263                     x[1] = c[1];
    1264                     y[1] = c[2];
    1265
    1266                     alpha = -alpha;
    1267                     dxr = Math.cos(alpha) * dxs - Math.sin(alpha) * dys;
    1268                     dyr = Math.sin(alpha) * dxs + Math.cos(alpha) * dys;
    1269
    1270                     x[2] = c[1] - dxr * te1; // Type.evaluate(this.visProp.tickendings[1]);
    1271                     y[2] = c[2] + dyr * te1; // Type.evaluate(this.visProp.tickendings[1]);
    1272                 }
    1273
    1274                 // Check if (parts of) the tick is inside the canvas.
    1275                 if (this._isInsideCanvas(x, y)) {
    1276                     return [x, y, major];
    1277                 }
    1278             }
    1279
    1280             return [];
    1281         },
    1282
    1283         /**
    1284          * Format label texts. Show the desired number of digits
    1285          * and use utf-8 minus sign.
    1286          * @param  {Number} value Number to be displayed
    1287          * @return {String}       The value converted into a string.
    1288          * @private
    1289          */
    1290         formatLabelText: function (value) {
    1291             var labelText,
    1292                 digits,
    1293                 ev_um = Type.evaluate(this.visProp.label.usemathjax),
    1294                 ev_uk = Type.evaluate(this.visProp.label.usekatex),
    1295                 ev_s = Type.evaluate(this.visProp.scalesymbol);
    1296
    1297             if (Type.isNumber(value)) {
    1298                 if (Type.evaluate(this.visProp.label.tofraction)) {
    1299                     if (ev_um) {
    1300                         labelText = '\\(' + Type.toFraction(value, true) + '\\)';
    1301                     } else {
    1302                         labelText = Type.toFraction(value, ev_uk);
    1303                     }
    1304                 } else {
    1305                     digits = Type.evaluate(this.visProp.digits);
    1306                     if (this.useLocale()) {
    1307                         labelText = this.formatNumberLocale(value, digits);
    1308                     } else {
    1309                         labelText = (Math.round(value * 1e11) / 1e11).toString();
    1310
    1311                         if (
    1312                             labelText.length > Type.evaluate(this.visProp.maxlabellength) ||
    1313                             labelText.indexOf("e") !== -1
    1314                         ) {
    1315                             if (Type.evaluate(this.visProp.precision) !== 3 && digits === 3) {
    1316                                 // Use the deprecated attribute "precision"
    1317                                 digits = Type.evaluate(this.visProp.precision);
    1318                             }
    1319
    1320                             //labelText = value.toPrecision(digits).toString();
    1321                             labelText = value.toExponential(digits).toString();
    1322                         }
    1323                     }
    1324                 }
    1325
    1326                 if (Type.evaluate(this.visProp.beautifulscientificticklabels)) {
    1327                     labelText = this.beautifyScientificNotationLabel(labelText);
    1328                 }
    1329
    1330                 if (labelText.indexOf(".") > -1 && labelText.indexOf("e") === -1) {
    1331                     // trim trailing zeros
    1332                     labelText = labelText.replace(/0+$/, "");
    1333                     // trim trailing .
    1334                     labelText = labelText.replace(/\.$/, "");
    1335                 }
    1336             } else {
    1337                 labelText = value.toString();
    1338             }
    1339
    1340             if (ev_s.length > 0) {
    1341                 if (labelText === "1") {
    1342                     labelText = ev_s;
    1343                 } else if (labelText === "-1") {
    1344                     labelText = "-" + ev_s;
    1345                 } else if (labelText !== "0") {
    1346                     labelText = labelText + ev_s;
    1347                 }
    1348             }
    1349
    1350             if (Type.evaluate(this.visProp.useunicodeminus)) {
    1351                 labelText = labelText.replace(/-/g, "\u2212");
    1352             }
    1353             return labelText;
    1354         },
    1355
    1356         /**
    1357          * Formats label texts to make labels displayed in scientific notation look beautiful.
    1358          * For example, label 5.00e+6 will become 5•10⁶, label -1.00e-7 will become into -1•10⁻⁷
    1359          * @param {String} labelText - The label that we want to convert
    1360          * @returns {String} If labelText was not in scientific notation, return labelText without modifications.
    1361          * Otherwise returns beautified labelText with proper superscript notation.
    1362          */
    1363         beautifyScientificNotationLabel: function (labelText) {
    1364             var returnString;
    1365
    1366             if (labelText.indexOf("e") === -1) {
    1367                 return labelText;
    1368             }
    1369
    1370             // Clean up trailing 0's, so numbers like 5.00e+6.0 for example become into 5e+6
    1371             returnString =
    1372                 parseFloat(labelText.substring(0, labelText.indexOf("e"))) +
    1373                 labelText.substring(labelText.indexOf("e"));
    1374
    1375             // Replace symbols like -,0,1,2,3,4,5,6,7,8,9 with their superscript version.
    1376             // Gets rid of + symbol since there is no need for it anymore.
    1377             returnString = returnString.replace(/e(.*)$/g, function (match, $1) {
    1378                 var temp = "\u2022" + "10";
    1379                 // Note: Since board ticks do not support HTTP elements like <sub>, we need to replace
    1380                 // all the numbers with superscript Unicode characters.
    1381                 temp += $1
    1382                     .replace(/-/g, "\u207B")
    1383                     .replace(/\+/g, "")
    1384                     .replace(/0/g, "\u2070")
    1385                     .replace(/1/g, "\u00B9")
    1386                     .replace(/2/g, "\u00B2")
    1387                     .replace(/3/g, "\u00B3")
    1388                     .replace(/4/g, "\u2074")
    1389                     .replace(/5/g, "\u2075")
    1390                     .replace(/6/g, "\u2076")
    1391                     .replace(/7/g, "\u2077")
    1392                     .replace(/8/g, "\u2078")
    1393                     .replace(/9/g, "\u2079");
    1394
    1395                 return temp;
    1396             });
    1397
    1398             return returnString;
    1399         },
    1400
    1401         /**
    1402          * Creates the label text for a given tick. A value for the text can be provided as a number or string
    1403          *
    1404          * @param  {JXG.Coords}    tick  The Coords-object of the tick to create a label for
    1405          * @param  {JXG.Coords}    zero  The Coords-object of line's zero
    1406          * @param  {Number|String} value A predefined value for this tick
    1407          * @returns {String}
    1408          * @private
    1409          */
    1410         generateLabelText: function (tick, zero, value) {
    1411             var labelText, distance;
    1412
    1413             // No value provided, equidistant, so assign distance as value
    1414             if (!Type.exists(value)) {
    1415                 // could be null or undefined
    1416                 distance = this.getDistanceFromZero(zero, tick);
    1417                 if (Math.abs(distance) < Mat.eps) {
    1418                     // Point is zero
    1419                     return "0";
    1420                 }
    1421                 value = distance / Type.evaluate(this.visProp.scale);
    1422             }
    1423             labelText = this.formatLabelText(value);
    1424
    1425             return labelText;
    1426         },
    1427
    1428         /**
    1429          * Create a tick label data, i.e. text and coordinates
    1430          * @param  {String}     labelText
    1431          * @param  {JXG.Coords} tick
    1432          * @param  {Number}     tickNumber
    1433          * @returns {Object} with properties 'x', 'y', 't' (text), 'i' (tick number) or null in case of o label
    1434          * @private
    1435          */
    1436         generateLabelData: function (labelText, tick, tickNumber) {
    1437             var xa, ya, m, fs;
    1438
    1439             // Test if large portions of the label are inside of the canvas
    1440             // This is the last chance to abandon the creation of the label if it is mostly
    1441             // outside of the canvas.
    1442             fs = Type.evaluate(this.visProp.label.fontsize);
    1443             xa = [tick.scrCoords[1], tick.scrCoords[1]];
    1444             ya = [tick.scrCoords[2], tick.scrCoords[2]];
    1445             m = fs === undefined ? 12 : fs;
    1446             m *= 0.5;
    1447             if (!this._isInsideCanvas(xa, ya, m)) {
    1448                 return null;
    1449             }
    1450
    1451             xa = Type.evaluate(this.visProp.label.offset[0]);
    1452             ya = Type.evaluate(this.visProp.label.offset[1]);
    1453
    1454             return {
    1455                 x: tick.usrCoords[1] + xa / this.board.unitX,
    1456                 y: tick.usrCoords[2] + ya / this.board.unitY,
    1457                 t: labelText,
    1458                 i: tickNumber
    1459             };
    1460         },
    1461
    1462         /**
    1463          * Recalculate the tick positions and the labels.
    1464          * @returns {JXG.Ticks}
    1465          */
    1466         update: function () {
    1467             if (this.needsUpdate) {
    1468                 //this.visPropCalc.visible = Type.evaluate(this.visProp.visible);
    1469                 // A canvas with no width or height will create an endless loop, so ignore it
    1470                 if (this.board.canvasWidth !== 0 && this.board.canvasHeight !== 0) {
    1471                     this.calculateTicksCoordinates();
    1472                 }
    1473                 // this.updateVisibility(this.line.visPropCalc.visible);
    1474                 //
    1475                 // for (var i = 0; i < this.labels.length; i++) {
    1476                 //     if (this.labels[i] !== null) {
    1477                 //         this.labels[i].prepareUpdate()
    1478                 //             .updateVisibility(this.line.visPropCalc.visible)
    1479                 //             .updateRenderer();
    1480                 //     }
    1481                 // }
    1482             }
    1483
    1484             return this;
    1485         },
    1486
    1487         /**
    1488          * Uses the boards renderer to update the arc.
    1489          * @returns {JXG.Ticks} Reference to the object.
    1490          */
    1491         updateRenderer: function () {
    1492             if (!this.needsUpdate) {
    1493                 return this;
    1494             }
    1495
    1496             if (this.visPropCalc.visible) {
    1497                 this.board.renderer.updateTicks(this);
    1498             }
    1499             this.updateRendererLabels();
    1500
    1501             this.setDisplayRendNode();
    1502             // if (this.visPropCalc.visible != this.visPropOld.visible) {
    1503             //     this.board.renderer.display(this, this.visPropCalc.visible);
    1504             //     this.visPropOld.visible = this.visPropCalc.visible;
    1505             // }
    1506
    1507             this.needsUpdate = false;
    1508             return this;
    1509         },
    1510
    1511         /**
    1512          * Updates the label elements of the major ticks.
    1513          *
    1514          * @private
    1515          * @returns {JXG.Ticks} Reference to the object.
    1516          */
    1517         updateRendererLabels: function () {
    1518             var i, j, lenData, lenLabels, attr, label, ld, visible;
    1519
    1520             // The number of labels needed
    1521             lenData = this.labelsData.length;
    1522             // The number of labels which already exist
    1523             // The existing labels are stored in this.labels[]
    1524             // The new label positions and label values are stored in this.labelsData[]
    1525             lenLabels = this.labels.length;
    1526
    1527             for (i = 0, j = 0; i < lenData; i++) {
    1528                 if (this.labelsData[i] === null) {
    1529                     // This is a tick without label
    1530                     continue;
    1531                 }
    1532
    1533                 ld = this.labelsData[i];
    1534                 if (j < lenLabels) {
    1535                     // Take an already existing text element
    1536                     label = this.labels[j];
    1537                     label.setText(ld.t);
    1538                     label.setCoords(ld.x, ld.y);
    1539                     j++;
    1540                 } else {
    1541                     // A new text element is needed
    1542                     this.labelCounter += 1;
    1543
    1544                     attr = {
    1545                         isLabel: true,
    1546                         layer: this.board.options.layer.line,
    1547                         highlightStrokeColor: this.board.options.text.strokeColor,
    1548                         highlightStrokeWidth: this.board.options.text.strokeWidth,
    1549                         highlightStrokeOpacity: this.board.options.text.strokeOpacity,
    1550                         priv: this.visProp.priv
    1551                     };
    1552                     attr = Type.deepCopy(attr, this.visProp.label);
    1553                     attr.id = this.id + ld.i + "Label" + this.labelCounter;
    1554
    1555                     label = JXG.createText(this.board, [ld.x, ld.y, ld.t], attr);
    1556                     this.addChild(label);
    1557                     label.setParents(this);
    1558                     label.isDraggable = false;
    1559                     label.dump = false;
    1560                     this.labels.push(label);
    1561                 }
    1562
    1563                 // Look-ahead if the label inherits visibility.
    1564                 // If yes, update label.
    1565                 visible = Type.evaluate(this.visProp.label.visible);
    1566                 if (visible === 'inherit') {
    1567                     visible = this.visPropCalc.visible;
    1568                 }
    1569
    1570                 label.prepareUpdate().updateVisibility(visible).updateRenderer();
    1571
    1572                 label.distanceX = Type.evaluate(this.visProp.label.offset[0]);
    1573                 label.distanceY = Type.evaluate(this.visProp.label.offset[1]);
    1574             }
    1575
    1576             // Hide unused labels
    1577             lenData = j;
    1578             for (j = lenData; j < lenLabels; j++) {
    1579                 this.board.renderer.display(this.labels[j], false);
    1580                 // Tick labels have the attribute "visible: 'inherit'"
    1581                 // This must explicitly set to false, otherwise
    1582                 // this labels would be set to visible in the upcoming
    1583                 // update of the labels.
    1584                 this.labels[j].visProp.visible = this.labels[j].visPropCalc.visible = false;
    1585             }
    1586
    1587             return this;
    1588         },
    1589
    1590         hideElement: function () {
    1591             var i;
    1592
    1593             JXG.deprecated("Element.hideElement()", "Element.setDisplayRendNode()");
    1594
    1595             this.visPropCalc.visible = false;
    1596             this.board.renderer.display(this, false);
    1597             for (i = 0; i < this.labels.length; i++) {
    1598                 if (Type.exists(this.labels[i])) {
    1599                     this.labels[i].hideElement();
    1600                 }
    1601             }
    1602
    1603             return this;
    1604         },
    1605
    1606         showElement: function () {
    1607             var i;
    1608
    1609             JXG.deprecated("Element.showElement()", "Element.setDisplayRendNode()");
    1610
    1611             this.visPropCalc.visible = true;
    1612             this.board.renderer.display(this, false);
    1613
    1614             for (i = 0; i < this.labels.length; i++) {
    1615                 if (Type.exists(this.labels[i])) {
    1616                     this.labels[i].showElement();
    1617                 }
    1618             }
    1619
    1620             return this;
    1621         }
    1622     }
    1623 );
    1624
    1625 /**
    1626  * @class Ticks are used as distance markers on a line or curve.
    1627  * They are mainly used for axis elements and slider elements. Ticks may stretch infinitely
    1628  * or finitely, which can be set with {@link Ticks#majorHeight} and {@link Ticks#minorHeight}.
    1629  * <p>
    1630  * There are the following ways to position the tick lines:
    1631  * <ol>
    1632  *  <li> If an array is given as optional second parameter for the constructor
    1633  * like e.g. <tt>board.create('ticks', [line, [1, 4, 5]])</tt>, then there will be (fixed) ticks at position
    1634  * 1, 4 and 5 of the line.
    1635  *  <li> If there is only one parameter given, like e.g. <tt>board.create('ticks', [line])</tt>, the ticks will be set
    1636  * equidistant across the line element. There are two variants:
    1637  *    <ol type="i">
    1638  *      <li> Setting the attribute <tt>insertTicks:false</tt>: in this case the distance between two major ticks
    1639  *          is determined by the attribute <tt>ticksDistance</tt>. This distance is given in user units.
    1640  *      <li> Setting the attribute <tt>insertTicks:true</tt>: in this case the distance between two major ticks
    1641  *          is set automatically, depending on
    1642  *          <ul>
    1643  *              <li> the size of the board,
    1644  *              <li> the attribute <tt>minTicksDistance</tt>,  which is the minimum distance between two consecutive minor ticks (in pixel).
    1645  *          </ul>
    1646  * The distance between two major ticks is a value of the form
    1647  * <i>a 10<sup>i</sup></i>, where <i>a</i> is one of <i>{1, 2, 5}</i> and
    1648  * the number <i>a 10<sup>i</sup></i> is maximized such that there are approximately
    1649  * 6 major ticks and there are at least "minTicksDistance" pixel between minor ticks.
    1650  * </ol>
    1651  * <p>
    1652  * For arbitrary lines (and not axes) a "zero coordinate" is determined
    1653  * which defines where the first tick is positioned. This zero coordinate
    1654  * can be altered with the attribute <tt>anchor</tt>. Possible values are "left", "middle", "right" or a number.
    1655  * The default value is "left".
    1656  *
    1657  * @pseudo
    1658  * @name Ticks
    1659  * @augments JXG.Ticks
    1660  * @constructor
    1661  * @type JXG.Ticks
    1662  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1663  * @param {JXG.Line|JXG.Curve} line The parents consist of the line or curve the ticks are going to be attached to.
    1664  * @param {Array} [ticks] Optional array of numbers. If given, a fixed number of static ticks is created
    1665  * at these user-supplied positions.
    1666  * <p>
    1667  * Deprecated: Alternatively, a number defining the distance between two major ticks
    1668  * can be specified. However, this is meanwhile ignored. Use attribute <tt>ticksDistance</tt> instead.
    1669  *
    1670  * @example
    1671  * // Add ticks to line 'l1' through 'p1' and 'p2'. The major ticks are
    1672  * // two units apart and 40 px long.
    1673  *   var p1 = board.create('point', [0, 3]);
    1674  *   var p2 = board.create('point', [1, 3]);
    1675  *   var l1 = board.create('line', [p1, p2]);
    1676  *   var t = board.create('ticks', [l1], {
    1677  *      ticksDistance: 2,
    1678  *      majorHeight: 40
    1679  *   });
    1680  * </pre><div class="jxgbox" id="JXGee7f2d68-75fc-4ec0-9931-c76918427e63" style="width: 300px; height: 300px;"></div>
    1681  * <script type="text/javascript">
    1682  * (function () {
    1683  *   var board = JXG.JSXGraph.initBoard('JXGee7f2d68-75fc-4ec0-9931-c76918427e63', {
    1684  *   boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: true});
    1685  *   var p1 = board.create('point', [0, 3]);
    1686  *   var p2 = board.create('point', [1, 3]);
    1687  *   var l1 = board.create('line', [p1, p2]);
    1688  *   var t = board.create('ticks', [l1, 2], {ticksDistance: 2, majorHeight: 40});
    1689  * })();
    1690  * </script><pre>
    1691  * @example
    1692  *  // Create ticks labels as fractions
    1693  * board.create('axis', [[0,1], [1,1]], {
    1694  *     ticks: {
    1695  *         label: {
    1696  *             toFraction: true,
    1697  *             useMathjax: true,
    1698  *             display: 'html',
    1699  *             anchorX: 'middle',
    1700  *             offset: [0, -10]
    1701  *         }
    1702  *     }
    1703  * });
    1704  *
    1705  * </pre><div id="JXG4455acb2-6bf3-4801-8887-d7fcc1e4e1da" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1706  * <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" id="MathJax-script"></script>
    1707  * <script type="text/javascript">
    1708  *     (function() {
    1709  *         var board = JXG.JSXGraph.initBoard('JXG4455acb2-6bf3-4801-8887-d7fcc1e4e1da',
    1710  *             {boundingbox: [-1.2, 2.3, 1.2, -2.3], axis: true, showcopyright: false, shownavigation: false});
    1711  *             board.create('axis', [[0,1], [1,1]], {
    1712  *                 ticks: {
    1713  *                     label: {
    1714  *                         toFraction: true,
    1715  *             useMathjax: true,
    1716  *             display: 'html',
    1717  *             anchorX: 'middle',
    1718  *             offset: [0, -10]
    1719  *                     }
    1720  *                 }
    1721  *             });
    1722  *
    1723  *     })();
    1724  *
    1725  * </script><pre>
    1726  *
    1727  * @example
    1728  */
    1729 JXG.createTicks = function (board, parents, attributes) {
    1730     var el,
    1731         dist,
    1732         attr = Type.copyAttributes(attributes, board.options, "ticks");
    1733
    1734     if (parents.length < 2) {
    1735         dist = attr.ticksdistance; // Will be ignored anyhow and attr.ticksDistance will be used instead
    1736     } else {
    1737         dist = parents[1];
    1738     }
    1739
    1740     if (
    1741         parents[0].elementClass === Const.OBJECT_CLASS_LINE ||
    1742         parents[0].elementClass === Const.OBJECT_CLASS_CURVE
    1743     ) {
    1744         el = new JXG.Ticks(parents[0], dist, attr);
    1745     } else {
    1746         throw new Error(
    1747             "JSXGraph: Can't create Ticks with parent types '" + typeof parents[0] + "'."
    1748         );
    1749     }
    1750
    1751     // deprecated
    1752     if (Type.isFunction(attr.generatelabelvalue)) {
    1753         el.generateLabelText = attr.generatelabelvalue;
    1754     }
    1755     if (Type.isFunction(attr.generatelabeltext)) {
    1756         el.generateLabelText = attr.generatelabeltext;
    1757     }
    1758
    1759     el.setParents(parents[0]);
    1760     el.isDraggable = true;
    1761     el.fullUpdate(parents[0].visPropCalc.visible);
    1762
    1763     return el;
    1764 };
    1765
    1766 /**
    1767  * @class Hatches can be used to mark congruent lines or curves.
    1768  * @pseudo
    1769  * @name Hatch
    1770  * @augments JXG.Ticks
    1771  * @constructor
    1772  * @type JXG.Ticks
    1773  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1774  * @param {JXG.Line|JXG.curve} line The line or curve the hatch marks are going to be attached to.
    1775  * @param {Number} numberofhashes Number of dashes. The distance of the hashes can be controlled with the attribute ticksDistance.
    1776  * @example
    1777  * // Create an axis providing two coords pairs.
    1778  *   var p1 = board.create('point', [0, 3]);
    1779  *   var p2 = board.create('point', [1, 3]);
    1780  *   var l1 = board.create('line', [p1, p2]);
    1781  *   var t = board.create('hatch', [l1, 3]);
    1782  * </pre><div class="jxgbox" id="JXG4a20af06-4395-451c-b7d1-002757cf01be" style="width: 300px; height: 300px;"></div>
    1783  * <script type="text/javascript">
    1784  * (function () {
    1785  *   var board = JXG.JSXGraph.initBoard('JXG4a20af06-4395-451c-b7d1-002757cf01be', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false});
    1786  *   var p1 = board.create('point', [0, 3]);
    1787  *   var p2 = board.create('point', [1, 3]);
    1788  *   var l1 = board.create('line', [p1, p2]);
    1789  *   var t = board.create('hatch', [l1, 3]);
    1790  * })();
    1791  * </script><pre>
    1792  *
    1793  * @example
    1794  * // Alter the position of the hatch
    1795  *
    1796  * var p = board.create('point', [-5, 0]);
    1797  * var q = board.create('point', [5, 0]);
    1798  * var li = board.create('line', [p, q]);
    1799  * var h = board.create('hatch', [li, 2], {anchor: 0.2, ticksDistance:0.4});
    1800  *
    1801  * </pre><div id="JXG05d720ee-99c9-11e6-a9c7-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1802  * <script type="text/javascript">
    1803  *     (function() {
    1804  *         var board = JXG.JSXGraph.initBoard('JXG05d720ee-99c9-11e6-a9c7-901b0e1b8723',
    1805  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    1806  *
    1807  *     var p = board.create('point', [-5, 0]);
    1808  *     var q = board.create('point', [5, 0]);
    1809  *     var li = board.create('line', [p, q]);
    1810  *     var h = board.create('hatch', [li, 2], {anchor: 0.2, ticksDistance:0.4});
    1811  *
    1812  *     })();
    1813  *
    1814  * </script><pre>
    1815  *
    1816  * @example
    1817  * // Alternative hatch faces
    1818  *
    1819  * var li = board.create('line', [[-6,0], [6,3]]);
    1820  * var h1 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'|'});
    1821  * var h2 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'>', anchor: 0.3});
    1822  * var h3 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'<', anchor: 0.7});
    1823  *
    1824  * </pre><div id="JXG974f7e89-eac8-4187-9aa3-fb8068e8384b" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1825  * <script type="text/javascript">
    1826  *     (function() {
    1827  *         var board = JXG.JSXGraph.initBoard('JXG974f7e89-eac8-4187-9aa3-fb8068e8384b',
    1828  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    1829  *     // Alternative hatch faces
    1830  *
    1831  *     var li = board.create('line', [[-6,0], [6,3]]);
    1832  *     var h1 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'|'});
    1833  *     var h2 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'>', anchor: 0.3});
    1834  *     var h3 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'<', anchor: 0.7});
    1835  *
    1836  *     })();
    1837  *
    1838  * </script><pre>
    1839  *
    1840  */
    1841 JXG.createHatchmark = function (board, parents, attributes) {
    1842     var num, i, base, width, totalwidth, el,
    1843         pos = [],
    1844         attr = Type.copyAttributes(attributes, board.options, 'hatch');
    1845
    1846     if (
    1847         (parents[0].elementClass !== Const.OBJECT_CLASS_LINE &&
    1848             parents[0].elementClass !== Const.OBJECT_CLASS_CURVE) ||
    1849         typeof parents[1] !== "number"
    1850     ) {
    1851         throw new Error(
    1852             "JSXGraph: Can't create Hatch mark with parent types '" +
    1853             typeof parents[0] +
    1854             "' and '" +
    1855             typeof parents[1] +
    1856             " and ''" +
    1857             typeof parents[2] +
    1858             "'."
    1859         );
    1860     }
    1861
    1862     num = parents[1];
    1863     width = attr.ticksdistance;
    1864     totalwidth = (num - 1) * width;
    1865     base = -totalwidth * 0.5;
    1866
    1867     for (i = 0; i < num; i++) {
    1868         pos[i] = base + i * width;
    1869     }
    1870
    1871     el = board.create('ticks', [parents[0], pos], attr);
    1872     el.elType = 'hatch';
    1873     parents[0].inherits.push(el);
    1874
    1875     return el;
    1876 };
    1877
    1878 JXG.registerElement("ticks", JXG.createTicks);
    1879 JXG.registerElement("hash", JXG.createHatchmark);
    1880 JXG.registerElement("hatch", JXG.createHatchmark);
    1881
    1882 export default JXG.Ticks;
    1883 // export default {
    1884 //     Ticks: JXG.Ticks,
    1885 //     createTicks: JXG.createTicks,
    1886 //     createHashmark: JXG.createHatchmark,
    1887 //     createHatchmark: JXG.createHatchmark
    1888 // };
    1889