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