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