      1 /*
      2     Copyright 2008-2018
      3         Matthias Ehmann,
      4         Michael Gerhaeuser,
      5         Carsten Miller,
      6         Bianca Valentin,
      7         Alfred Wassermann,
      8         Peter Wilfahrt
      9
     10     This file is part of JSXGraph.
     11
     12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
     13
     14     You can redistribute it and/or modify it under the terms of the
     15
     16       * GNU Lesser General Public License as published by
     17         the Free Software Foundation, either version 3 of the License, or
     18         (at your option) any later version
     19       OR
     20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
     21
     22     JSXGraph is distributed in the hope that it will be useful,
     23     but WITHOUT ANY WARRANTY; without even the implied warranty of
     24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     25     GNU Lesser General Public License for more details.
     26
     27     You should have received a copy of the GNU Lesser General Public License and
     28     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
     29     and <http://opensource.org/licenses/MIT/>.
     30  */
     31
     32
     33 /*global JXG: true, define: true*/
     34 /*jslint nomen: true, plusplus: true*/
     35
     36 /* depends:
     37  jxg
     38  math/math
     39  math/geometry
     40  base/constants
     41  base/element
     42  base/coords
     43  utils/type
     44   elements:
     45    text
     46  */
     47
     48 /**
     49  * @fileoverview In this file the geometry object Ticks is defined. Ticks provides
     50  * methods for creation and management of ticks on an axis.
     51  * @author graphjs
     52  * @version 0.1
     53  */
     54
     55 define([
     56     'jxg', 'math/math', 'math/geometry', 'base/constants', 'base/element', 'base/coords', 'utils/type', 'base/text'
     57 ], function (JXG, Mat, Geometry, Const, GeometryElement, Coords, Type, Text) {
     58
     59     "use strict";
     60
     61     /**
     62      * Creates ticks for an axis.
     63      * @class Ticks provides methods for creation and management
     64      * of ticks on an axis.
     65      * @param {JXG.Line} line Reference to the axis the ticks are drawn on.
     66      * @param {Number|Array} ticks Number defining the distance between two major ticks or an array defining static ticks.
     67      * @param {Object} attributes Properties
     68      * @see JXG.Line#addTicks
     69      * @constructor
     70      * @extends JXG.GeometryElement
     71      */
     72     JXG.Ticks = function (line, ticks, attributes) {
     73         this.constructor(line.board, attributes, Const.OBJECT_TYPE_TICKS, Const.OBJECT_CLASS_OTHER);
     74
     75         /**
     76          * The line the ticks belong to.
     77          * @type JXG.Line
     78          */
     79         this.line = line;
     80
     81         /**
     82          * The board the ticks line is drawn on.
     83          * @type JXG.Board
     84          */
     85         this.board = this.line.board;
     86
     87         /**
     88          * A function calculating ticks delta depending on the ticks number.
     89          * @type Function
     90          */
     91         this.ticksFunction = null;
     92
     93         /**
     94          * Array of fixed ticks.
     95          * @type Array
     96          */
     97         this.fixedTicks = null;
     98
     99         /**
    100          * Equidistant ticks. Distance is defined by ticksFunction
    101          * @type Boolean
    102          */
    103         this.equidistant = false;
    104
    105         this.labelsData = [];
    106
    107         if (Type.isFunction(ticks)) {
    108             this.ticksFunction = ticks;
    109             throw new Error("Function arguments are no longer supported.");
    110         }
    111
    112         if (Type.isArray(ticks)) {
    113             this.fixedTicks = ticks;
    114         } else {
    115             if (Math.abs(ticks) < Mat.eps || ticks < 0) {
    116                 ticks = attributes.defaultdistance;
    117             }
    118
    119             /*
    120              * Ticks function:
    121              * determines the distance (in user units) of two major ticks
    122              */
    123             this.ticksFunction = this.makeTicksFunction(ticks);
    124
    125             this.equidistant = true;
    126         }
    127
    128         /**
    129          * Least distance between two ticks, measured in pixels.
    130          * @type int
    131          */
    132         this.minTicksDistance = attributes.minticksdistance;
    133
    134         /**
    135          * Stores the ticks coordinates
    136          * @type {Array}
    137          */
    138         this.ticks = [];
    139
    140         /**
    141          * Distance between two major ticks in user coordinates
    142          * @type {Number}
    143          */
    144         this.ticksDelta = 1;
    145
    146         /**
    147          * Array where the labels are saved. There is an array element for every tick,
    148          * even for minor ticks which don't have labels. In this case the array element
    149          * contains just <tt>null</tt>.
    150          * @type Array
    151          */
    152         this.labels = [];
    153
    154         /**
    155          * A list of labels which have to be displayed in updateRenderer.
    156          * @type {Array}
    157          */
    158         this.labelData = [];
    159
    160         /**
    161          * To ensure the uniqueness of label ids this counter is used.
    162          * @type {number}
    163          */
    164         this.labelCounter = 0;
    165
    166         this.id = this.line.addTicks(this);
    167         this.elType = 'ticks';
    168         this.inherits.push(this.labels);
    169         this.board.setId(this, 'Ti');
    170     };
    171
    172     JXG.Ticks.prototype = new GeometryElement();
    173
    174     JXG.extend(JXG.Ticks.prototype, /** @lends JXG.Ticks.prototype */ {
    175
    176         /**
    177          * Ticks function:
    178          * determines the distance (in user units) of two major ticks.
    179          * See above in constructor and in @see JXG.GeometryElement#setAttribute
    180          *
    181          * @private
    182          * @param {Number} ticks Distance between two major ticks
    183          * @returns {Function} returns method ticksFunction
    184          */
    185         makeTicksFunction: function (ticks) {
    186             return function () {
    187                 var delta, b, dist;
    188
    189                 if (Type.evaluate(this.visProp.insertticks)) {
    190                     b = this.getLowerAndUpperBounds(this.getZeroCoordinates(), 'ticksdistance');
    191                     dist = b.upper - b.lower;
    192                     delta = Math.pow(10, Math.floor(Math.log(0.6 * dist) / Math.LN10));
    193                     if (dist <= 6 * delta) {
    194                         delta *= 0.5;
    195                     }
    196                     return delta;
    197                 }
    198
    199                 // upto 0.99.1:
    200                 return ticks;
    201             };
    202         },
    203
    204         /**
    205          * Checks whether (x,y) is near the line.
    206          * @param {Number} x Coordinate in x direction, screen coordinates.
    207          * @param {Number} y Coordinate in y direction, screen coordinates.
    208          * @returns {Boolean} True if (x,y) is near the line, False otherwise.
    209          */
    210         hasPoint: function (x, y) {
    211             var i, t,
    212                 len = (this.ticks && this.ticks.length) || 0,
    213                 r = this.board.options.precision.hasPoint +
    214                         Type.evaluate(this.visProp.strokewidth) * 0.5;
    215
    216             if (!Type.evaluate(this.line.visProp.scalable)) {
    217                 return false;
    218             }
    219
    220             // Ignore non-axes and axes that are not horizontal or vertical
    221             if (this.line.stdform[1] !== 0 && this.line.stdform[2] !== 0 && this.line.type !== Const.OBJECT_TYPE_AXIS) {
    222                 return false;
    223             }
    224
    225             for (i = 0; i < len; i++) {
    226                 t = this.ticks[i];
    227
    228                 // Skip minor ticks
    229                 if (t[2]) {
    230                     // Ignore ticks at zero
    231                     if (!((this.line.stdform[1] === 0 && Math.abs(t[0][0] - this.line.point1.coords.scrCoords[1]) < Mat.eps) ||
    232                             (this.line.stdform[2] === 0 && Math.abs(t[1][0] - this.line.point1.coords.scrCoords[2]) < Mat.eps))) {
    233                         // tick length is not zero, ie. at least one pixel
    234                         if (Math.abs(t[0][0] - t[0][1]) >= 1 || Math.abs(t[1][0] - t[1][1]) >= 1) {
    235                             if (this.line.stdform[1] === 0) {
    236                                 // Allow dragging near axes only.
    237                                 if (Math.abs(y - (t[1][0] + t[1][1]) * 0.5) < 2 * r && t[0][0] - r < x && x < t[0][1] + r) {
    238                                     return true;
    239                                 }
    240                             } else if (this.line.stdform[2] === 0) {
    241                                 if (Math.abs(x - (t[0][0] + t[0][1]) * 0.5) < 2 * r && t[1][0] - r < y && y < t[1][1] + r) {
    242                                     return true;
    243                                 }
    244                             }
    245                         }
    246                     }
    247                 }
    248             }
    249
    250             return false;
    251         },
    252
    253         /**
    254          * Sets x and y coordinate of the tick.
    255          * @param {number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
    256          * @param {Array} coords coordinates in screen/user units
    257          * @param {Array} oldcoords previous coordinates in screen/user units
    258          * @returns {JXG.Ticks} this element
    259          */
    260         setPositionDirectly: function (method, coords, oldcoords) {
    261             var dx, dy,
    262                 c = new Coords(method, coords, this.board),
    263                 oldc = new Coords(method, oldcoords, this.board),
    264                 bb = this.board.getBoundingBox();
    265
    266             if (!Type.evaluate(this.line.visProp.scalable)) {
    267                 return this;
    268             }
    269
    270             // horizontal line
    271             if (Math.abs(this.line.stdform[1]) < Mat.eps && Math.abs(c.usrCoords[1] * oldc.usrCoords[1]) > Mat.eps) {
    272                 dx = oldc.usrCoords[1] / c.usrCoords[1];
    273                 bb[0] *= dx;
    274                 bb[2] *= dx;
    275                 this.board.setBoundingBox(bb, false);
    276             // vertical line
    277             } else if (Math.abs(this.line.stdform[2]) < Mat.eps && Math.abs(c.usrCoords[2] * oldc.usrCoords[2]) > Mat.eps) {
    278                 dy = oldc.usrCoords[2] / c.usrCoords[2];
    279                 bb[3] *= dy;
    280                 bb[1] *= dy;
    281                 this.board.setBoundingBox(bb, false);
    282             }
    283
    284             return this;
    285         },
    286
    287         /**
    288          * (Re-)calculates the ticks coordinates.
    289          * @private
    290          */
    291         calculateTicksCoordinates: function () {
    292             var coordsZero, bounds;
    293
    294             // Calculate Ticks width and height in Screen and User Coordinates
    295             this.setTicksSizeVariables();
    296             // If the parent line is not finite, we can stop here.
    297             if (Math.abs(this.dx) < Mat.eps &&
    298                 Math.abs(this.dy) < Mat.eps) {
    299                 return;
    300             }
    301
    302             // Get Zero
    303             coordsZero = this.getZeroCoordinates();
    304
    305             // Calculate lower bound and upper bound limits based on distance between p1 and centre and p2 and centre
    306             bounds = this.getLowerAndUpperBounds(coordsZero);
    307
    308             // Clean up
    309             this.ticks = [];
    310             this.labelsData = [];
    311             // Create Ticks Coordinates and Labels
    312             if (this.equidistant) {
    313                 this.generateEquidistantTicks(coordsZero, bounds);
    314             } else {
    315                 this.generateFixedTicks(coordsZero, bounds);
    316             }
    317
    318             return this;
    319         },
    320
    321         /**
    322          * Sets the variables used to set the height and slope of each tick.
    323          *
    324          * @private
    325          */
    326         setTicksSizeVariables: function () {
    327             var d,
    328                 distMaj = Type.evaluate(this.visProp.majorheight) * 0.5,
    329                 distMin = Type.evaluate(this.visProp.minorheight) * 0.5;
    330
    331             // ticks width and height in screen units
    332             this.dxMaj = this.line.stdform[1];
    333             this.dyMaj = this.line.stdform[2];
    334             this.dxMin = this.dxMaj;
    335             this.dyMin = this.dyMaj;
    336
    337             // ticks width and height in user units
    338             this.dx = this.dxMaj;
    339             this.dy = this.dyMaj;
    340
    341             // After this, the length of the vector (dxMaj, dyMaj) in screen coordinates is equal to distMaj pixel.
    342             d = Math.sqrt(
    343                 this.dxMaj * this.dxMaj * this.board.unitX * this.board.unitX +
    344                     this.dyMaj * this.dyMaj * this.board.unitY * this.board.unitY
    345             );
    346             this.dxMaj *= distMaj / d * this.board.unitX;
    347             this.dyMaj *= distMaj / d * this.board.unitY;
    348             this.dxMin *= distMin / d * this.board.unitX;
    349             this.dyMin *= distMin / d * this.board.unitY;
    350
    351             // Grid-like ticks?
    352             this.minStyle= (Type.evaluate(this.visProp.minorheight) < 0) ? 'infinite' : 'finite';
    353             this.majStyle= (Type.evaluate(this.visProp.majorheight) < 0) ? 'infinite' : 'finite';
    354         },
    355
    356         /**
    357          * Returns the coordinates of the point zero of the line.
    358          *
    359          * If the line is an {@link Axis}, the coordinates of the projection of the board's zero point is returned
    360          *
    361          * Otherwise, the coordinates of the point that acts as zero are established depending on the value of {@link JXG.Ticks#anchor}
    362          *
    363          * @returns {JXG.Coords} Coords object for the Zero point on the line
    364          * @private
    365          */
    366         getZeroCoordinates: function () {
    367             var c1x, c1y, c1z, c2x, c2y, c2z,
    368                 ev_a = Type.evaluate(this.visProp.anchor);
    369
    370             if (this.line.type === Const.OBJECT_TYPE_AXIS) {
    371                 return Geometry.projectPointToLine({
    372                     coords: {
    373                         usrCoords: [1, 0, 0]
    374                     }
    375                 }, this.line, this.board);
    376             }
    377
    378             c1z = this.line.point1.coords.usrCoords[0];
    379             c1x = this.line.point1.coords.usrCoords[1];
    380             c1y = this.line.point1.coords.usrCoords[2];
    381             c2z = this.line.point2.coords.usrCoords[0];
    382             c2x = this.line.point2.coords.usrCoords[1];
    383             c2y = this.line.point2.coords.usrCoords[2];
    384
    385             if (ev_a === 'right') {
    386                 return this.line.point2.coords;
    387             } else if (ev_a === 'middle') {
    388                 return new Coords(Const.COORDS_BY_USER, [
    389                     (c1z + c2z) * 0.5,
    390                     (c1x + c2x) * 0.5,
    391                     (c1y + c2y) * 0.5
    392                 ], this.board);
    393             } else if (Type.isNumber(ev_a)) {
    394                 return new Coords(Const.COORDS_BY_USER, [
    395                     c1z + (c2z - c1z) * ev_a,
    396                     c1x + (c2x - c1x) * ev_a,
    397                     c1y + (c2y - c1y) * ev_a
    398                 ], this.board);
    399             }
    400
    401             return this.line.point1.coords;
    402         },
    403
    404         /**
    405          * Calculate the lower and upper bounds for tick rendering
    406          * If {@link JXG.Ticks#includeBoundaries} is false, the boundaries will exclude point1 and point2
    407          *
    408          * @param  {JXG.Coords} coordsZero
    409          * @returns {String} type  (Optional) If type=='ticksdistance' the bounds are
    410          *                         the intersection of the line with the bounding box of the board.
    411          *                         Otherwise it is the projection of the corners of the bounding box
    412          *                         to the line. The first case i s needed to automatically
    413          *                         generate ticks. The second case is for drawing of the ticks.
    414          * @returns {Object}     contains the lower and upper bounds
    415          *
    416          * @private
    417          */
    418         getLowerAndUpperBounds: function (coordsZero, type) {
    419             var lowerBound, upperBound,
    420                 // The line's defining points that will be adjusted to be within the board limits
    421                 point1 = new Coords(Const.COORDS_BY_USER, this.line.point1.coords.usrCoords, this.board),
    422                 point2 = new Coords(Const.COORDS_BY_USER, this.line.point2.coords.usrCoords, this.board),
    423                 // Are the original defining points within the board?
    424                 isPoint1inBoard = (Math.abs(point1.usrCoords[0]) >= Mat.eps &&
    425                     point1.scrCoords[1] >= 0.0 && point1.scrCoords[1] <= this.board.canvasWidth &&
    426                     point1.scrCoords[2] >= 0.0 && point1.scrCoords[2] <= this.board.canvasHeight),
    427                 isPoint2inBoard = (Math.abs(point2.usrCoords[0]) >= Mat.eps &&
    428                     point2.scrCoords[1] >= 0.0 && point2.scrCoords[1] <= this.board.canvasWidth &&
    429                     point2.scrCoords[2] >= 0.0 && point2.scrCoords[2] <= this.board.canvasHeight),
    430                 // We use the distance from zero to P1 and P2 to establish lower and higher points
    431                 dZeroPoint1, dZeroPoint2,
    432                 ev_sf = Type.evaluate(this.line.visProp.straightfirst),
    433                 ev_sl = Type.evaluate(this.line.visProp.straightlast),
    434                 ev_i = Type.evaluate(this.visProp.includeboundaries),
    435                 obj;
    436
    437             // Adjust line limit points to be within the board
    438             if (Type.exists(type) || type === 'tickdistance') {
    439                 // The good old calcStraight is needed for determining the distance between major ticks.
    440                 // Here, only the visual area is of importance
    441                 Geometry.calcStraight(this.line, point1, point2, Type.evaluate(this.line.visProp.margin));
    442             } else {
    443                 // This function projects the corners of the board to the line.
    444                 // This is important for diagonal lines with infinite tick lines.
    445                 Geometry.calcLineDelimitingPoints(this.line, point1, point2);
    446             }
    447             // Shorten ticks bounds such that ticks are not through arrow heads
    448             obj = this.board.renderer.getPositionArrowHead(this.line, point1, point2,
    449                         Type.evaluate(this.line.visProp.strokewidth));
    450             point1.setCoordinates(Const.COORDS_BY_SCREEN, [
    451                     point1.scrCoords[1] - obj.d1x,
    452                     point1.scrCoords[2] - obj.d1y
    453                 ]);
    454             point2.setCoordinates(Const.COORDS_BY_SCREEN, [
    455                     point2.scrCoords[1] - obj.d2x,
    456                     point2.scrCoords[2] - obj.d2y
    457                 ]);
    458
    459             // Calculate distance from Zero to P1 and to P2
    460             dZeroPoint1 = this.getDistanceFromZero(coordsZero, point1);
    461             dZeroPoint2 = this.getDistanceFromZero(coordsZero, point2);
    462
    463             // We have to establish if the direction is P1->P2 or P2->P1 to set the lower and upper
    464             // boundaries appropriately. As the distances contain also a sign to indicate direction,
    465             // we can compare dZeroPoint1 and dZeroPoint2 to establish the line direction
    466             if (dZeroPoint1 < dZeroPoint2) { // Line goes P1->P2
    467                 lowerBound = dZeroPoint1;
    468                 if (!ev_sf && isPoint1inBoard && !ev_i) {
    469                     lowerBound += Mat.eps;
    470                 }
    471                 upperBound = dZeroPoint2;
    472                 if (!ev_sl && isPoint2inBoard && !ev_i) {
    473                     upperBound -= Mat.eps;
    474                 }
    475             } else if (dZeroPoint2 < dZeroPoint1) { // Line goes P2->P1
    476                 lowerBound = dZeroPoint2;
    477                 if (!ev_sl && isPoint2inBoard && !ev_i) {
    478                     lowerBound += Mat.eps;
    479                 }
    480                 upperBound = dZeroPoint1;
    481                 if (!ev_sf && isPoint1inBoard && !ev_i) {
    482                     upperBound -= Mat.eps;
    483                 }
    484             } else { // P1 = P2 = Zero, we can't do a thing
    485                 lowerBound = 0;
    486                 upperBound = 0;
    487             }
    488
    489             return {
    490                 lower: lowerBound,
    491                 upper: upperBound
    492             };
    493         },
    494
    495         /**
    496          * Calculates the distance in user coordinates from zero to a given point including its sign
    497          *
    498          * @param  {JXG.Coords} zero  coordinates of the point considered zero
    499          * @param  {JXG.Coords} point coordinates of the point to find out the distance
    500          * @returns {Number}           distance between zero and point, including its sign
    501          * @private
    502          */
    503         getDistanceFromZero: function (zero, point) {
    504             var eps = Mat.eps,
    505                 distance = zero.distance(Const.COORDS_BY_USER, point);
    506
    507             // Establish sign
    508             if (this.line.type === Const.OBJECT_TYPE_AXIS) {
    509                 if ((Mat.relDif(zero.usrCoords[1], point.usrCoords[1]) > eps &&
    510                         zero.usrCoords[1] - point.usrCoords[1] > eps) ||
    511                     (Mat.relDif(zero.usrCoords[2], point.usrCoords[2]) > eps &&
    512                         zero.usrCoords[2] - point.usrCoords[2] > eps)) {
    513
    514                     distance *= -1;
    515                 }
    516             } else if (Type.evaluate(this.visProp.anchor) === 'right') {
    517                 if (Geometry.isSameDirection(zero, this.line.point1.coords, point)) {
    518                     distance *= -1;
    519                 }
    520             } else {
    521                 if (!Geometry.isSameDirection(zero, this.line.point2.coords, point)) {
    522                     distance *= -1;
    523                 }
    524             }
    525             return distance;
    526         },
    527
    528         /**
    529          * Creates ticks coordinates and labels automatically.
    530          * The frequency of ticks is affected by the values of {@link JXG.Ticks#insertTicks} and {@link JXG.Ticks#ticksDistance}
    531          *
    532          * @param  {JXG.Coords} coordsZero coordinates of the point considered zero
    533          * @param  {Object}     bounds     contains the lower and upper boundaries for ticks placement
    534          * @private
    535          */
    536         generateEquidistantTicks: function (coordsZero, bounds) {
    537             var tickPosition,
    538                 // Calculate X and Y distance between two major ticks
    539                 deltas = this.getXandYdeltas(),
    540                 // Distance between two major ticks in user coordinates
    541                 ticksDelta = (this.equidistant ? this.ticksFunction(1) : this.ticksDelta),
    542                 ev_it = Type.evaluate(this.visProp.insertticks),
    543                 ev_mt = Type.evaluate(this.visProp.minorticks);
    544
    545             // adjust ticks distance
    546             ticksDelta *= Type.evaluate(this.visProp.scale);
    547             if (ev_it && this.minTicksDistance > Mat.eps) {
    548                 ticksDelta = this.adjustTickDistance(ticksDelta, coordsZero, deltas);
    549                 ticksDelta /= (ev_mt + 1);
    550             } else if (!ev_it) {
    551                 ticksDelta /= (ev_mt + 1);
    552             }
    553             this.ticksDelta = ticksDelta;
    554
    555             if (ticksDelta < Mat.eps) {
    556                 return;
    557             }
    558
    559             // Position ticks from zero to the positive side while not reaching the upper boundary
    560             tickPosition = 0;
    561             if (!Type.evaluate(this.visProp.drawzero)) {
    562                 tickPosition = ticksDelta;
    563             }
    564             while (tickPosition <= bounds.upper) {
    565                 // Only draw ticks when we are within bounds, ignore case where  tickPosition < lower < upper
    566                 if (tickPosition >= bounds.lower) {
    567                     this.processTickPosition(coordsZero, tickPosition, ticksDelta, deltas);
    568                 }
    569                 tickPosition += ticksDelta;
    570             }
    571
    572             // Position ticks from zero (not inclusive) to the negative side while not reaching the lower boundary
    573             tickPosition = -ticksDelta;
    574             while (tickPosition >= bounds.lower) {
    575                 // Only draw ticks when we are within bounds, ignore case where lower < upper < tickPosition
    576                 if (tickPosition <= bounds.upper) {
    577                     this.processTickPosition(coordsZero, tickPosition, ticksDelta, deltas);
    578                 }
    579                 tickPosition -= ticksDelta;
    580             }
    581         },
    582
    583         /**
    584          * Auxiliary method used by {@link JXG.Ticks#generateEquidistantTicks} to adjust the
    585          * distance between two ticks depending on {@link JXG.Ticks#minTicksDistance} value
    586          *
    587          * @param  {Number}     ticksDelta  distance between two major ticks in user coordinates
    588          * @param  {JXG.Coords} coordsZero  coordinates of the point considered zero
    589          * @param  {Object}     deltas      x and y distance in pixel between two user units
    590          * @param  {Object}     bounds      upper and lower bound of the tick positions in user units.
    591          * @private
    592          */
    593         adjustTickDistance: function (ticksDelta, coordsZero, deltas) {
    594             var nx, ny, bounds,
    595                 distScr,
    596                 sgn = 1,
    597                 ev_minti = Type.evaluate(this.visProp.minorticks);
    598
    599             bounds = this.getLowerAndUpperBounds(coordsZero, 'ticksdistance');
    600             nx = coordsZero.usrCoords[1] + deltas.x * ticksDelta;
    601             ny = coordsZero.usrCoords[2] + deltas.y * ticksDelta;
    602             distScr = coordsZero.distance(Const.COORDS_BY_SCREEN, new Coords(Const.COORDS_BY_USER, [nx, ny], this.board));
    603             while (distScr / (ev_minti + 1) < this.minTicksDistance) {
    604                 if (sgn === 1) {
    605                     ticksDelta *= 2;
    606                 } else {
    607                     ticksDelta *= 5;
    608                 }
    609                 sgn *= -1;
    610
    611                 nx = coordsZero.usrCoords[1] + deltas.x * ticksDelta;
    612                 ny = coordsZero.usrCoords[2] + deltas.y * ticksDelta;
    613                 distScr = coordsZero.distance(Const.COORDS_BY_SCREEN, new Coords(Const.COORDS_BY_USER, [nx, ny], this.board));
    614             }
    615             return ticksDelta;
    616         },
    617
    618         /**
    619          * Auxiliary method used by {@link JXG.Ticks#generateEquidistantTicks} to create a tick
    620          * in the line at the given tickPosition.
    621          *
    622          * @param  {JXG.Coords} coordsZero    coordinates of the point considered zero
    623          * @param  {Number}     tickPosition  current tick position relative to zero
    624          * @param  {Number}     ticksDelta    distance between two major ticks in user coordinates
    625          * @param  {Object}     deltas      x and y distance between two major ticks
    626          * @private
    627          */
    628         processTickPosition: function (coordsZero, tickPosition, ticksDelta, deltas) {
    629             var x, y, tickCoords, ti;
    630
    631             // Calculates tick coordinates
    632             x = coordsZero.usrCoords[1] + tickPosition * deltas.x;
    633             y = coordsZero.usrCoords[2] + tickPosition * deltas.y;
    634             tickCoords = new Coords(Const.COORDS_BY_USER, [x, y], this.board);
    635
    636             // Test if tick is a major tick.
    637             // This is the case if tickPosition/ticksDelta is
    638             // a multiple of the number of minorticks+1
    639             tickCoords.major = Math.round(tickPosition / ticksDelta) % (Type.evaluate(this.visProp.minorticks) + 1) === 0;
    640
    641             // Compute the start position and the end position of a tick.
    642             // If both positions are out of the canvas, ti is empty.
    643             ti = this.tickEndings(tickCoords, tickCoords.major);
    644             if (ti.length === 3) {
    645                 this.ticks.push(ti);
    646                 if (tickCoords.major && Type.evaluate(this.visProp.drawlabels)) {
    647                     this.labelsData.push(
    648                         this.generateLabelData(
    649                             this.generateLabelText(tickCoords, coordsZero),
    650                             tickCoords,
    651                             this.ticks.length
    652                         )
    653                     );
    654                 } else {
    655                     this.labelsData.push(null);
    656                 }
    657             }
    658         },
    659
    660         /**
    661          * Creates ticks coordinates and labels based on {@link JXG.Ticks#fixedTicks} and {@link JXG.Ticks#labels}.
    662          *
    663          * @param  {JXG.Coords} coordsZero Coordinates of the point considered zero
    664          * @param  {Object}     bounds     contains the lower and upper boundaries for ticks placement
    665          * @private
    666          */
    667         generateFixedTicks: function (coordsZero, bounds) {
    668             var tickCoords, labelText, i, ti,
    669                 x, y,
    670                 hasLabelOverrides = Type.isArray(this.visProp.labels),
    671                 // Calculate X and Y distance between two major points in the line
    672                 deltas = this.getXandYdeltas(),
    673                 ev_dl = Type.evaluate(this.visProp.drawlabels);
    674
    675             for (i = 0; i < this.fixedTicks.length; i++) {
    676                 x = coordsZero.usrCoords[1] + this.fixedTicks[i] * deltas.x;
    677                 y = coordsZero.usrCoords[2] + this.fixedTicks[i] * deltas.y;
    678                 tickCoords = new Coords(Const.COORDS_BY_USER, [x, y], this.board);
    679
    680                 // Compute the start position and the end position of a tick.
    681                 // If tick is out of the canvas, ti is empty.
    682                 ti = this.tickEndings(tickCoords, true);
    683                 if (ti.length === 3 && this.fixedTicks[i] >= bounds.lower &&
    684                     this.fixedTicks[i] <= bounds.upper) {
    685                     this.ticks.push(ti);
    686
    687                     if (ev_dl &&
    688                             (hasLabelOverrides || Type.exists(this.visProp.labels[i]))) {
    689                         labelText = hasLabelOverrides ?
    690                                         Type.evaluate(this.visProp.labels[i]) : this.fixedTicks[i];
    691                         this.labelsData.push(
    692                             this.generateLabelData(
    693                                 this.generateLabelText(tickCoords, coordsZero, labelText),
    694                                 tickCoords,
    695                                 i
    696                             )
    697                         );
    698                     } else {
    699                         this.labelsData.push(null);
    700                     }
    701                 }
    702             }
    703         },
    704
    705         /**
    706          * Calculates the x and y distance in pixel between two units in user space.
    707          *
    708          * @returns {Object}
    709          * @private
    710          */
    711         getXandYdeltas: function () {
    712             var
    713                 // Auxiliary points to store the start and end of the line according to its direction
    714                 point1UsrCoords, point2UsrCoords,
    715                 distP1P2 = this.line.point1.Dist(this.line.point2);
    716
    717             if (this.line.type === Const.OBJECT_TYPE_AXIS) {
    718                 // When line is an Axis, direction depends on Board Coordinates system
    719
    720                 // assume line.point1 and line.point2 are in correct order
    721                 point1UsrCoords = this.line.point1.coords.usrCoords;
    722                 point2UsrCoords = this.line.point2.coords.usrCoords;
    723
    724                 // Check if direction is incorrect, then swap
    725                 if (point1UsrCoords[1] > point2UsrCoords[1] ||
    726                         (Math.abs(point1UsrCoords[1] - point2UsrCoords[1]) < Mat.eps &&
    727                         point1UsrCoords[2] > point2UsrCoords[2])) {
    728                     point1UsrCoords = this.line.point2.coords.usrCoords;
    729                     point2UsrCoords = this.line.point1.coords.usrCoords;
    730                 }
    731             } else {
    732                 // line direction is always from P1 to P2 for non Axis types
    733                 point1UsrCoords = this.line.point1.coords.usrCoords;
    734                 point2UsrCoords = this.line.point2.coords.usrCoords;
    735             }
    736             return {
    737                 x: (point2UsrCoords[1] - point1UsrCoords[1]) / distP1P2,
    738                 y: (point2UsrCoords[2] - point1UsrCoords[2]) / distP1P2
    739             };
    740         },
    741
    742         /**
    743          * Check if (parts of) the tick is inside the canvas. The tick intersects the boundary
    744          * at two positions: [x[0], y[0]] and [x[1], y[1]] in screen coordinates.
    745          * @param  {Array}  x Array of length two
    746          * @param  {Array}  y Array of length two
    747          * @return {Boolean}   true if parts of the tick are inside of the canvas or on the boundary.
    748          */
    749         _isInsideCanvas: function(x, y, m) {
    750             var cw = this.board.canvasWidth,
    751                 ch = this.board.canvasHeight;
    752
    753             if (m === undefined) {
    754                 m = 0;
    755             }
    756             return (x[0] >= m && x[0] <= cw - m && y[0] >= m && y[0] <= ch - m) ||
    757                     (x[1] >= m && x[1] <= cw - m && y[1] >= m && y[1] <= ch - m);
    758         },
    759
    760         /**
    761          * @param {JXG.Coords} coords Coordinates of the tick on the line.
    762          * @param {Boolean} major True if tick is major tick.
    763          * @returns {Array} Array of length 3 containing start and end coordinates in screen coordinates
    764          *                 of the tick (arrays of length 2). 3rd entry is true if major tick otherwise false.
    765          *                 If the tick is outside of the canvas, the return array is empty.
    766          * @private
    767          */
    768         tickEndings: function (coords, major) {
    769             var c, lineStdForm, intersection,
    770                 dxs, dys,
    771                 style,
    772                 x = [-2000000, -2000000],
    773                 y = [-2000000, -2000000];
    774
    775             c = coords.scrCoords;
    776             if (major) {
    777                 dxs = this.dxMaj;
    778                 dys = this.dyMaj;
    779                 style = this.majStyle;
    780             } else {
    781                 dxs = this.dxMin;
    782                 dys = this.dyMin;
    783                 style = this.minStyle;
    784             }
    785             lineStdForm = [-dys * c[1] - dxs * c[2], dys, dxs];
    786
    787             // For all ticks regardless if of finite or infinite
    788             // tick length the intersection with the canvas border is
    789             // computed.
    790             if (style === 'infinite') {
    791                 intersection = Geometry.meetLineBoard(lineStdForm, this.board);
    792                 x[0] = intersection[0].scrCoords[1];
    793                 x[1] = intersection[1].scrCoords[1];
    794                 y[0] = intersection[0].scrCoords[2];
    795                 y[1] = intersection[1].scrCoords[2];
    796             } else {
    797                 x[0] = c[1] + dxs * Type.evaluate(this.visProp.tickendings[0]);
    798                 y[0] = c[2] - dys * Type.evaluate(this.visProp.tickendings[0]);
    799                 x[1] = c[1] - dxs * Type.evaluate(this.visProp.tickendings[1]);
    800                 y[1] = c[2] + dys * Type.evaluate(this.visProp.tickendings[1]);
    801             }
    802
    803             // Check if (parts of) the tick is inside the canvas.
    804             if (this._isInsideCanvas(x, y)) {
    805                 return [x, y, major];
    806             }
    807
    808             return [];
    809         },
    810
    811         /**
    812          * Format label texts. Show the desired number of digits
    813          * and use utf-8 minus sign.
    814          * @param  {Number} value Number to be displayed
    815          * @return {String}       The value converted into a string.
    816          * @private
    817          */
    818         formatLabelText: function(value) {
    819             var labelText = value.toString(),
    820                 ev_s = Type.evaluate(this.visProp.scalesymbol);
    821
    822             // if value is Number
    823             if (Type.isNumber(value)) {
    824                 if (labelText.length > Type.evaluate(this.visProp.maxlabellength) ||
    825                         labelText.indexOf('e') !== -1) {
    826                     labelText = value.toPrecision(Type.evaluate(this.visProp.precision)).toString();
    827                 }
    828                 if (labelText.indexOf('.') > -1 && labelText.indexOf('e') === -1) {
    829                     // trim trailing zeros
    830                     labelText = labelText.replace(/0+$/, '');
    831                     // trim trailing .
    832                     labelText = labelText.replace(/\.$/, '');
    833                 }
    834             }
    835
    836             if (ev_s.length > 0) {
    837                 if (labelText === '1') {
    838                     labelText = ev_s;
    839                 } else if (labelText === '-1') {
    840                     labelText = '-' + ev_s;
    841                 } else if (labelText !== '0') {
    842                     labelText = labelText + ev_s;
    843                 }
    844             }
    845
    846             if (Type.evaluate(this.visProp.useunicodeminus)) {
    847                 labelText = labelText.replace(/-/g, '\u2212');
    848             }
    849             return labelText;
    850         },
    851
    852         /**
    853          * Creates the label text for a given tick. A value for the text can be provided as a number or string
    854          *
    855          * @param  {JXG.Coords}    tick  The Coords-object of the tick to create a label for
    856          * @param  {JXG.Coords}    zero  The Coords-object of line's zero
    857          * @param  {Number|String} value A predefined value for this tick
    858          * @returns {String}
    859          * @private
    860          */
    861         generateLabelText: function (tick, zero, value) {
    862             var labelText,
    863                 distance = this.getDistanceFromZero(zero, tick);
    864
    865             if (Math.abs(distance) < Mat.eps) { // Point is zero
    866                 labelText = '0';
    867             } else {
    868                 // No value provided, equidistant, so assign distance as value
    869                 if (!Type.exists(value)) { // could be null or undefined
    870                     value = distance / Type.evaluate(this.visProp.scale);
    871                 }
    872
    873                 labelText = this.formatLabelText(value);
    874             }
    875
    876             return labelText;
    877         },
    878
    879         /**
    880          * Create a tick label data, i.e. text and coordinates
    881          * @param  {String}     labelText
    882          * @param  {JXG.Coords} tick
    883          * @param  {Number}     tickNumber
    884          * @returns {Object} with properties 'x', 'y', 't' (text), 'i' (tick number) or null in case of o label
    885          * @private
    886          */
    887         generateLabelData: function (labelText, tick, tickNumber) {
    888              var xa, ya, m, fs;
    889
    890              // Test if large portions of the label are inside of the canvas
    891              // This is the last chance to abandon the creation of the label if it is mostly
    892              // outside of the canvas.
    893              fs = Type.evaluate(this.visProp.label.fontsize);
    894              xa = [tick.scrCoords[1], tick.scrCoords[1]];
    895              ya = [tick.scrCoords[2], tick.scrCoords[2]];
    896              m = (fs === undefined) ? 12 : fs;
    897              m *= 1.5;
    898              if (!this._isInsideCanvas(xa, ya, m)) {
    899                  return null;
    900              }
    901
    902              xa = Type.evaluate(this.visProp.label.offset[0]);
    903              ya = Type.evaluate(this.visProp.label.offset[1]);
    904
    905              return {
    906                  x: tick.usrCoords[1] + xa / (this.board.unitX),
    907                  y: tick.usrCoords[2] + ya / (this.board.unitY),
    908                  t: labelText,
    909                  i: tickNumber
    910              };
    911          },
    912
    913         /**
    914          * Recalculate the tick positions and the labels.
    915          * @returns {JXG.Ticks}
    916          */
    917         update: function () {
    918             if (this.needsUpdate) {
    919                 //this.visPropCalc.visible = Type.evaluate(this.visProp.visible);
    920                 // A canvas with no width or height will create an endless loop, so ignore it
    921                 if (this.board.canvasWidth !== 0 && this.board.canvasHeight !== 0) {
    922                     this.calculateTicksCoordinates();
    923                 }
    924                 // this.updateVisibility(this.line.visPropCalc.visible);
    925                 //
    926                 // for (var i = 0; i < this.labels.length; i++) {
    927                 //     if (this.labels[i] !== null) {
    928                 //         this.labels[i].prepareUpdate()
    929                 //             .updateVisibility(this.line.visPropCalc.visible)
    930                 //             .updateRenderer();
    931                 //     }
    932                 // }
    933             }
    934
    935             return this;
    936         },
    937
    938         /**
    939          * Uses the boards renderer to update the arc.
    940          * @returns {JXG.Ticks} Reference to the object.
    941          */
    942         updateRenderer: function () {
    943             if (!this.needsUpdate) {
    944                 return this;
    945             }
    946
    947             if (this.visPropCalc.visible) {
    948                 this.board.renderer.updateTicks(this);
    949             }
    950             this.updateRendererLabels();
    951
    952             this.setDisplayRendNode();
    953             // if (this.visPropCalc.visible != this.visPropOld.visible) {
    954             //     this.board.renderer.display(this, this.visPropCalc.visible);
    955             //     this.visPropOld.visible = this.visPropCalc.visible;
    956             // }
    957
    958             this.needsUpdate = false;
    959             return this;
    960         },
    961
    962         /**
    963          * Updates the label elements of the major ticks.
    964          *
    965          * @private
    966          * @returns {JXG.Ticks} Reference to the object.
    967          */
    968         updateRendererLabels: function() {
    969             var i, j,
    970                 lenData, lenLabels,
    971                 attr,
    972                 label, ld,
    973                 visible;
    974
    975             // The number of labels needed
    976             lenData = this.labelsData.length;
    977             // The number of labels which already exist
    978             lenLabels = this.labels.length;
    979
    980             for (i = 0, j = 0; i < lenData; i++) {
    981                 if (this.labelsData[i] === null) {
    982                     continue;
    983                 }
    984
    985                 ld = this.labelsData[i];
    986                 if (j < lenLabels) {
    987                     // Take an already existing text element
    988                     label = this.labels[j];
    989                     label.setText(ld.t);
    990                     label.setCoords(ld.x, ld.y);
    991                     j++;
    992                 } else {
    993                     // A new text element is needed
    994                     this.labelCounter += 1;
    995
    996                     attr = {
    997                         isLabel: true,
    998                         layer: this.board.options.layer.line,
    999                         highlightStrokeColor: this.board.options.text.strokeColor,
    1000                         highlightStrokeWidth: this.board.options.text.strokeWidth,
    1001                         highlightStrokeOpacity: this.board.options.text.strokeOpacity,
    1002                         priv: this.visProp.priv
    1003                     };
    1004                     attr = Type.deepCopy(attr, this.visProp.label);
    1005                     attr.id = this.id + ld.i + 'Label' + this.labelCounter;
    1006
    1007                     label = Text.createText(this.board, [ld.x, ld.y, ld.t], attr);
    1008                     label.isDraggable = false;
    1009                     label.dump = false;
    1010                     this.labels.push(label);
    1011                 }
    1012
    1013                 visible = Type.evaluate(this.visProp.label.visible);
    1014                 if (visible === 'inherit') {
    1015                     visible = this.visPropCalc.visible;
    1016                 }
    1017                 label.prepareUpdate()
    1018                     .updateVisibility(visible)
    1019                     .updateRenderer();
    1020                 //this.board.renderer.display(label, visible);
    1021
    1022                 label.distanceX = Type.evaluate(this.visProp.label.offset[0]);
    1023                 label.distanceY = Type.evaluate(this.visProp.label.offset[1]);
    1024             }
    1025
    1026             // Hide unused labels
    1027             lenData = j;
    1028             for (j = lenData; j < lenLabels; j++) {
    1029                 this.board.renderer.display(this.labels[j], false);
    1030                 // Tick labels have the attribute "visible: 'inherit'"
    1031                 // This must explicitely set to false, otherwise
    1032                 // this labels would be set to visible in the upcoming
    1033                 // update of the labels.
    1034                 this.labels[j].visProp.visible = false;
    1035             }
    1036
    1037             return this;
    1038         },
    1039
    1040         hideElement: function () {
    1041             var i;
    1042
    1043             JXG.deprecated('Element.hideElement()', 'Element.setDisplayRendNode()');
    1044
    1045             this.visPropCalc.visible = false;
    1046             this.board.renderer.display(this, false);
    1047             for (i = 0; i < this.labels.length; i++) {
    1048                 if (Type.exists(this.labels[i])) {
    1049                     this.labels[i].hideElement();
    1050                 }
    1051             }
    1052
    1053             return this;
    1054         },
    1055
    1056         showElement: function () {
    1057             var i;
    1058
    1059             JXG.deprecated('Element.showElement()', 'Element.setDisplayRendNode()');
    1060
    1061             this.visPropCalc.visible = true;
    1062             this.board.renderer.display(this, false);
    1063
    1064             for (i = 0; i < this.labels.length; i++) {
    1065                 if (Type.exists(this.labels[i])) {
    1066                     this.labels[i].showElement();
    1067                 }
    1068             }
    1069
    1070             return this;
    1071         }
    1072     });
    1073
    1074     /**
    1075      * @class Ticks are used as distance markers on a line.
    1076      * @pseudo
    1077      * @description
    1078      * @name Ticks
    1079      * @augments JXG.Ticks
    1080      * @constructor
    1081      * @type JXG.Ticks
    1082      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1083      * @param {JXG.Line} line The parents consist of the line the ticks are going to be attached to.
    1084      * @param {Number} distance Number defining the distance between two major ticks or an
    1085      * array defining static ticks. Alternatively, the distance can be specified with the attribute
    1086      * "ticksDistance". For arbitrary lines (and not axes) a "zero coordinate" is determined
    1087      * which defines where the first tick is positioned. This zero coordinate
    1088      * can be altered with the attribute "anchor". Possible values are "left", "middle", "right" or a number.
    1089      * The default value is "middle".
    1090      *
    1091      * @example
    1092      * // Create an axis providing two coord pairs.
    1093      *   var p1 = board.create('point', [0, 3]);
    1094      *   var p2 = board.create('point', [1, 3]);
    1095      *   var l1 = board.create('line', [p1, p2]);
    1096      *   var t = board.create('ticks', [l1], {ticksDistance: 2});
    1097      * </pre><div class="jxgbox" id="ee7f2d68-75fc-4ec0-9931-c76918427e63" style="width: 300px; height: 300px;"></div>
    1098      * <script type="text/javascript">
    1099      * (function () {
    1100      *   var board = JXG.JSXGraph.initBoard('ee7f2d68-75fc-4ec0-9931-c76918427e63', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false});
    1101      *   var p1 = board.create('point', [0, 3]);
    1102      *   var p2 = board.create('point', [1, 3]);
    1103      *   var l1 = board.create('line', [p1, p2]);
    1104      *   var t = board.create('ticks', [l1, 2], {ticksDistance: 2});
    1105      * })();
    1106      * </script><pre>
    1107      */
    1108     JXG.createTicks = function (board, parents, attributes) {
    1109         var el, dist,
    1110             attr = Type.copyAttributes(attributes, board.options, 'ticks');
    1111
    1112         if (parents.length < 2) {
    1113             dist = attr.ticksdistance;
    1114         } else {
    1115             dist = parents[1];
    1116         }
    1117
    1118         if (parents[0].elementClass === Const.OBJECT_CLASS_LINE) {
    1119             el = new JXG.Ticks(parents[0], dist, attr);
    1120         } else {
    1121             throw new Error("JSXGraph: Can't create Ticks with parent types '" + (typeof parents[0]) + "'.");
    1122         }
    1123
    1124         // deprecated
    1125         if (Type.isFunction(attr.generatelabelvalue)) {
    1126             el.generateLabelText = attr.generatelabelvalue;
    1127         }
    1128         if (Type.isFunction(attr.generatelabeltext)) {
    1129             el.generateLabelText = attr.generatelabeltext;
    1130         }
    1131
    1132         el.setParents(parents[0]);
    1133         el.isDraggable = true;
    1134         el.fullUpdate(parents[0].visPropCalc.visible);
    1135
    1136         return el;
    1137     };
    1138
    1139     /**
    1140      * @class Hatches can be used to mark congruent lines.
    1141      * @pseudo
    1142      * @description
    1143      * @name Hatch
    1144      * @augments JXG.Ticks
    1145      * @constructor
    1146      * @type JXG.Ticks
    1147      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    1148      * @param {JXG.Line} line The line the hatch marks are going to be attached to.
    1149      * @param {Number} numberofhashes Number of dashes.
    1150      * @example
    1151      * // Create an axis providing two coord pairs.
    1152      *   var p1 = board.create('point', [0, 3]);
    1153      *   var p2 = board.create('point', [1, 3]);
    1154      *   var l1 = board.create('line', [p1, p2]);
    1155      *   var t = board.create('hatch', [l1, 3]);
    1156      * </pre><div class="jxgbox" id="4a20af06-4395-451c-b7d1-002757cf01be" style="width: 300px; height: 300px;"></div>
    1157      * <script type="text/javascript">
    1158      * (function () {
    1159      *   var board = JXG.JSXGraph.initBoard('4a20af06-4395-451c-b7d1-002757cf01be', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false});
    1160      *   var p1 = board.create('point', [0, 3]);
    1161      *   var p2 = board.create('point', [1, 3]);
    1162      *   var l1 = board.create('line', [p1, p2]);
    1163      *   var t = board.create('hatch', [l1, 3]);
    1164      * })();
    1165      * </script><pre>
    1166      *
    1167      * @example
    1168      * // Alter the position of the hatch
    1169      * var board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-10, 10, 10, -5], keepaspectratio:true});
    1170      *
    1171      * var p = board.create('point', [-5, 0]);
    1172      * var q = board.create('point', [5, 0]);
    1173      * var li = board.create('line', [p, q]);
    1174      * var h = board.create('hatch', [li, 2], {anchor: 0.2});
    1175      *
    1176      * </pre><div id="05d720ee-99c9-11e6-a9c7-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
    1177      * <script type="text/javascript">
    1178      *     (function() {
    1179      *         var board = JXG.JSXGraph.initBoard('05d720ee-99c9-11e6-a9c7-901b0e1b8723',
    1180      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    1181      *     var board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-10, 10, 10, -5], keepaspectratio:true});
    1182      *
    1183      *     var p = board.create('point', [-5, 0]);
    1184      *     var q = board.create('point', [5, 0]);
    1185      *     var li = board.create('line', [p, q]);
    1186      *     var h = board.create('hatch', [li, 2], {anchor: 0.2});
    1187      *
    1188      *     })();
    1189      *
    1190      * </script><pre>
    1191      *
    1192      */
    1193     JXG.createHatchmark = function (board, parents, attributes) {
    1194         var num, i, base, width, totalwidth, el,
    1195             pos = [],
    1196             attr = Type.copyAttributes(attributes, board.options, 'hatch');
    1197
    1198         if (parents[0].elementClass !== Const.OBJECT_CLASS_LINE || typeof parents[1] !== 'number') {
    1199             throw new Error("JSXGraph: Can't create Hatch mark with parent types '" + (typeof parents[0]) + "' and '" + (typeof parents[1]) + " and ''" + (typeof parents[2]) + "'.");
    1200         }
    1201
    1202         num = parents[1];
    1203         width = attr.ticksdistance;
    1204         totalwidth = (num - 1) * width;
    1205         base = -totalwidth * 0.5;
    1206
    1207         for (i = 0; i < num; i++) {
    1208             pos[i] = base + i * width;
    1209         }
    1210
    1211         el = board.create('ticks', [parents[0], pos], attr);
    1212         el.elType = 'hatch';
    1213
    1214         return el;
    1215     };
    1216
    1217     JXG.registerElement('ticks', JXG.createTicks);
    1218     JXG.registerElement('hash', JXG.createHatchmark);
    1219     JXG.registerElement('hatch', JXG.createHatchmark);
    1220
    1221     return {
    1222         Ticks: JXG.Ticks,
    1223         createTicks: JXG.createTicks,
    1224         createHashmark: JXG.createHatchmark,
    1225         createHatchmark: JXG.createHatchmark
    1226     };
    1227 });
    1228