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