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