1 /*
  2     Copyright 2008-2013
  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) {
112                 ticks = attributes.defaultdistance;
113             }
114 
115             this.ticksFunction = function () {
116                 return ticks;
117             };
118             this.equidistant = true;
119         }
120 
121         /**
122          * Least distance between two ticks, measured in pixels.
123          * @type int
124          */
125         this.minTicksDistance = attributes.minticksdistance;
126 
127         /**
128          * Maximum distance between two ticks, measured in pixels. Is used only when insertTicks
129          * is set to true.
130          * @type int
131          * @see #insertTicks
132          * @deprecated This value will be ignored.
133          */
134         this.maxTicksDistance = attributes.maxticksdistance;
135 
136         /**
137          * Array where the labels are saved. There is an array element for every tick,
138          * even for minor ticks which don't have labels. In this case the array element
139          * contains just <tt>null</tt>.
140          * @type Array
141          */
142         this.labels = [];
143 
144         /**
145          * A list of labels that are currently unused and ready for reassignment.
146          * @type {Array}
147          */
148         this.labelsRepo = [];
149 
150         /**
151          * To ensure the uniqueness of label ids this counter is used.
152          * @type {number}
153          */
154         this.labelCounter = 0;
155 
156         this.id = this.line.addTicks(this);
157         this.board.setId(this, 'Ti');
158     };
159 
160     JXG.Ticks.prototype = new GeometryElement();
161 
162     JXG.extend(JXG.Ticks.prototype, /** @lends JXG.Ticks.prototype */ {
163         /**
164          * Checks whether (x,y) is near the line.
165          * @param {Number} x Coordinate in x direction, screen coordinates.
166          * @param {Number} y Coordinate in y direction, screen coordinates.
167          * @return {Boolean} True if (x,y) is near the line, False otherwise.
168          */
169         hasPoint: function (x, y) {
170             var i, t,
171                 len = (this.ticks && this.ticks.length) || 0,
172                 r = this.board.options.precision.hasPoint;
173 
174             if (!this.line.visProp.scalable) {
175                 return false;
176             }
177 
178             // Ignore non-axes and axes that are not horizontal or vertical
179             if (this.line.stdform[1] !== 0 && this.line.stdform[2] !== 0 && this.line.type !== Const.OBJECT_TYPE_AXIS) {
180                 return false;
181             }
182 
183             for (i = 0; i < len; i++) {
184                 t = this.ticks[i];
185 
186                 // Skip minor ticks
187                 if (t[2]) {
188                     // Ignore ticks at zero
189                     if (!((this.line.stdform[1] === 0 && Math.abs(t[0][0] - this.line.point1.coords.scrCoords[1]) < Mat.eps) ||
190                             (this.line.stdform[2] === 0 && Math.abs(t[1][0] - this.line.point1.coords.scrCoords[2]) < Mat.eps))) {
191                         // tick length is not zero, ie. at least one pixel
192                         if (Math.abs(t[0][0] - t[0][1]) >= 1 || Math.abs(t[1][0] - t[1][1]) >= 1) {
193                             if (this.line.stdform[1] === 0) {
194                                 // Allow dragging near axes only.
195                                 if (Math.abs(y - (t[1][0] + t[1][1]) * 0.5) < 2 * r && t[0][0] - r < x && x < t[0][1] + r) {
196                                     return true;
197                                 }
198                             } else if (this.line.stdform[2] === 0) {
199                                 if (Math.abs(x - (t[0][0] + t[0][1]) * 0.5) < 2 * r && t[1][0] - r < y && y < t[1][1] + r) {
200                                     return true;
201                                 }
202                             }
203                         }
204                     }
205                 }
206             }
207 
208             return false;
209         },
210 
211         generateLabelValue: function (tick, center) {
212             var anchor = this.visProp.anchor,
213                 f = -1,
214                 p1 = this.line.point1,
215                 p2 = this.line.point2;
216 
217             // horizontal axis
218             if (anchor === 'left' && Math.abs(p1.coords.usrCoords[2] - p2.coords.usrCoords[2]) < Mat.eps) {
219                 return tick.usrCoords[1];
220             }
221 
222             // vertical axis
223             if (anchor === 'left' && Math.abs(p1.coords.usrCoords[1] - p2.coords.usrCoords[1]) < Mat.eps) {
224                 return tick.usrCoords[2];
225             }
226 
227             if ((this.visProp.anchor === 'right' && !Geometry.isSameDirection(p2.coords, p1.coords, tick)) ||
228                     (this.visProp.anchor !== 'right' && Geometry.isSameDirection(p1.coords, p2.coords, tick))) {
229                 f = 1;
230             }
231 
232             return f * center.distance(Const.COORDS_BY_USER, tick);
233         },
234 
235         /**
236          * Sets x and y coordinate of the tick.
237          * @param {number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
238          * @param {Array} coords coordinates in screen/user units
239          * @param {Array} oldcoords previous coordinates in screen/user units
240          * @returns {JXG.Ticks} this element
241          */
242         setPositionDirectly: function (method, coords, oldcoords) {
243             var dx, dy, i,
244                 c = new Coords(method, coords, this.board),
245                 oldc = new Coords(method, oldcoords, this.board),
246                 bb = this.board.getBoundingBox();
247 
248 
249             if (!this.line.visProp.scalable) {
250                 return this;
251             }
252 
253             // horizontal line
254             if (Math.abs(this.line.stdform[1]) < Mat.eps && Math.abs(c.usrCoords[1] * oldc.usrCoords[1]) > Mat.eps) {
255                 dx = oldc.usrCoords[1] / c.usrCoords[1];
256                 bb[0] *= dx;
257                 bb[2] *= dx;
258                 this.board.setBoundingBox(bb, false);
259             // vertical line
260             } else if (Math.abs(this.line.stdform[2]) < Mat.eps && Math.abs(c.usrCoords[2] * oldc.usrCoords[2]) > Mat.eps) {
261                 dy = oldc.usrCoords[2] / c.usrCoords[2];
262                 bb[3] *= dy;
263                 bb[1] *= dy;
264                 this.board.setBoundingBox(bb, false);
265             }
266 
267             return this;
268         },
269 
270         /**
271          * (Re-)calculates the ticks coordinates.
272          */
273         calculateTicksCoordinates: function () {
274             var center, d, bb, perp, coordsZero,
275                 symbTicksDelta, f,
276                 // Point 1 of the line
277                 p1 = this.line.point1,
278                 // Point 2 of the line
279                 p2 = this.line.point2,
280                 // Distance between the two points from above
281                 distP1P2 = p1.Dist(p2),
282                 // Distance of X coordinates of two major ticks
283                 // Initialized with the distance of Point 1 to a point between Point 1 and Point 2 on the line and with distance 1
284                 // this equals always 1 for lines parallel to x = 0 or y = 0. It's only important for lines other than that.
285                 deltaX = (p2.coords.usrCoords[1] - p1.coords.usrCoords[1]) / distP1P2,
286                 // The same thing for Y coordinates
287                 deltaY = (p2.coords.usrCoords[2] - p1.coords.usrCoords[2]) / distP1P2,
288                 // Distance of p1 to the unit point in screen coordinates
289                 distScr = p1.coords.distance(Const.COORDS_BY_SCREEN, new Coords(Const.COORDS_BY_USER, [p1.coords.usrCoords[1] + deltaX, p1.coords.usrCoords[2] + deltaY], this.board)),
290                 // Distance between two major ticks in user coordinates
291                 ticksDelta = (this.equidistant ? this.ticksFunction(1) : 1),
292                 // This factor is for enlarging ticksDelta and it switches between 5 and 2
293                 // Hence, if two major ticks are too close together they'll be expanded to a distance of 5
294                 // if they're still too close together, they'll be expanded to a distance of 10 etc
295                 factor = 5,
296                 // Coordinates of the current tick
297                 tickCoords,
298                 // Coordinates of the first drawn tick
299                 startTick, symbStartTick,
300                 // two counters
301                 i, j,
302                 // the distance of the tick to p1. Is displayed on the board using a label
303                 // for majorTicks
304                 tickPosition,
305                 symbTickPosition,
306                 // infinite or finite tick length
307                 style,
308                 // new position
309                 nx = 0,
310                 ny = 0,
311                 ti,
312                 dirs = 2,
313                 dir = -1,
314 
315                 // the following variables are used to define ticks height and slope
316                 eps = Mat.eps,
317                 pos, lb, ub,
318                 distMaj = this.visProp.majorheight * 0.5,
319                 distMin = this.visProp.minorheight * 0.5,
320                 // ticks width and height in screen units
321                 dxMaj, dyMaj,
322                 dxMin, dyMin,
323                 // ticks width and height in user units
324                 dx, dy,
325                 oldRepoLength = this.labelsRepo.length;
326             // END OF variable declaration
327 
328             // This will trap this update routine in an endless loop. Besides, there's not much we can show
329             // on such a tiny board, so we just get out of here immediately.
330             if (this.board.canvasWidth === 0 || this.board.canvasHeight === 0) {
331                 return;
332             }
333 
334             // Grid-like ticks
335             if (this.visProp.minorheight < 0) {
336                 this.minStyle = 'infinite';
337             } else {
338                 this.minStyle = 'finite';
339             }
340 
341             if (this.visProp.majorheight < 0) {
342                 this.majStyle = 'infinite';
343             } else {
344                 this.majStyle = 'finite';
345             }
346 
347             if (this.visProp.anchor === 'right') {
348                 coordsZero = p2.coords;
349             } else if (this.visProp.anchor === 'middle') {
350                 coordsZero = new Coords(JXG.COORDS_BY_USER, [
351                     (p1.coords.usrCoords[1] + p2.coords.usrCoords[1]) / 2,
352                     (p1.coords.usrCoords[2] + p2.coords.usrCoords[2]) / 2
353                 ], this.board);
354             } else {
355                 coordsZero = p1.coords;
356             }
357 
358             // Set lower and upper bound for the tick distance.
359             // This is necessary for segments.
360             if (this.line.visProp.straightfirst) {
361                 lb = Number.NEGATIVE_INFINITY;
362             } else {
363                 if (this.visProp.anchor === 'middle') {
364                     lb = -distP1P2 / 2 + eps;
365                 } else if (this.visProp.anchor === 'right') {
366                     lb = -distP1P2 + eps;
367                 } else {
368                     lb = eps;
369                 }
370             }
371 
372             if (this.line.visProp.straightlast) {
373                 ub = Number.POSITIVE_INFINITY;
374             } else {
375                 if (this.visProp.anchor === 'middle') {
376                     ub = distP1P2 / 2 - eps;
377                 } else if (this.visProp.anchor === 'right') {
378                     ub = -eps;
379                 } else {
380                     ub = distP1P2 - eps;
381                 }
382             }
383 
384             // This piece of code used to be in AbstractRenderer.updateAxisTicksInnerLoop
385             // and has been moved in here to clean up the renderers code.
386             //
387             // The code above only calculates the position of the ticks. The following code parts
388             // calculate the dx and dy values which make ticks out of this positions, i.e. from the
389             // position (p_x, p_y) calculated above we have to draw a line from
390             // (p_x - dx, py - dy) to (p_x + dx, p_y + dy) to get a tick.
391             dxMaj = this.line.stdform[1];
392             dyMaj = this.line.stdform[2];
393             dxMin = dxMaj;
394             dyMin = dyMaj;
395             dx = dxMaj;
396             dy = dyMaj;
397 
398             // After this, the length of the vector (dxMaj, dyMaj) in screen coordinates is equal to distMaj pixel.
399             d = Math.sqrt(dxMaj * dxMaj * this.board.unitX * this.board.unitX + dyMaj * dyMaj * this.board.unitY * this.board.unitY);
400             dxMaj *= distMaj / d * this.board.unitX;
401             dyMaj *= distMaj / d * this.board.unitY;
402             dxMin *= distMin / d * this.board.unitX;
403             dyMin *= distMin / d * this.board.unitY;
404 
405             // Begin cleanup
406             this.removeTickLabels();
407 
408             // If the parent line is not finite, we can stop here.
409             if (Math.abs(dx) < Mat.eps && Math.abs(dy) < Mat.eps) {
410                 return;
411             }
412 
413             // initialize storage arrays
414             // ticks stores the ticks coordinates
415             this.ticks = [];
416 
417             // labels stores the text to display beside the ticks
418             this.labels = [];
419             // END cleanup
420 
421             // we have an array of fixed ticks we have to draw
422             if (!this.equidistant) {
423                 for (i = 0; i < this.fixedTicks.length; i++) {
424                     nx = coordsZero.usrCoords[1] + this.fixedTicks[i] * deltaX;
425                     ny = coordsZero.usrCoords[2] + this.fixedTicks[i] * deltaY;
426                     tickCoords = new Coords(Const.COORDS_BY_USER, [nx, ny], this.board);
427                     ti = this._tickEndings(tickCoords, dx, dy, dxMaj, dyMaj, dxMin, dyMin, /*major:*/ true);
428 
429                     // Compute the start position and the end position of a tick.
430                     // If both positions are out of the canvas, ti is empty.
431                     if (ti.length === 3 && this.fixedTicks[i] >= lb && this.fixedTicks[i] < ub) {
432                         this.ticks.push(ti);
433                     }
434 
435                     this.labels.push(this._makeLabel(this.visProp.labels[i] || this.fixedTicks[i], tickCoords, this.board, this.visProp.drawlabels, this.id, i, coordsZero));
436                     // visibility test missing
437                 }
438                 return;
439             }
440 
441             // ok, we have equidistant ticks and not special ticks, so we continue here with generating them:
442 
443             symbTicksDelta = ticksDelta;
444             ticksDelta *= this.visProp.scale;
445 
446             // adjust distances
447             if (this.visProp.insertticks && this.minTicksDistance > Mat.eps) {
448                 f = this._adjustTickDistance(ticksDelta, distScr, factor, coordsZero, deltaX, deltaY);
449                 ticksDelta *= f;
450                 symbTicksDelta *= f;
451             }
452 
453             if (!this.visProp.insertticks) {
454                 ticksDelta /= this.visProp.minorticks + 1;
455                 symbTicksDelta /= this.visProp.minorticks + 1;
456             }
457             this.ticksDelta = ticksDelta;
458 
459 
460 
461             // We shoot into the middle of the canvas
462             // to the tick position which is closest to the center
463             // of the canvas. We do this by an orthogonal projection
464             // of the canvas center to the line and by rounding of the
465             // distance of the projected point to point1 of the line.
466             // This position is saved in
467             // center and startTick.
468             bb = this.board.getBoundingBox();
469             nx = (bb[0] + bb[2]) * 0.5;
470             ny = (bb[1] + bb[3]) * 0.5;
471 
472             // Project the center of the canvas to the line.
473             perp = [
474                 nx * this.line.stdform[2] - ny * this.line.stdform[1],
475                 -this.line.stdform[2],
476                 this.line.stdform[1]
477             ];
478             center = Mat.crossProduct(this.line.stdform, perp);
479             center[1] /= center[0];
480             center[2] /= center[0];
481             center[0] = 1;
482 
483             // Round the distance of center to point1
484             tickCoords = new Coords(Const.COORDS_BY_USER, center, this.board);
485             d = coordsZero.distance(Const.COORDS_BY_USER, tickCoords);
486 
487             if ((p2.X() - p1.X()) * (center[1] - p1.X()) < 0 || (p2.Y() - p1.Y()) * (center[2] - p1.Y()) < 0) {
488                 d *= -1;
489             }
490             tickPosition = Math.round(d / ticksDelta) * ticksDelta;
491 
492             // Find the correct direction of center from point1
493             if (Math.abs(tickPosition) > Mat.eps) {
494                 dir = Math.abs(tickPosition) / tickPosition;
495             }
496 
497             // From now on, we jump around center
498             center[1] = coordsZero.usrCoords[1] + deltaX * tickPosition;
499             center[2] = coordsZero.usrCoords[2] + deltaY * tickPosition;
500             startTick = tickPosition;
501             tickPosition = 0;
502 
503             symbTickPosition = 0;
504             // this could be done more elaborate to prevent rounding errors
505             symbStartTick = startTick / this.visProp.scale;
506 
507             nx = center[1];
508             ny = center[2];
509 
510             // counter for label ids
511             i = 0;
512             j = 0;
513 
514             // Now, we jump around the center
515             // until we are outside of the canvas.
516             // If this is the case we proceed in the other
517             // direction until we are out of the canvas in this direction, too.
518             // Then we are done.
519             do {
520                 tickCoords = new Coords(Const.COORDS_BY_USER, [nx, ny], this.board);
521 
522                 // Test if tick is a major tick.
523                 // This is the case if (dir*tickPosition+startTick)/ticksDelta is
524                 // a multiple of the number of minorticks+1
525                 tickCoords.major = Math.round((dir * tickPosition + startTick) / ticksDelta) % (this.visProp.minorticks + 1) === 0;
526 
527                 // Compute the start position and the end position of a tick.
528                 // If both positions are out of the canvas, ti is empty.
529                 ti = this._tickEndings(tickCoords, dx, dy, dxMaj, dyMaj, dxMin, dyMin, tickCoords.major);
530 
531                 // The tick has an overlap with the board?
532                 if (ti.length === 3) {
533                     pos = dir * symbTickPosition + symbStartTick;
534                     if ((Math.abs(pos) >= eps || this.visProp.drawzero) && (pos > lb && pos < ub)) {
535                         this.ticks.push(ti);
536 
537                         if (tickCoords.major) {
538                             this.labels.push(this._makeLabel(pos, tickCoords, this.board, this.visProp.drawlabels, this.id, i, coordsZero));
539                         } else {
540                             this.labels.push(null);
541                         }
542                         i++;
543                     }
544 
545                     // Toggle direction
546                     if (dirs === 2) {
547                         dir *= (-1);
548                     }
549 
550                     // Increase distance from center
551                     if (j % 2 === 0 || dirs === 1) {
552                         tickPosition += ticksDelta;
553                         symbTickPosition += symbTicksDelta;
554                     }
555                 } else {
556                     dir *= (-1);
557                     dirs -= 1;
558                 }
559 
560                 j++;
561 
562                 nx = center[1] + dir * deltaX * tickPosition;
563                 ny = center[2] + dir * deltaY * tickPosition;
564             } while (dirs > 0);
565 
566             for (i = oldRepoLength; i < this.labelsRepo.length; i++) {
567                 this.labelsRepo[i].setAttribute({visible: false});
568             }
569 
570             this.needsUpdate = true;
571             this.updateRenderer();
572         },
573 
574         /**
575          * @private
576          */
577         _adjustTickDistance: function (ticksDelta, distScr, factor, p1c, deltaX, deltaY) {
578             var nx, ny, f = 1;
579 
580             while (distScr > 4 * this.minTicksDistance) {
581                 f /= 10;
582                 nx = p1c.usrCoords[1] + deltaX * ticksDelta * f;
583                 ny = p1c.usrCoords[2] + deltaY * ticksDelta * f;
584                 distScr = p1c.distance(Const.COORDS_BY_SCREEN, new Coords(Const.COORDS_BY_USER, [nx, ny], this.board));
585             }
586 
587             // If necessary, enlarge ticksDelta
588             while (distScr <= this.minTicksDistance) {
589                 f *= factor;
590                 factor = (factor === 5 ? 2 : 5);
591                 nx = p1c.usrCoords[1] + deltaX * ticksDelta * f;
592                 ny = p1c.usrCoords[2] + deltaY * ticksDelta * f;
593                 distScr = p1c.distance(Const.COORDS_BY_SCREEN, new Coords(Const.COORDS_BY_USER, [nx, ny], this.board));
594             }
595 
596             return f;
597         },
598 
599         /**
600          * @param {JXG.Coords} coords Coordinates of the tick on the line.
601          * @param {Number} dx horizontal tick extension in user coordinates.
602          * @param {Number} dy vertical tick extension in user coordinates.
603          * @param {Number} dxMaj horizontal tick direction in screen coordinates.
604          * @param {Number} dyMaj vertical tick direction in screen coordinates.
605          * @param {Number} dxMin horizontal tick direction in screen coordinates.
606          * @param {Number} dyMin vertical tick direction in screen coordinates.
607          * @param {Boolean} major True if tick is major tick.
608          * @return {Array} Array of length 3 containing start and end coordinates in screen coordinates
609          *                 of the tick (arrays of length 2). 3rd entry is true if major tick otherwise false.
610          *                 If the tick is outside of the canvas, the return array is empty.
611          * @private
612          */
613         _tickEndings: function (coords, dx, dy, dxMaj, dyMaj, dxMin, dyMin, major) {
614             var i, c,
615                 cw = this.board.canvasWidth,
616                 ch = this.board.canvasHeight,
617                 x = [-1000 * cw, -1000 * ch],
618                 y = [-1000 * cw, -1000 * ch],
619                 dxs, dys,
620                 s, style,
621                 count = 0,
622                 isInsideCanvas = false;
623 
624             c = coords.scrCoords;
625             if (major) {
626                 dxs = dxMaj;
627                 dys = dyMaj;
628                 style = this.majStyle;
629             } else {
630                 dxs = dxMin;
631                 dys = dyMin;
632                 style = this.minStyle;
633             }
634 
635             // For all ticks regardless if of finite or infinite
636             // tick length the intersection with the canvas border is
637             // computed.
638 
639             // horizontal line and vertical tick
640             if (Math.abs(dx) < Mat.eps) {
641                 x[0] = c[1];
642                 x[1] = c[1];
643                 y[0] = 0;
644                 y[1] = ch;
645                 // vertical line and horizontal tick
646             } else if (Math.abs(dy) < Mat.eps) {
647                 x[0] = 0;
648                 x[1] = cw;
649                 y[0] = c[2];
650                 y[1] = c[2];
651                 // other
652             } else {
653                 count = 0;
654 
655                 // intersect with top
656                 s = Mat.crossProduct([0, 0, 1], [-dys * c[1] - dxs * c[2], dys, dxs]);
657                 s[1] /= s[0];
658                 if (s[1] >= 0 && s[1] <= cw) {
659                     x[count] = s[1];
660                     y[count] = 0;
661                     count++;
662                 }
663 
664                 // intersect with left
665                 s = Mat.crossProduct([0, 1, 0], [-dys * c[1] - dxs * c[2], dys, dxs]);
666                 s[2] /= s[0];
667                 if (s[2] >= 0 && s[2] <= ch) {
668                     x[count] = 0;
669                     y[count] = s[2];
670                     count++;
671                 }
672 
673                 if (count < 2) {
674                     // intersect with bottom
675                     s = Mat.crossProduct([ch * ch, 0, -ch], [-dys * c[1] - dxs * c[2], dys, dxs]);
676                     s[1] /= s[0];
677                     if (s[1] >= 0 && s[1] <= cw) {
678                         x[count] = s[1];
679                         y[count] = ch;
680                         count++;
681                     }
682                 }
683                 if (count < 2) {
684                     // intersect with right
685                     s = Mat.crossProduct([cw * cw, -cw, 0], [-dys * c[1] - dxs * c[2], dys, dxs]);
686                     s[2] /= s[0];
687                     if (s[2] >= 0 && s[2] <= ch) {
688                         x[count] = cw;
689                         y[count] = s[2];
690                     }
691                 }
692             }
693 
694             isInsideCanvas = (x[0] >= 0 && x[0] <= cw && y[0] >= 0 && y[0] <= ch) ||
695                 (x[1] >= 0 && x[1] <= cw && y[1] >= 0 && y[1] <= ch);
696 
697             // finite tick length
698             if (style === 'finite') {
699                 x[0] = c[1] + dxs * this.visProp.tickendings[0];
700                 y[0] = c[2] - dys * this.visProp.tickendings[0];
701                 x[1] = c[1] - dxs * this.visProp.tickendings[1];
702                 y[1] = c[2] + dys * this.visProp.tickendings[1];
703             }
704 
705             if (isInsideCanvas) {
706                 return [x, y, major];
707             }
708 
709             return [];
710         },
711 
712         /**
713          * Create a tick label
714          * @param {Number} pos
715          * @param {JXG.Coords} newTick
716          * @param {JXG.Board} board
717          * @param {Boolean} drawLabels
718          * @param {String} id Id of the ticks object
719          * @param {Number} i
720          * @param {JXG.Coords} center
721          * @returns {JXG.Text}
722          * @private
723          */
724         _makeLabel: function (pos, newTick, board, drawLabels, id, i, center) {
725             var labelText, label, attr,
726                 num = (typeof pos === 'number');
727 
728             if (!drawLabels) {
729                 return null;
730             }
731 
732             // Correct label also for frozen tick lines.
733             if (this.equidistant) {
734                 pos = this.generateLabelValue(newTick, center) / this.visProp.scale;
735             }
736 
737             labelText = pos.toString();
738             if (newTick.distance(Const.COORDS_BY_USER, center) < Mat.eps) {
739                 labelText = '0';
740             }
741 
742             if (num && (labelText.length > this.visProp.maxlabellength || labelText.indexOf('e') !== -1)) {
743                 labelText = pos.toPrecision(this.visProp.precision).toString();
744             }
745             if (num && labelText.indexOf('.') > -1 && labelText.indexOf('e') === -1) {
746                 // trim trailing zeros
747                 labelText = labelText.replace(/0+$/, '');
748                 // trim trailing .
749                 labelText = labelText.replace(/\.$/, '');
750             }
751 
752             if (this.visProp.scalesymbol.length > 0 && labelText === '1') {
753                 labelText = this.visProp.scalesymbol;
754             } else if (this.visProp.scalesymbol.length > 0 && labelText === '0') {
755                 labelText = '0';
756             } else {
757                 labelText = labelText + this.visProp.scalesymbol;
758             }
759 
760             attr = {
761                 isLabel: true,
762                 layer: board.options.layer.line,
763                 highlightStrokeColor: board.options.text.strokeColor,
764                 highlightStrokeWidth: board.options.text.strokeWidth,
765                 highlightStrokeOpacity: board.options.text.strokeOpacity,
766                 visible: this.visProp.visible,
767                 priv: this.visProp.priv
768             };
769             attr = Type.deepCopy(attr, this.visProp.label);
770 
771             if (this.labelsRepo.length > 0) {
772                 label = this.labelsRepo.splice(this.labelsRepo.length - 1, 1)[0];
773                 // this is done later on anyways
774                 //label.setCoords(newTick.usrCoords[1], newTick.usrCoords[2]);
775                 label.setText(labelText);
776                 label.setAttribute(attr);
777             } else {
778                 this.labelCounter += 1;
779                 attr.id = id + i + 'Label' + this.labelCounter;
780                 label = Text.createText(board, [newTick.usrCoords[1], newTick.usrCoords[2], labelText], attr);
781             }
782             label.isDraggable = false;
783             label.dump = false;
784 
785             /*
786              * Ticks have their own label handling which is done below and not
787              * in Text.update().
788              * The reason is that there is no parent element for the labels
789              * which can determine the label position.
790              */
791             //label.distanceX = 4;
792             //label.distanceY = -parseInt(label.visProp.fontsize)+3; //-9;
793             label.distanceX = this.visProp.label.offset[0];
794             label.distanceY = this.visProp.label.offset[1];
795             label.setCoords(newTick.usrCoords[1] + label.distanceX / (board.unitX),
796                 newTick.usrCoords[2] + label.distanceY / (board.unitY));
797 
798             label.visProp.visible = drawLabels;
799             //label.prepareUpdate().update().updateRenderer();
800             return label;
801         },
802 
803         /**
804          * Removes the HTML divs of the tick labels
805          * before repositioning
806          * @private
807          */
808         removeTickLabels: function () {
809             var j;
810 
811             // remove existing tick labels
812             if (Type.exists(this.labels)) {
813                 if ((this.board.needsFullUpdate || this.needsRegularUpdate || this.needsUpdate) &&
814                         !(this.board.renderer.type === 'canvas' && this.board.options.text.display === 'internal')) {
815                     for (j = 0; j < this.labels.length; j++) {
816                         if (Type.exists(this.labels[j])) {
817                             //this.board.removeObject(this.labels[j]);
818                             this.labelsRepo.push(this.labels[j]);
819                         }
820                     }
821                 }
822             }
823         },
824 
825         /**
826          * Recalculate the tick positions and the labels.
827          * @returns {JXG.Ticks}
828          */
829         update: function () {
830             if (this.needsUpdate) {
831                 this.calculateTicksCoordinates();
832             }
833 
834             return this;
835         },
836 
837         /**
838          * Uses the boards renderer to update the arc.
839          * @returns {JXG.Ticks}
840          */
841         updateRenderer: function () {
842             if (this.needsUpdate) {
843                 if (this.ticks) {
844                     this.board.renderer.updateTicks(this, this.dxMaj, this.dyMaj, this.dxMin, this.dyMin, this.minStyle, this.majStyle);
845                 }
846                 this.needsUpdate = false;
847             }
848 
849             return this;
850         },
851 
852         hideElement: function () {
853             var i;
854 
855             this.visProp.visible = false;
856             this.board.renderer.hide(this);
857 
858             for (i = 0; i < this.labels.length; i++) {
859                 if (Type.exists(this.labels[i])) {
860                     this.labels[i].hideElement();
861                 }
862             }
863 
864             return this;
865         },
866 
867         showElement: function () {
868             var i;
869 
870             this.visProp.visible = true;
871             this.board.renderer.show(this);
872 
873             for (i = 0; i < this.labels.length; i++) {
874                 if (Type.exists(this.labels[i])) {
875                     this.labels[i].showElement();
876                 }
877             }
878 
879             return this;
880         }
881     });
882 
883     /**
884      * @class Ticks are used as distance markers on a line.
885      * @pseudo
886      * @description
887      * @name Ticks
888      * @augments JXG.Ticks
889      * @constructor
890      * @type JXG.Ticks
891      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
892      * @param {JXG.Line,Number,Function} line,_distance,_generateLabelFunc The parents consist of the line the ticks are going to be attached to and the
893      * distance between two major ticks.
894      * 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.
895      * @example
896      * // Create an axis providing two coord pairs.
897      *   var p1 = board.create('point', [0, 3]);
898      *   var p2 = board.create('point', [1, 3]);
899      *   var l1 = board.create('line', [p1, p2]);
900      *   var t = board.create('ticks', [l1], {ticksDistance: 2});
901      * </pre><div id="ee7f2d68-75fc-4ec0-9931-c76918427e63" style="width: 300px; height: 300px;"></div>
902      * <script type="text/javascript">
903      * (function () {
904      *   var board = JXG.JSXGraph.initBoard('ee7f2d68-75fc-4ec0-9931-c76918427e63', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false});
905      *   var p1 = board.create('point', [0, 3]);
906      *   var p2 = board.create('point', [1, 3]);
907      *   var l1 = board.create('line', [p1, p2]);
908      *   var t = board.create('ticks', [l1, 2], {ticksDistance: 2});
909      * })();
910      * </script><pre>
911      */
912     JXG.createTicks = function (board, parents, attributes) {
913         var el, dist,
914             attr = Type.copyAttributes(attributes, board.options, 'ticks');
915 
916         if (parents.length < 2) {
917             dist = attr.ticksdistance;
918         } else {
919             dist = parents[1];
920         }
921 
922         if (parents[0].elementClass === Const.OBJECT_CLASS_LINE) {
923             el = new JXG.Ticks(parents[0], dist, attr);
924         } else {
925             throw new Error("JSXGraph: Can't create Ticks with parent types '" + (typeof parents[0]) + "'.");
926         }
927 
928         if (typeof attr.generatelabelvalue === 'function') {
929             el.generateLabelValue = attr.generatelabelvalue;
930         }
931 
932         el.isDraggable = true;
933 
934         return el;
935     };
936 
937     /**
938      * @class Hashes can be used to mark congruent lines.
939      * @pseudo
940      * @description
941      * @name Hatch
942      * @augments JXG.Ticks
943      * @constructor
944      * @type JXG.Ticks
945      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
946      * @param {JXG.Line,Number} line,numberofhashes The parents consist of the line the hatch marks are going to be attached to and the
947      * number of dashes.
948      * @example
949      * // Create an axis providing two coord pairs.
950      *   var p1 = board.create('point', [0, 3]);
951      *   var p2 = board.create('point', [1, 3]);
952      *   var l1 = board.create('line', [p1, p2]);
953      *   var t = board.create('hatch', [l1, 3]);
954      * </pre><div id="4a20af06-4395-451c-b7d1-002757cf01be" style="width: 300px; height: 300px;"></div>
955      * <script type="text/javascript">
956      * (function () {
957      *   var board = JXG.JSXGraph.initBoard('4a20af06-4395-451c-b7d1-002757cf01be', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false});
958      *   var p1 = board.create('point', [0, 3]);
959      *   var p2 = board.create('point', [1, 3]);
960      *   var l1 = board.create('line', [p1, p2]);
961      *   var t = board.create('hatch', [l1, 3]);
962      * })();
963      * </script><pre>
964      */
965     JXG.createHatchmark = function (board, parents, attributes) {
966         var num, i, base, width, totalwidth, el,
967             pos = [],
968             attr = Type.copyAttributes(attributes, board.options, 'hatch');
969 
970         if (parents[0].elementClass !== Const.OBJECT_CLASS_LINE || typeof parents[1] !== 'number') {
971             throw new Error("JSXGraph: Can't create Hatch mark with parent types '" + (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'.");
972         }
973 
974         num = parents[1];
975         width = attr.ticksdistance;
976         totalwidth = (num - 1) * width;
977         base = -totalwidth / 2;
978 
979         for (i = 0; i < num; i++) {
980             pos[i] = base + i * width;
981         }
982 
983         el = board.create('ticks', [parents[0], pos], attr);
984         el.elType = 'hatch';
985     };
986 
987     JXG.registerElement('ticks', JXG.createTicks);
988     JXG.registerElement('hash', JXG.createHatchmark);
989     JXG.registerElement('hatch', JXG.createHatchmark);
990 
991     return {
992         Ticks: JXG.Ticks,
993         createTicks: JXG.createTicks,
994         createHashmark: JXG.createHatchmark,
995         createHatchmark: JXG.createHatchmark
996     };
997 });
998