1 /*
  2     Copyright 2008-2016
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
 29     and <http://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 
 33 /*global JXG: true, define: true*/
 34 /*jslint nomen: true, plusplus: true*/
 35 
 36 /* depends:
 37  jxg
 38  math/math
 39  math/geometry
 40  base/constants
 41  base/element
 42  base/coords
 43  utils/type
 44   elements:
 45    text
 46  */
 47 
 48 /**
 49  * @fileoverview In this file the geometry object Ticks is defined. Ticks provides
 50  * methods for creation and management of ticks on an axis.
 51  * @author graphjs
 52  * @version 0.1
 53  */
 54 
 55 define([
 56     'jxg', 'math/math', 'math/geometry', 'base/constants', 'base/element', 'base/coords', 'utils/type', 'base/text'
 57 ], function (JXG, Mat, Geometry, Const, GeometryElement, Coords, Type, Text) {
 58 
 59     "use strict";
 60 
 61     /**
 62      * Creates ticks for an axis.
 63      * @class Ticks provides methods for creation and management
 64      * of ticks on an axis.
 65      * @param {JXG.Line} line Reference to the axis the ticks are drawn on.
 66      * @param {Number|Array} ticks Number defining the distance between two major ticks or an array defining static ticks.
 67      * @param {Object} attributes Properties
 68      * @see JXG.Line#addTicks
 69      * @constructor
 70      * @extends JXG.GeometryElement
 71      */
 72     JXG.Ticks = function (line, ticks, attributes) {
 73         this.constructor(line.board, attributes, Const.OBJECT_TYPE_TICKS, Const.OBJECT_CLASS_OTHER);
 74 
 75         /**
 76          * The line the ticks belong to.
 77          * @type JXG.Line
 78          */
 79         this.line = line;
 80 
 81         /**
 82          * The board the ticks line is drawn on.
 83          * @type JXG.Board
 84          */
 85         this.board = this.line.board;
 86 
 87         /**
 88          * A function calculating ticks delta depending on the ticks number.
 89          * @type Function
 90          */
 91         this.ticksFunction = null;
 92 
 93         /**
 94          * Array of fixed ticks.
 95          * @type Array
 96          */
 97         this.fixedTicks = null;
 98 
 99         /**
100          * Equidistant ticks. Distance is defined by ticksFunction
101          * @type Boolean
102          */
103         this.equidistant = false;
104 
105         if (Type.isFunction(ticks)) {
106             this.ticksFunction = ticks;
107             throw new Error("Function arguments are no longer supported.");
108         }
109 
110         if (Type.isArray(ticks)) {
111             this.fixedTicks = ticks;
112         } else {
113             if (Math.abs(ticks) < Mat.eps || ticks < 0) {
114                 ticks = attributes.defaultdistance;
115             }
116 
117             /*
118              * Ticks function:
119              * determines the distance (in user units) of two major ticks
120              */
121             this.ticksFunction = this.makeTicksFunction(ticks);
122 
123             this.equidistant = true;
124         }
125 
126         /**
127          * Least distance between two ticks, measured in pixels.
128          * @type int
129          */
130         this.minTicksDistance = attributes.minticksdistance;
131 
132         /**
133          * Stores the ticks coordinates
134          * @type {Array}
135          */
136         this.ticks = [];
137 
138         /**
139          * Distance between two major ticks in user coordinates
140          * @type {Number}
141          */
142         this.ticksDelta = 1;
143 
144         /**
145          * Array where the labels are saved. There is an array element for every tick,
146          * even for minor ticks which don't have labels. In this case the array element
147          * contains just <tt>null</tt>.
148          * @type Array
149          */
150         this.labels = [];
151 
152         /**
153          * A list of labels that are currently unused and ready for reassignment.
154          * @type {Array}
155          */
156         this.labelsRepo = [];
157 
158         /**
159          * To ensure the uniqueness of label ids this counter is used.
160          * @type {number}
161          */
162         this.labelCounter = 0;
163 
164         this.id = this.line.addTicks(this);
165         this.elType = 'ticks';
166         this.board.setId(this, 'Ti');
167     };
168 
169     JXG.Ticks.prototype = new GeometryElement();
170 
171     JXG.extend(JXG.Ticks.prototype, /** @lends JXG.Ticks.prototype */ {
172 
173         /**
174          * Ticks function:
175          * determines the distance (in user units) of two major ticks.
176          * See above in constructor and in @see JXG.GeometryElement#setAttribute
177          *
178          * @private
179          * @param {Number} ticks Distance between two major ticks
180          * @returns {Function} returns method ticksFunction
181          */
182         makeTicksFunction: function (ticks) {
183             return function () {
184                 var delta, b, dist;
185 
186                 if (this.visProp.insertticks) {
187                     b = this.getLowerAndUpperBounds(this.getZeroCoordinates(), 'ticksdistance');
188                     dist = b.upper - b.lower;
189                     delta = Math.pow(10, Math.floor(Math.log(0.6 * dist) / Math.LN10));
190                     if (dist <= 6 * delta) {
191                         delta *= 0.5;
192                     }
193                     return delta;
194                 }
195 
196                 // upto 0.99.1:
197                 return ticks;
198             };
199         },
200 
201         /**
202          * Checks whether (x,y) is near the line.
203          * @param {Number} x Coordinate in x direction, screen coordinates.
204          * @param {Number} y Coordinate in y direction, screen coordinates.
205          * @returns {Boolean} True if (x,y) is near the line, False otherwise.
206          */
207         hasPoint: function (x, y) {
208             var i, t,
209                 len = (this.ticks && this.ticks.length) || 0,
210                 r = this.board.options.precision.hasPoint;
211 
212             if (!this.line.visProp.scalable) {
213                 return false;
214             }
215 
216             // Ignore non-axes and axes that are not horizontal or vertical
217             if (this.line.stdform[1] !== 0 && this.line.stdform[2] !== 0 && this.line.type !== Const.OBJECT_TYPE_AXIS) {
218                 return false;
219             }
220 
221             for (i = 0; i < len; i++) {
222                 t = this.ticks[i];
223 
224                 // Skip minor ticks
225                 if (t[2]) {
226                     // Ignore ticks at zero
227                     if (!((this.line.stdform[1] === 0 && Math.abs(t[0][0] - this.line.point1.coords.scrCoords[1]) < Mat.eps) ||
228                             (this.line.stdform[2] === 0 && Math.abs(t[1][0] - this.line.point1.coords.scrCoords[2]) < Mat.eps))) {
229                         // tick length is not zero, ie. at least one pixel
230                         if (Math.abs(t[0][0] - t[0][1]) >= 1 || Math.abs(t[1][0] - t[1][1]) >= 1) {
231                             if (this.line.stdform[1] === 0) {
232                                 // Allow dragging near axes only.
233                                 if (Math.abs(y - (t[1][0] + t[1][1]) * 0.5) < 2 * r && t[0][0] - r < x && x < t[0][1] + r) {
234                                     return true;
235                                 }
236                             } else if (this.line.stdform[2] === 0) {
237                                 if (Math.abs(x - (t[0][0] + t[0][1]) * 0.5) < 2 * r && t[1][0] - r < y && y < t[1][1] + r) {
238                                     return true;
239                                 }
240                             }
241                         }
242                     }
243                 }
244             }
245 
246             return false;
247         },
248 
249         /**
250          * Sets x and y coordinate of the tick.
251          * @param {number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
252          * @param {Array} coords coordinates in screen/user units
253          * @param {Array} oldcoords previous coordinates in screen/user units
254          * @returns {JXG.Ticks} this element
255          */
256         setPositionDirectly: function (method, coords, oldcoords) {
257             var dx, dy,
258                 c = new Coords(method, coords, this.board),
259                 oldc = new Coords(method, oldcoords, this.board),
260                 bb = this.board.getBoundingBox();
261 
262             if (!this.line.visProp.scalable) {
263                 return this;
264             }
265 
266             // horizontal line
267             if (Math.abs(this.line.stdform[1]) < Mat.eps && Math.abs(c.usrCoords[1] * oldc.usrCoords[1]) > Mat.eps) {
268                 dx = oldc.usrCoords[1] / c.usrCoords[1];
269                 bb[0] *= dx;
270                 bb[2] *= dx;
271                 this.board.setBoundingBox(bb, false);
272             // vertical line
273             } else if (Math.abs(this.line.stdform[2]) < Mat.eps && Math.abs(c.usrCoords[2] * oldc.usrCoords[2]) > Mat.eps) {
274                 dy = oldc.usrCoords[2] / c.usrCoords[2];
275                 bb[3] *= dy;
276                 bb[1] *= dy;
277                 this.board.setBoundingBox(bb, false);
278             }
279 
280             return this;
281         },
282 
283          /**
284          * (Re-)calculates the ticks coordinates.
285          * @private
286          */
287         calculateTicksCoordinates: function () {
288             var coordsZero, bounds, i,
289                 oldRepoLength = this.labelsRepo.length;
290 
291             // Calculate Ticks width and height in Screen and User Coordinates
292             this.setTicksSizeVariables();
293             // If the parent line is not finite, we can stop here.
294             if (Math.abs(this.dx) < Mat.eps && Math.abs(this.dy) < Mat.eps) {
295                 return;
296             }
297 
298             // Get Zero
299             coordsZero = this.getZeroCoordinates();
300 
301             // Calculate lower bound and upper bound limits based on distance between p1 and centre and p2 and centre
302             bounds = this.getLowerAndUpperBounds(coordsZero);
303 
304             // Clean up
305             this.removeTickLabels();
306             this.ticks = [];
307             this.labels = [];
308 
309             // Create Ticks Coordinates and Labels
310             if (this.equidistant) {
311                 this.generateEquidistantTicks(coordsZero, bounds);
312             } else {
313                 this.generateFixedTicks(coordsZero, bounds);
314             }
315 
316             // Hide unused labels in labelsRepo
317             for (i = oldRepoLength; i < this.labelsRepo.length; i++) {
318                 this.labelsRepo[i].setAttribute({visible: false});
319             }
320         },
321 
322         /**
323          * Sets the variables used to set the height and slope of each tick.
324          *
325          * @private
326          */
327         setTicksSizeVariables: function () {
328             var d,
329                 distMaj = this.visProp.majorheight * 0.5,
330                 distMin = this.visProp.minorheight * 0.5;
331 
332             // ticks width and height in screen units
333             this.dxMaj = this.line.stdform[1];
334             this.dyMaj = this.line.stdform[2];
335             this.dxMin = this.dxMaj;
336             this.dyMin = this.dyMaj;
337 
338             // ticks width and height in user units
339             this.dx = this.dxMaj;
340             this.dy = this.dyMaj;
341 
342             // After this, the length of the vector (dxMaj, dyMaj) in screen coordinates is equal to distMaj pixel.
343             d = Math.sqrt(
344                 this.dxMaj * this.dxMaj * this.board.unitX * this.board.unitX +
345                     this.dyMaj * this.dyMaj * this.board.unitY * this.board.unitY
346             );
347             this.dxMaj *= distMaj / d * this.board.unitX;
348             this.dyMaj *= distMaj / d * this.board.unitY;
349             this.dxMin *= distMin / d * this.board.unitX;
350             this.dyMin *= distMin / d * this.board.unitY;
351 
352             // Grid-like ticks?
353             this.minStyle = 'finite';
354             if (this.visProp.minorheight < 0) {
355                 this.minStyle = 'infinite';
356             }
357 
358             this.majStyle = 'finite';
359             if (this.visProp.majorheight < 0) {
360                 this.majStyle = 'infinite';
361             }
362         },
363 
364         /**
365          * Returns the coordinates of the point zero of the line.
366          *
367          * If the line is an {@link Axis}, the coordinates of the projection of the board's zero point is returned
368          *
369          * Otherwise, the coordinates of the point that acts as zero are established depending on the value of {@link JXG.Ticks#anchor}
370          *
371          * @returns {JXG.Coords} Coords object for the Zero point on the line
372          * @private
373          */
374         getZeroCoordinates: function () {
375             if (this.line.type === Const.OBJECT_TYPE_AXIS) {
376                 return Geometry.projectPointToLine({
377                     coords: {
378                         usrCoords: [1, 0, 0]
379                     }
380                 }, this.line, this.board);
381             }
382 
383             if (this.visProp.anchor === 'right') {
384                 return this.line.point2.coords;
385             }
386 
387             if (this.visProp.anchor === 'middle') {
388                 return new Coords(Const.COORDS_BY_USER, [
389                     (this.line.point1.coords.usrCoords[1] + this.line.point2.coords.usrCoords[1]) / 2,
390                     (this.line.point1.coords.usrCoords[2] + this.line.point2.coords.usrCoords[2]) / 2
391                 ], this.board);
392             }
393 
394             return this.line.point1.coords;
395         },
396 
397         /**
398          * Calculate the lower and upper bounds for tick rendering
399          * If {@link JXG.Ticks#includeBoundaries} is false, the boundaries will exclude point1 and point2
400          *
401          * @param  {JXG.Coords} coordsZero
402          * @returns {String} type  (Optional) If type=='ticksdistance' the bounds are the intersection of the line with the bounding box of the board.
403          *              Otherwise it is the projection of the corners of the bounding box to the line. The first case i s needed to automatically
404          *              generate ticks. The second case is for drawing of the ticks.
405          * @returns {Object}     contains the lower and upper bounds
406          *
407          * @private
408          */
409         getLowerAndUpperBounds: function (coordsZero, type) {
410             var lowerBound, upperBound,
411                 // The line's defining points that will be adjusted to be within the board limits
412                 point1 = new Coords(Const.COORDS_BY_USER, this.line.point1.coords.usrCoords, this.board),
413                 point2 = new Coords(Const.COORDS_BY_USER, this.line.point2.coords.usrCoords, this.board),
414                 // Are the original defining points within the board?
415                 isPoint1inBoard = (Math.abs(point1.usrCoords[0]) >= Mat.eps &&
416                     point1.scrCoords[1] >= 0.0 && point1.scrCoords[1] <= this.board.canvasWidth &&
417                     point1.scrCoords[2] >= 0.0 && point1.scrCoords[2] <= this.board.canvasHeight),
418                 isPoint2inBoard = (Math.abs(point2.usrCoords[0]) >= Mat.eps &&
419                     point2.scrCoords[1] >= 0.0 && point2.scrCoords[1] <= this.board.canvasWidth &&
420                     point2.scrCoords[2] >= 0.0 && point2.scrCoords[2] <= this.board.canvasHeight),
421                 // We use the distance from zero to P1 and P2 to establish lower and higher points
422                 dZeroPoint1, dZeroPoint2;
423 
424             // Adjust line limit points to be within the board
425             if (JXG.exists(type) || type === 'tickdistance') {
426                 // The good old calcStraight is needed for determining the distance between major ticks.
427                 // Here, only the visual area is of importance
428                 Geometry.calcStraight(this.line, point1, point2, this.line.visProp.margin);
429             } else {
430                 // This function projects the corners of the board to the line.
431                 // This is important for diagonal lines with infinite tick lines.
432                 Geometry.calcLineDelimitingPoints(this.line, point1, point2);
433             }
434 
435             // Calculate distance from Zero to P1 and to P2
436             dZeroPoint1 = this.getDistanceFromZero(coordsZero, point1);
437             dZeroPoint2 = this.getDistanceFromZero(coordsZero, point2);
438 
439             // We have to establish if the direction is P1->P2 or P2->P1 to set the lower and upper
440             // boundaries appropriately. As the distances contain also a sign to indicate direction,
441             // we can compare dZeroPoint1 and dZeroPoint2 to establish the line direction
442             if (dZeroPoint1 < dZeroPoint2) { // Line goes P1->P2
443                 lowerBound = dZeroPoint1;
444                 if (!this.line.visProp.straightfirst && isPoint1inBoard && !this.visProp.includeboundaries) {
445                     lowerBound += Mat.eps;
446                 }
447                 upperBound = dZeroPoint2;
448                 if (!this.line.visProp.straightlast && isPoint2inBoard && !this.visProp.includeboundaries) {
449                     upperBound -= Mat.eps;
450                 }
451             } else if (dZeroPoint2 < dZeroPoint1) { // Line goes P2->P1
452                 lowerBound = dZeroPoint2;
453                 if (!this.line.visProp.straightlast && isPoint2inBoard && !this.visProp.includeboundaries) {
454                     lowerBound += Mat.eps;
455                 }
456                 upperBound = dZeroPoint1;
457                 if (!this.line.visProp.straightfirst && isPoint1inBoard && !this.visProp.includeboundaries) {
458                     upperBound -= Mat.eps;
459                 }
460             } else { // P1 = P2 = Zero, we can't do a thing
461                 lowerBound = 0;
462                 upperBound = 0;
463             }
464 
465             return {
466                 lower: lowerBound,
467                 upper: upperBound
468             };
469         },
470 
471         /**
472          * Calculates the distance in user coordinates from zero to a given point including its sign
473          *
474          * @param  {JXG.Coords} zero  coordinates of the point considered zero
475          * @param  {JXG.Coords} point coordinates of the point to find out the distance
476          * @returns {Number}           distance between zero and point, including its sign
477          * @private
478          */
479         getDistanceFromZero: function (zero, point) {
480             var eps = Mat.eps,
481                 distance = zero.distance(Const.COORDS_BY_USER, point);
482 
483             // Establish sign
484             if (this.line.type === Const.OBJECT_TYPE_AXIS) {
485                 if ((Mat.relDif(zero.usrCoords[1], point.usrCoords[1]) > eps &&
486                         zero.usrCoords[1] - point.usrCoords[1] > eps) ||
487                     (Mat.relDif(zero.usrCoords[2], point.usrCoords[2]) > eps &&
488                         zero.usrCoords[2] - point.usrCoords[2] > eps)) {
489 
490                     distance *= -1;
491                 }
492             } else if (this.visProp.anchor === 'right') {
493                 if (Geometry.isSameDirection(zero, this.line.point1.coords, point)) {
494                     distance *= -1;
495                 }
496             } else {
497                 if (!Geometry.isSameDirection(zero, this.line.point2.coords, point)) {
498                     distance *= -1;
499                 }
500             }
501             return distance;
502         },
503 
504         /**
505          * Creates ticks coordinates and labels automatically.
506          * The frequency of ticks is affected by the values of {@link JXG.Ticks#insertTicks} and {@link JXG.Ticks#ticksDistance}
507          *
508          * @param  {JXG.Coords} coordsZero coordinates of the point considered zero
509          * @param  {Object}     bounds     contains the lower and upper boundaries for ticks placement
510          * @private
511          */
512         generateEquidistantTicks: function (coordsZero, bounds) {
513             var tickPosition,
514                 // Calculate X and Y distance between two major ticks
515                 deltas = this.getXandYdeltas(),
516                 // Distance between two major ticks in user coordinates
517                 ticksDelta = (this.equidistant ? this.ticksFunction(1) : this.ticksDelta);
518 
519             // adjust ticks distance
520             ticksDelta *= this.visProp.scale;
521             if (this.visProp.insertticks && this.minTicksDistance > Mat.eps) {
522                 ticksDelta = this.adjustTickDistance(ticksDelta, coordsZero, deltas);
523                 ticksDelta /= (this.visProp.minorticks + 1);
524             } else if (!this.visProp.insertticks) {
525                 ticksDelta /= (this.visProp.minorticks + 1);
526             }
527             this.ticksDelta = ticksDelta;
528 
529             if (ticksDelta < Mat.eps) {
530                 return;
531             }
532 
533             // Position ticks from zero to the positive side while not reaching the upper boundary
534             tickPosition = 0;
535             if (!this.visProp.drawzero) {
536                 tickPosition = ticksDelta;
537             }
538 
539             while (tickPosition <= bounds.upper) {
540                 // Only draw ticks when we are within bounds, ignore case where  tickPosition < lower < upper
541                 if (tickPosition >= bounds.lower) {
542                     this.processTickPosition(coordsZero, tickPosition, ticksDelta, deltas);
543                 }
544                 tickPosition += ticksDelta;
545             }
546 
547             // Position ticks from zero (not inclusive) to the negative side while not reaching the lower boundary
548             tickPosition = -ticksDelta;
549             while (tickPosition >= bounds.lower) {
550                 // Only draw ticks when we are within bounds, ignore case where lower < upper < tickPosition
551                 if (tickPosition <= bounds.upper) {
552                     this.processTickPosition(coordsZero, tickPosition, ticksDelta, deltas);
553                 }
554                 tickPosition -= ticksDelta;
555             }
556         },
557 
558         /**
559          * Auxiliary method used by {@link JXG.Ticks#generateEquidistantTicks} to adjust the
560          * distance between two ticks depending on {@link JXG.Ticks#minTicksDistance} value
561          *
562          * @param  {Number}     ticksDelta  distance between two major ticks in user coordinates
563          * @param  {JXG.Coords} coordsZero  coordinates of the point considered zero
564          * @param  {Object}     deltas      x and y distance in pixel between two user units
565          * @param  {Object}     bounds      upper and lower bound of the tick positions in user units.
566          * @private
567          */
568         adjustTickDistance: function (ticksDelta, coordsZero, deltas) {
569             var nx, ny, bounds,
570                 distScr,
571                 sgn = 1;
572 
573             bounds = this.getLowerAndUpperBounds(coordsZero, 'ticksdistance');
574             nx = coordsZero.usrCoords[1] + deltas.x * ticksDelta;
575             ny = coordsZero.usrCoords[2] + deltas.y * ticksDelta;
576             distScr = coordsZero.distance(Const.COORDS_BY_SCREEN, new Coords(Const.COORDS_BY_USER, [nx, ny], this.board));
577             while (distScr / (this.visProp.minorticks + 1) < this.minTicksDistance) {
578                 if (sgn === 1) {
579                     ticksDelta *= 2;
580                 } else {
581                     ticksDelta *= 5;
582                 }
583                 sgn *= -1;
584 
585                 nx = coordsZero.usrCoords[1] + deltas.x * ticksDelta;
586                 ny = coordsZero.usrCoords[2] + deltas.y * ticksDelta;
587                 distScr = coordsZero.distance(Const.COORDS_BY_SCREEN, new Coords(Const.COORDS_BY_USER, [nx, ny], this.board));
588             }
589             return ticksDelta;
590         },
591 
592         /**
593          * Auxiliary method used by {@link JXG.Ticks#generateEquidistantTicks} to create a tick
594          * in the line at the given tickPosition.
595          *
596          * @param  {JXG.Coords} coordsZero    coordinates of the point considered zero
597          * @param  {Number}     tickPosition  current tick position relative to zero
598          * @param  {Number}     ticksDelta    distance between two major ticks in user coordinates
599          * @param  {Object}     deltas      x and y distance between two major ticks
600          * @private
601          */
602         processTickPosition: function (coordsZero, tickPosition, ticksDelta, deltas) {
603             var x, y, tickCoords, ti, labelText;
604             // Calculates tick coordinates
605             x = coordsZero.usrCoords[1] + tickPosition * deltas.x;
606             y = coordsZero.usrCoords[2] + tickPosition * deltas.y;
607             tickCoords = new Coords(Const.COORDS_BY_USER, [x, y], this.board);
608 
609             // Test if tick is a major tick.
610             // This is the case if tickPosition/ticksDelta is
611             // a multiple of the number of minorticks+1
612             tickCoords.major = Math.round(tickPosition / ticksDelta) % (this.visProp.minorticks + 1) === 0;
613 
614             // Compute the start position and the end position of a tick.
615             // If both positions are out of the canvas, ti is empty.
616             ti = this.tickEndings(tickCoords, tickCoords.major);
617             if (ti.length === 3) {
618                 this.ticks.push(ti);
619 
620                 if (tickCoords.major && this.visProp.drawlabels) {
621                     labelText = this.generateLabelText(tickCoords, coordsZero);
622                     this.labels.push(this.generateLabel(labelText, tickCoords, this.ticks.length));
623                 } else {
624                     this.labels.push(null);
625                 }
626             }
627         },
628 
629         /**
630          * Creates ticks coordinates and labels based on {@link JXG.Ticks#fixedTicks} and {@link JXG.Ticks#labels}.
631          *
632          * @param  {JXG.Coords} coordsZero Coordinates of the point considered zero
633          * @param  {Object}     bounds     contains the lower and upper boundaries for ticks placement
634          * @private
635          */
636         generateFixedTicks: function (coordsZero, bounds) {
637             var tickCoords, labelText, i, ti,
638                 x, y,
639                 hasLabelOverrides = Type.isArray(this.visProp.labels),
640                 // Calculate X and Y distance between two major points in the line
641                 deltas = this.getXandYdeltas();
642 
643             for (i = 0; i < this.fixedTicks.length; i++) {
644                 x = coordsZero.usrCoords[1] + this.fixedTicks[i] * deltas.x;
645                 y = coordsZero.usrCoords[2] + this.fixedTicks[i] * deltas.y;
646                 tickCoords = new Coords(Const.COORDS_BY_USER, [x, y], this.board);
647 
648                 // Compute the start position and the end position of a tick.
649                 // If tick is out of the canvas, ti is empty.
650                 ti = this.tickEndings(tickCoords, true);
651                 if (ti.length === 3 && this.fixedTicks[i] >= bounds.lower && this.fixedTicks[i] <= bounds.upper) {
652                     this.ticks.push(ti);
653 
654                     if (this.visProp.drawlabels && (hasLabelOverrides || Type.exists(this.visProp.labels[i]))) {
655                         labelText = hasLabelOverrides ? this.visProp.labels[i] : this.fixedTicks[i];
656                         this.labels.push(
657                             this.generateLabel(this.generateLabelText(tickCoords, coordsZero, labelText), tickCoords, i)
658                         );
659                     } else {
660                         this.labels.push(null);
661                     }
662                 }
663             }
664         },
665 
666         /**
667          * Calculates the x and y distance in pixel between two units in user space.
668          *
669          * @returns {Object}
670          * @private
671          */
672         getXandYdeltas: function () {
673             var
674                 // Auxiliary points to store the start and end of the line according to its direction
675                 point1UsrCoords, point2UsrCoords,
676                 distP1P2 = this.line.point1.Dist(this.line.point2);
677 
678             if (this.line.type === Const.OBJECT_TYPE_AXIS) {
679                 // When line is an Axis, direction depends on Board Coordinates system
680 
681                 // assume line.point1 and line.point2 are in correct order
682                 point1UsrCoords = this.line.point1.coords.usrCoords;
683                 point2UsrCoords = this.line.point2.coords.usrCoords;
684 
685                 // Check if direction is incorrect, then swap
686                 if (point1UsrCoords[1] > point2UsrCoords[1] ||
687                         (Math.abs(point1UsrCoords[1] - point2UsrCoords[1]) < Mat.eps &&
688                         point1UsrCoords[2] > point2UsrCoords[2])) {
689                     point1UsrCoords = this.line.point2.coords.usrCoords;
690                     point2UsrCoords = this.line.point1.coords.usrCoords;
691                 }
692             } else {
693                 // line direction is always from P1 to P2 for non Axis types
694                 point1UsrCoords = this.line.point1.coords.usrCoords;
695                 point2UsrCoords = this.line.point2.coords.usrCoords;
696             }
697             return {
698                 x: (point2UsrCoords[1] - point1UsrCoords[1]) / distP1P2,
699                 y: (point2UsrCoords[2] - point1UsrCoords[2]) / distP1P2
700             };
701         },
702 
703         /**
704          * @param {JXG.Coords} coords Coordinates of the tick on the line.
705          * @param {Boolean} major True if tick is major tick.
706          * @returns {Array} Array of length 3 containing start and end coordinates in screen coordinates
707          *                 of the tick (arrays of length 2). 3rd entry is true if major tick otherwise false.
708          *                 If the tick is outside of the canvas, the return array is empty.
709          * @private
710          */
711         tickEndings: function (coords, major) {
712             var c, lineStdForm, intersection,
713                 dxs, dys,
714                 style,
715                 cw = this.board.canvasWidth,
716                 ch = this.board.canvasHeight,
717                 x = [-1000 * cw, -1000 * ch],
718                 y = [-1000 * cw, -1000 * ch],
719                 isInsideCanvas = false;
720 
721             c = coords.scrCoords;
722             if (major) {
723                 dxs = this.dxMaj;
724                 dys = this.dyMaj;
725                 style = this.majStyle;
726             } else {
727                 dxs = this.dxMin;
728                 dys = this.dyMin;
729                 style = this.minStyle;
730             }
731             lineStdForm = [-dys * c[1] - dxs * c[2], dys, dxs];
732 
733             // For all ticks regardless if of finite or infinite
734             // tick length the intersection with the canvas border is
735             // computed.
736 
737             if (style === 'infinite') {
738                 intersection = Geometry.meetLineBoard(lineStdForm, this.board);
739                 x[0] = intersection[0].scrCoords[1];
740                 x[1] = intersection[1].scrCoords[1];
741                 y[0] = intersection[0].scrCoords[2];
742                 y[1] = intersection[1].scrCoords[2];
743             } else {
744                 x[0] = c[1] + dxs * this.visProp.tickendings[0];
745                 y[0] = c[2] - dys * this.visProp.tickendings[0];
746                 x[1] = c[1] - dxs * this.visProp.tickendings[1];
747                 y[1] = c[2] + dys * this.visProp.tickendings[1];
748             }
749 
750             // check if (parts of) the tick is inside the canvas.
751             isInsideCanvas = (x[0] >= 0 && x[0] <= cw && y[0] >= 0 && y[0] <= ch) ||
752                 (x[1] >= 0 && x[1] <= cw && y[1] >= 0 && y[1] <= ch);
753 
754             if (isInsideCanvas) {
755                 return [x, y, major];
756             }
757 
758             return [];
759         },
760 
761         /**
762          * Creates the label text for a given tick. A value for the text can be provided as a number or string
763          *
764          * @param  {JXG.Coords}    tick  The Coords-object of the tick to create a label for
765          * @param  {JXG.Coords}    zero  The Coords-object of line's zero
766          * @param  {Number|String} value A predefined value for this tick
767          * @returns {String}
768          * @private
769          */
770         generateLabelText: function (tick, zero, value) {
771             var labelText,
772                 distance = this.getDistanceFromZero(zero, tick);
773 
774             if (Math.abs(distance) < Mat.eps) { // Point is zero
775                 labelText = '0';
776             } else {
777                 // No value provided, equidistant, so assign distance as value
778                 if (!Type.exists(value)) { // could be null or undefined
779                     value = distance / this.visProp.scale;
780                 }
781 
782                 labelText = value.toString();
783 
784                 // if value is Number
785                 if (Type.isNumber(value)) {
786                     if (labelText.length > this.visProp.maxlabellength || labelText.indexOf('e') !== -1) {
787                         labelText = value.toPrecision(this.visProp.precision).toString();
788                     }
789                     if (labelText.indexOf('.') > -1 && labelText.indexOf('e') === -1) {
790                         // trim trailing zeros
791                         labelText = labelText.replace(/0+$/, '');
792                         // trim trailing .
793                         labelText = labelText.replace(/\.$/, '');
794                     }
795                 }
796 
797                 if (this.visProp.scalesymbol.length > 0) {
798                     if (labelText === '1') {
799                         labelText = this.visProp.scalesymbol;
800                     } else if (labelText === '-1') {
801                         labelText = '-' + this.visProp.scalesymbol;
802                     } else if (labelText !== '0') {
803                         labelText = labelText + this.visProp.scalesymbol;
804                     }
805                 }
806 
807                 if (this.visProp.useunicodeminus) {
808                     labelText = labelText.replace(/-/g, '\u2212');
809                 }
810 
811             }
812 
813             return labelText;
814         },
815 
816         /**
817          * Create a tick label
818          * @param  {String}     labelText
819          * @param  {JXG.Coords} tick
820          * @param  {Number}     tickNumber
821          * @returns {JXG.Text}
822          * @private
823          */
824         generateLabel: function (labelText, tick, tickNumber) {
825             var label,
826                 attr = {
827                     isLabel: true,
828                     layer: this.board.options.layer.line,
829                     highlightStrokeColor: this.board.options.text.strokeColor,
830                     highlightStrokeWidth: this.board.options.text.strokeWidth,
831                     highlightStrokeOpacity: this.board.options.text.strokeOpacity,
832                     visible: this.visProp.visible,
833                     priv: this.visProp.priv
834                 };
835 
836             attr = Type.deepCopy(attr, this.visProp.label);
837 
838             if (this.labelsRepo.length > 0) {
839                 label = this.labelsRepo.pop();
840                 label.setText(labelText);
841                 label.setAttribute(attr);
842             } else {
843                 this.labelCounter += 1;
844                 attr.id = this.id + tickNumber + 'Label' + this.labelCounter;
845                 label = Text.createText(this.board, [tick.usrCoords[1], tick.usrCoords[2], labelText], attr);
846             }
847 
848             label.isDraggable = false;
849             label.dump = false;
850 
851             label.distanceX = this.visProp.label.offset[0];
852             label.distanceY = this.visProp.label.offset[1];
853             label.setCoords(
854                 tick.usrCoords[1] + label.distanceX / (this.board.unitX),
855                 tick.usrCoords[2] + label.distanceY / (this.board.unitY)
856             );
857 
858             return label;
859         },
860 
861         /**
862          * Removes the HTML divs of the tick labels
863          * before repositioning
864          * @private
865          */
866         removeTickLabels: function () {
867             var j;
868 
869             // remove existing tick labels
870             if (Type.exists(this.labels)) {
871                 if ((this.board.needsFullUpdate || this.needsRegularUpdate || this.needsUpdate) &&
872                         !(this.board.renderer.type === 'canvas' && this.board.options.text.display === 'internal')) {
873                     for (j = 0; j < this.labels.length; j++) {
874                         if (Type.exists(this.labels[j])) {
875                             this.labelsRepo.push(this.labels[j]);
876                         }
877                     }
878                 }
879             }
880         },
881 
882         /**
883          * Recalculate the tick positions and the labels.
884          * @returns {JXG.Ticks}
885          */
886         update: function () {
887             if (this.needsUpdate) {
888                 // A canvas with no width or height will create an endless loop, so ignore it
889                 if (this.board.canvasWidth !== 0 && this.board.canvasHeight !== 0) {
890                     this.calculateTicksCoordinates();
891                 }
892             }
893 
894             return this;
895         },
896 
897         /**
898          * Uses the boards renderer to update the arc.
899          * @returns {JXG.Ticks}
900          */
901         updateRenderer: function () {
902             if (this.needsUpdate) {
903                 this.board.renderer.updateTicks(this);
904                 this.needsUpdate = false;
905             }
906 
907             return this;
908         },
909 
910         hideElement: function () {
911             var i;
912 
913             this.visProp.visible = false;
914             this.board.renderer.hide(this);
915 
916             for (i = 0; i < this.labels.length; i++) {
917                 if (Type.exists(this.labels[i])) {
918                     this.labels[i].hideElement();
919                 }
920             }
921 
922             return this;
923         },
924 
925         showElement: function () {
926             var i;
927 
928             this.visProp.visible = true;
929             this.board.renderer.show(this);
930 
931             for (i = 0; i < this.labels.length; i++) {
932                 if (Type.exists(this.labels[i])) {
933                     this.labels[i].showElement();
934                 }
935             }
936 
937             return this;
938         }
939     });
940 
941     /**
942      * @class Ticks are used as distance markers on a line.
943      * @pseudo
944      * @description
945      * @name Ticks
946      * @augments JXG.Ticks
947      * @constructor
948      * @type JXG.Ticks
949      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
950      * @param {JXG.Line,Number,Function} line,_distance,_generateLabelFunc The parents consist of the line the ticks are going to be attached to and the
951      * distance between two major ticks.
952      * The third parameter (optional) is a function which determines the tick label. It has as parameter a coords object containing the coordinates of the new tick.
953      * @example
954      * // Create an axis providing two coord pairs.
955      *   var p1 = board.create('point', [0, 3]);
956      *   var p2 = board.create('point', [1, 3]);
957      *   var l1 = board.create('line', [p1, p2]);
958      *   var t = board.create('ticks', [l1], {ticksDistance: 2});
959      * </pre><div class="jxgbox"id="ee7f2d68-75fc-4ec0-9931-c76918427e63" style="width: 300px; height: 300px;"></div>
960      * <script type="text/javascript">
961      * (function () {
962      *   var board = JXG.JSXGraph.initBoard('ee7f2d68-75fc-4ec0-9931-c76918427e63', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false});
963      *   var p1 = board.create('point', [0, 3]);
964      *   var p2 = board.create('point', [1, 3]);
965      *   var l1 = board.create('line', [p1, p2]);
966      *   var t = board.create('ticks', [l1, 2], {ticksDistance: 2});
967      * })();
968      * </script><pre>
969      */
970     JXG.createTicks = function (board, parents, attributes) {
971         var el, dist,
972             attr = Type.copyAttributes(attributes, board.options, 'ticks');
973 
974         if (parents.length < 2) {
975             dist = attr.ticksdistance;
976         } else {
977             dist = parents[1];
978         }
979 
980         if (parents[0].elementClass === Const.OBJECT_CLASS_LINE) {
981             el = new JXG.Ticks(parents[0], dist, attr);
982         } else {
983             throw new Error("JSXGraph: Can't create Ticks with parent types '" + (typeof parents[0]) + "'.");
984         }
985 
986         // deprecated
987         if (Type.isFunction(attr.generatelabelvalue)) {
988             el.generateLabelText = attr.generatelabelvalue;
989         }
990         if (Type.isFunction(attr.generatelabeltext)) {
991             el.generateLabelText = attr.generatelabeltext;
992         }
993 
994         el.setParents(parents[0]);
995         el.isDraggable = true;
996 
997         return el;
998     };
999 
1000     /**
1001      * @class Hashes can be used to mark congruent lines.
1002      * @pseudo
1003      * @description
1004      * @name Hatch
1005      * @augments JXG.Ticks
1006      * @constructor
1007      * @type JXG.Ticks
1008      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
1009      * @param {JXG.Line,Number} line,numberofhashes The parents consist of the line the hatch marks are going to be attached to and the
1010      * number of dashes.
1011      * @example
1012      * // Create an axis providing two coord pairs.
1013      *   var p1 = board.create('point', [0, 3]);
1014      *   var p2 = board.create('point', [1, 3]);
1015      *   var l1 = board.create('line', [p1, p2]);
1016      *   var t = board.create('hatch', [l1, 3]);
1017      * </pre><div class="jxgbox"id="4a20af06-4395-451c-b7d1-002757cf01be" style="width: 300px; height: 300px;"></div>
1018      * <script type="text/javascript">
1019      * (function () {
1020      *   var board = JXG.JSXGraph.initBoard('4a20af06-4395-451c-b7d1-002757cf01be', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false});
1021      *   var p1 = board.create('point', [0, 3]);
1022      *   var p2 = board.create('point', [1, 3]);
1023      *   var l1 = board.create('line', [p1, p2]);
1024      *   var t = board.create('hatch', [l1, 3]);
1025      * })();
1026      * </script><pre>
1027      */
1028     JXG.createHatchmark = function (board, parents, attributes) {
1029         var num, i, base, width, totalwidth, el,
1030             pos = [],
1031             attr = Type.copyAttributes(attributes, board.options, 'hatch');
1032 
1033         if (parents[0].elementClass !== Const.OBJECT_CLASS_LINE || typeof parents[1] !== 'number') {
1034             throw new Error("JSXGraph: Can't create Hatch mark with parent types '" + (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'.");
1035         }
1036 
1037         num = parents[1];
1038         width = attr.ticksdistance;
1039         totalwidth = (num - 1) * width;
1040         base = -totalwidth / 2;
1041 
1042         for (i = 0; i < num; i++) {
1043             pos[i] = base + i * width;
1044         }
1045 
1046         el = board.create('ticks', [parents[0], pos], attr);
1047         el.elType = 'hatch';
1048 
1049         return el;
1050     };
1051 
1052     JXG.registerElement('ticks', JXG.createTicks);
1053     JXG.registerElement('hash', JXG.createHatchmark);
1054     JXG.registerElement('hatch', JXG.createHatchmark);
1055 
1056     return {
1057         Ticks: JXG.Ticks,
1058         createTicks: JXG.createTicks,
1059         createHashmark: JXG.createHatchmark,
1060         createHatchmark: JXG.createHatchmark
1061     };
1062 });
1063