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