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