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