1 /*
  2     Copyright 2008,2009
  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: you can redistribute it and/or modify
 13     it under the terms of the GNU Lesser General Public License as published by
 14     the Free Software Foundation, either version 3 of the License, or
 15     (at your option) any later version.
 16 
 17     JSXGraph is distributed in the hope that it will be useful,
 18     but WITHOUT ANY WARRANTY; without even the implied warranty of
 19     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 20     GNU Lesser General Public License for more details.
 21 
 22     You should have received a copy of the GNU Lesser General Public License
 23     along with JSXGraph.  If not, see <http://www.gnu.org/licenses/>.
 24 */
 25 
 26 /**
 27  * @fileoverview In this file the geometry object Ticks is defined. Ticks provides
 28  * methods for creation and management of ticks on an axis.
 29  * @author graphjs
 30  * @version 0.1
 31  */
 32 
 33 /**
 34  * Creates ticks for an axis.
 35  * @class Ticks provides methods for creation and management
 36  * of ticks on an axis.
 37  * @param {JXG.Line} line Reference to the axis the ticks are drawn on.
 38  * @param {Number|Array} ticks Number defining the distance between two major ticks or an array defining static ticks.
 39  * @param {Object} attributes Properties
 40  * @see JXG.Line#addTicks
 41  * @constructor
 42  * @extends JXG.GeometryElement
 43  */
 44 JXG.Ticks = function (line, ticks, attributes) {
 45     this.constructor(line.board, attributes, JXG.OBJECT_TYPE_TICKS, JXG.OBJECT_CLASS_OTHER);
 46 
 47     /**
 48      * The line the ticks belong to.
 49      * @type JXG.Line
 50      */
 51     this.line = line;
 52 
 53     /**
 54      * The board the ticks line is drawn on.
 55      * @type JXG.Board
 56      */
 57     this.board = this.line.board;
 58 
 59     /**
 60      * A function calculating ticks delta depending on the ticks number.
 61      * @type Function
 62      */
 63     this.ticksFunction = null;
 64 
 65     /**
 66      * Array of fixed ticks.
 67      * @type Array
 68      */
 69     this.fixedTicks = null;
 70 
 71     /**
 72      * Equidistant ticks. Distance is defined by ticksFunction
 73      * @type Boolean
 74      */
 75     this.equidistant = false;
 76 
 77     if(JXG.isFunction(ticks)) {
 78         this.ticksFunction = ticks;
 79         throw new Error("Function arguments are no longer supported.");
 80     } else if(JXG.isArray(ticks)) {
 81         this.fixedTicks = ticks;
 82     } else {
 83         if(Math.abs(ticks) < JXG.Math.eps)
 84             ticks = attributes.defaultdistance;
 85         this.ticksFunction = function (i) { return ticks; };
 86         this.equidistant = true;
 87     }
 88 
 89     /**
 90      * Least distance between two ticks, measured in pixels.
 91      * @type int
 92      */
 93     this.minTicksDistance = attributes.minticksdistance;
 94 
 95     /**
 96      * Maximum distance between two ticks, measured in pixels. Is used only when insertTicks
 97      * is set to true.
 98      * @type int
 99      * @see #insertTicks
100      * @deprecated This value will be ignored.
101      */
102     this.maxTicksDistance = attributes.maxticksdistance;
103 
104     /**
105      * Array where the labels are saved. There is an array element for every tick,
106      * even for minor ticks which don't have labels. In this case the array element
107      * contains just <tt>null</tt>.
108      * @type Array
109      */
110     this.labels = [];
111 
112     this.id = this.line.addTicks(this);
113     this.board.setId(this,'Ti');
114 };
115 
116 JXG.Ticks.prototype = new JXG.GeometryElement();
117 
118 JXG.extend(JXG.Ticks.prototype, /** @lends JXG.Ticks.prototype */ {
119     /**
120      * Checks whether (x,y) is near the line.
121      * @param {Number} x Coordinate in x direction, screen coordinates.
122      * @param {Number} y Coordinate in y direction, screen coordinates.
123      * @return {Boolean} True if (x,y) is near the line, False otherwise.
124      */
125     hasPoint: function (x, y) {
126         var i, t,
127             len = this.ticks.length,
128             r = this.board.options.precision.hasPoint;
129             
130         if (!this.line.visProp.scalable) {
131             return false;
132         }
133         
134         // Ignore non-axes and axes that are not horizontal or vertical
135         if (this.line.stdform[1]!=0 
136             && this.line.stdform[2]!=0
137             && this.line.type!=JXG.OBJECT_TYPE_AXIS) {  
138             
139             return false;
140         }
141 
142         for (i=0; i<len; i++) {
143             t = this.ticks[i];
144             if (!t[2]) continue;       // Skip minor ticks
145             
146             // Ignore ticks at zero
147             if (this.line.stdform[1]==0 && Math.abs(t[0][0]-this.line.point1.coords.scrCoords[1])<JXG.Math.eps) {
148                 continue;
149             } else if (this.line.stdform[2]==0 && Math.abs(t[1][0]-this.line.point1.coords.scrCoords[2])<JXG.Math.eps) {
150                 continue;
151             }
152                 
153             if (Math.abs(t[0][0]-t[0][1])>=1 || Math.abs(t[1][0]-t[1][1])>=1) {    // tick length is not zero, ie. at least one pixel
154                 /*
155                 if (t[0][0]-r < x 
156                     && x < t[0][1]+r 
157                     && t[1][0]-r < y 
158                     && y < t[1][1]+r) {
159                 
160                     return true;
161                 }
162                 */
163                 if (this.line.stdform[1]==0) {
164                     if (Math.abs(y - (t[1][0]+t[1][1])*0.5) < 2*r    // Allow dragging near axes only.
165                         && t[0][0]-r < x
166                         && x < t[0][1]+r) {
167                 
168                         return true;
169                     }
170                 } else if  (this.line.stdform[2]==0) {
171                     if (Math.abs(x - (t[0][0]+t[0][1])*0.5) < 2*r  
172                         && t[1][0]-r < y
173                         && y < t[1][1]+r) {
174                 
175                         return true;
176                     }
177                 }
178             }
179         }
180         
181        return false;
182     },
183 
184     setPositionDirectly: function (method, coords, oldcoords) {
185         var dx, dy, i, 
186             c = new JXG.Coords(method, coords, this.board),
187             oldc = new JXG.Coords(method, oldcoords, this.board),
188             bb = this.board.getBoundingBox();
189 
190         
191         if (!this.line.visProp.scalable) {
192             return this;
193         }
194         
195         if (Math.abs(this.line.stdform[1])<JXG.Math.eps         // horizontal line     
196             && Math.abs(c.usrCoords[1]*oldc.usrCoords[1])>JXG.Math.eps) {  
197             dx =oldc.usrCoords[1] / c.usrCoords[1];
198             bb[0] *= dx;
199             bb[2] *= dx;
200             this.board.setBoundingBox(bb, false);
201         } else if (Math.abs(this.line.stdform[2])<JXG.Math.eps // vertical line
202             && Math.abs(c.usrCoords[2]*oldc.usrCoords[2])>JXG.Math.eps) { 
203             dy = oldc.usrCoords[2] / c.usrCoords[2];
204             bb[3] *= dy;
205             bb[1] *= dy;
206             this.board.setBoundingBox(bb, false);
207         }
208         //this.board.mode = this.board.BOARD_MODE_DRAG;
209         //this.board.needsFullUpdate = true;
210         
211         return this;
212     },
213     
214 
215     /**
216      * (Re-)calculates the ticks coordinates.
217      */
218     calculateTicksCoordinates: function() {
219             // Point 1 of the line
220         var p1 = this.line.point1,
221             // Point 2 of the line
222             p2 = this.line.point2,
223             // Distance between the two points from above
224             distP1P2 = p1.Dist(p2),
225             // Distance of X coordinates of two major ticks
226             // Initialized with the distance of Point 1 to a point between Point 1 and Point 2 on the line and with distance 1
227             // this equals always 1 for lines parallel to x = 0 or y = 0. It's only important for lines other than that.
228             deltaX = (p2.coords.usrCoords[1] - p1.coords.usrCoords[1])/distP1P2,
229             // The same thing for Y coordinates
230             deltaY = (p2.coords.usrCoords[2] - p1.coords.usrCoords[2])/distP1P2,
231             // Distance of p1 to the unit point in screen coordinates
232             distScr = p1.coords.distance(JXG.COORDS_BY_SCREEN, new JXG.Coords(JXG.COORDS_BY_USER, [p1.coords.usrCoords[1] + deltaX, p1.coords.usrCoords[2] + deltaY], this.board)),
233             // Distance between two major ticks in user coordinates
234             ticksDelta = (this.equidistant ? this.ticksFunction(1) : 1),
235             symbTicksDelta, f,
236             // This factor is for enlarging ticksDelta and it switches between 5 and 2
237             // Hence, if two major ticks are too close together they'll be expanded to a distance of 5
238             // if they're still too close together, they'll be expanded to a distance of 10 etc
239             factor = 5,
240             // Coordinates of the current tick
241             tickCoords,
242             // Coordinates of the first drawn tick
243             startTick,
244             symbStartTick,
245             // two counters
246             i, j,
247             // the distance of the tick to p1. Is displayed on the board using a label
248             // for majorTicks
249             tickPosition,
250             symbTickPosition,
251             // infinite or finite tick length
252             style,
253             // new position
254             nx = 0,
255             ny = 0,
256             ti,
257 			dirs = 2, dir = -1, 
258 			center, d, bb, perp,
259             
260             // the following variables are used to define ticks height and slope
261             eps = JXG.Math.eps, pos, lb, ub,
262             distMaj = this.visProp.majorheight * 0.5,
263             distMin = this.visProp.minorheight * 0.5,
264             // ticks width and height in screen units
265             dxMaj, dyMaj,
266             dxMin, dyMin,
267             // ticks width and height in user units
268             dx, dy;
269         // END OF variable declaration
270 
271         // This will trap this update routine in an endless loop. Besides, there's not much we can show
272         // on such a tiny board, so we just get out of here immediately.
273         if (this.board.canvasWidth === 0 || this.board.canvasHeight === 0) {
274             return;
275         }
276 
277         // Grid-like ticks
278         if (this.visProp.minorheight < 0)  {
279             this.minStyle = 'infinite';
280         } else {
281             this.minStyle = 'finite';
282         }
283             
284         if(this.visProp.majorheight < 0) {
285             this.majStyle = 'infinite';
286         } else {
287             this.majStyle = 'finite';
288         }
289 
290         // Set lower and upper bound for the tick distance.
291         // This is necessary for segments.
292         if (this.line.visProp.straightfirst) {
293             lb = Number.NEGATIVE_INFINITY;
294         } else {
295             lb = 0 + eps;
296         }
297 
298         if (this.line.visProp.straightlast) {
299             ub = Number.POSITIVE_INFINITY;
300         } else {
301             ub = distP1P2 - eps;
302         }
303 
304         // This piece of code used to be in AbstractRenderer.updateAxisTicksInnerLoop
305         // and has been moved in here to clean up the renderers code.
306         //
307         // The code above only calculates the position of the ticks. The following code parts
308         // calculate the dx and dy values which make ticks out of this positions, i.e. from the
309         // position (p_x, p_y) calculated above we have to draw a line from
310         // (p_x - dx, py - dy) to (p_x + dx, p_y + dy) to get a tick.
311         dxMaj = this.line.stdform[1];
312         dyMaj = this.line.stdform[2];
313         dxMin = dxMaj;
314         dyMin = dyMaj;
315         dx = dxMaj;
316         dy = dyMaj;
317         
318         // After this, the length of the vector (dxMaj, dyMaj) in screen coordinates is equal to distMaj pixel.
319         d = Math.sqrt(dxMaj*dxMaj*this.board.unitX*this.board.unitX + dyMaj*dyMaj*this.board.unitY*this.board.unitY);
320         dxMaj *= distMaj/d*this.board.unitX;
321         dyMaj *= distMaj/d*this.board.unitY;
322         dxMin *= distMin/d*this.board.unitX;
323         dyMin *= distMin/d*this.board.unitY;
324 
325         // Begin cleanup
326         this.removeTickLabels();
327         
328         // If the parent line is not finite, we can stop here.
329         if (Math.abs(dx)<JXG.Math.eps && Math.abs(dy)<JXG.Math.eps) {
330             return;
331         }
332 
333         // initialize storage arrays
334         // ticks stores the ticks coordinates
335         this.ticks = [];
336         
337         // labels stores the text to display beside the ticks
338         this.labels = [];
339         // END cleanup
340         
341         // we have an array of fixed ticks we have to draw
342         if (!this.equidistant) {
343             for (i = 0; i < this.fixedTicks.length; i++) {
344                 nx = p1.coords.usrCoords[1] + this.fixedTicks[i]*deltaX;
345                 ny = p1.coords.usrCoords[2] + this.fixedTicks[i]*deltaY;
346                 tickCoords = new JXG.Coords(JXG.COORDS_BY_USER, [nx, ny], this.board);
347                 
348                 ti = this._tickEndings(tickCoords, dx, dy, dxMaj, dyMaj, dxMin, dyMin, /*major:*/ true);
349 				// Compute the start position and the end position of a tick.
350 				// If both positions are out of the canvas, ti is empty.
351                 if (ti.length==2 && this.fixedTicks[i]>=lb && this.fixedTicks[i]<ub) {
352                     this.ticks.push(ti);
353                 }
354                 this.labels.push(this._makeLabel(this.visProp.labels[i] || this.fixedTicks[i], tickCoords, this.board, this.visProp.drawlabels, this.id, i));
355                 // visibility test missing
356             }
357             return;
358         }
359 
360         symbTicksDelta = ticksDelta;
361         ticksDelta *= this.visProp.scale;
362 
363         // ok, we have equidistant ticks and not special ticks, so we continue here with generating them:
364         // adjust distances
365         if (this.visProp.insertticks && this.minTicksDistance > JXG.Math.eps) {
366             f = this._adjustTickDistance(ticksDelta, distScr, factor, p1.coords, deltaX, deltaY);
367             ticksDelta *= f;
368             symbTicksDelta *= f;
369         }
370 
371         if (!this.visProp.insertticks) {
372             ticksDelta /= this.visProp.minorticks+1;
373             symbTicksDelta /= this.visProp.minorticks+1;
374         }
375 
376         this.ticksDelta = ticksDelta;
377         this.symbTicksDelta = symbTicksDelta;
378 
379 
380 		// We shoot into the middle of the canvas
381 		// to the tick position which is closest to the center
382 		// of the canvas. We do this by an orthogonal projection
383 		// of the canvas center to the line and by rounding of the
384 		// distance of the projected point to point1 of the line.
385 		// This position is saved in
386 		// center and startTick.
387         bb = this.board.getBoundingBox();
388         nx = (bb[0]+bb[2])*0.5;
389         ny = (bb[1]+bb[3])*0.5;
390 		
391 		// Project the center of the canvas to the line.
392 		perp = [nx*this.line.stdform[2]-ny*this.line.stdform[1], 
393 				-this.line.stdform[2], 
394 				 this.line.stdform[1]];
395 		center = JXG.Math.crossProduct(this.line.stdform, perp);
396 		center[1] /= center[0];
397 		center[2] /= center[0];
398 		center[0] = 1;
399 		// Round the distance of center to point1
400         tickCoords = new JXG.Coords(JXG.COORDS_BY_USER, center.slice(1), this.board);
401         d = p1.coords.distance(JXG.COORDS_BY_USER, tickCoords);
402         if ((p2.X()-p1.X())*(center[1]-p1.X())<0 || (p2.Y()-p1.Y())*(center[2]-p1.Y())<0) {
403             d *= -1;
404         }
405         tickPosition = Math.round(d/ticksDelta)*ticksDelta;
406 
407 		// Find the correct direction of center from point1
408         if (Math.abs(tickPosition)>JXG.Math.eps) {
409             dir = Math.abs(tickPosition)/tickPosition;
410         }
411 		// From now on, we jump around center
412         center[1] = p1.coords.usrCoords[1] + deltaX*tickPosition;
413         center[2] = p1.coords.usrCoords[2] + deltaY*tickPosition;
414         startTick = tickPosition;
415         tickPosition = 0;
416 
417         symbTickPosition = 0;
418         // this could be done more elaborate to prevent rounding errors
419         symbStartTick = startTick/this.visProp.scale;
420 
421         nx = center[1];
422         ny = center[2];
423         i = 0;          // counter for label ids
424         j = 0;
425 		// Now, we jump around center
426 		// until we are outside of the canvas.
427 		// If this is the case we proceed in the other
428 		// direction until we are out of the canvas in this direction, too.
429 		// Then we are done.
430         do {
431             tickCoords = new JXG.Coords(JXG.COORDS_BY_USER, [nx, ny], this.board);
432 
433 			// Test if tick is a major tick.
434             // This is the case if (dir*tickPosition+startTick)/ticksDelta is
435             // a multiple of the number of minorticks+1
436             tickCoords.major = Math.round((dir * tickPosition + startTick) / ticksDelta) % (this.visProp.minorticks + 1) === 0;
437             
438 			// Compute the start position and the end position of a tick.
439 			// If both positions are out of the canvas, ti is empty.
440             ti = this._tickEndings(tickCoords, dx, dy, dxMaj, dyMaj, dxMin, dyMin, tickCoords.major);
441             
442             // The tick has an overlap with the board?
443             if (ti.length === 3) {
444                 pos = dir*symbTickPosition+symbStartTick;
445                 if ((Math.abs(pos) >= eps || this.visProp.drawzero) && (pos > lb && pos < ub)) {
446                     this.ticks.push(ti);
447                     
448                     if (tickCoords.major) {
449                         this.labels.push(this._makeLabel(pos, tickCoords, this.board, this.visProp.drawlabels, this.id, i));
450                     } else {
451                         this.labels.push(null);
452                     }
453                     i++;
454                 }
455                 
456                 // Toggle direction
457                 if (dirs==2) {
458                     dir *= (-1);
459                 }
460 				// Increase distance from center
461                 if (j % 2 === 0 || dirs === 1) {
462                     tickPosition += ticksDelta;
463                     symbTickPosition += symbTicksDelta;
464                 }
465             } else {
466                 dir *= (-1);
467                 dirs--;
468             }
469 
470             j++;
471             
472             nx = center[1] + dir*deltaX*tickPosition;
473             ny = center[2] + dir*deltaY*tickPosition;
474         } while (dirs>0);
475 
476         this.needsUpdate = true;
477         this.updateRenderer();
478     },
479     
480     /**
481      * @private
482      */
483     _adjustTickDistance: function(ticksDelta, distScr, factor, p1c, deltaX, deltaY) {
484         var nx, ny, f = 1;
485         
486         while (distScr > 4*this.minTicksDistance) {
487             f /= 10;
488             nx = p1c.usrCoords[1] + deltaX*ticksDelta*f;
489             ny = p1c.usrCoords[2] + deltaY*ticksDelta*f;
490             distScr = p1c.distance(JXG.COORDS_BY_SCREEN, new JXG.Coords(JXG.COORDS_BY_USER, [nx, ny], this.board));
491         }
492 
493         // If necessary, enlarge ticksDelta
494         while (distScr < this.minTicksDistance) {
495             f *= factor;
496             factor = (factor == 5 ? 2 : 5);
497             nx = p1c.usrCoords[1] + deltaX*ticksDelta*f;
498             ny = p1c.usrCoords[2] + deltaY*ticksDelta*f;
499             distScr = p1c.distance(JXG.COORDS_BY_SCREEN, new JXG.Coords(JXG.COORDS_BY_USER, [nx, ny], this.board));
500         }
501         return f;
502     },
503 
504     /**
505      * @param {JXG.Coords} coords Coordinates of the tick on the line.
506      * @param {Number} dx horizontal tick extension in user coordinates.
507      * @param {Number} dy vertical tick extension in user coordinates.
508      * @param {Number} dxMaj horizontal tick direction in screen coordinates.
509      * @param {Number} dyMaj vertical tick direction in screen coordinates.
510      * @param {Number} dxMin horizontal tick direction in screen coordinates.
511      * @param {Number} dyMin vertical tick direction in screen coordinates.
512      * @param {Boolean} major True if tick is major tick.
513      * @return {Array} Array of length 3 containing start and end coordinates in screen coordinates
514      *                 of the tick (arrays of length 2). 3rd entry is true if major tick otherwise false.
515      *                 If the tick is outside of the canvas, the return array is empty.
516      * @private
517      */
518     _tickEndings: function(coords, dx, dy, dxMaj, dyMaj, dxMin, dyMin, major) {
519         var i, c, 
520             cw = this.board.canvasWidth,
521             ch = this.board.canvasHeight,
522             x = [-1000*cw, -1000*ch],
523             y = [-1000*cw, -1000*ch], 
524             dxs, dys,
525             s, style,
526             count = 0,
527             isInsideCanvas = false;
528 
529 		c = coords.scrCoords;
530 		if (major) {
531 			dxs = dxMaj;
532 			dys = dyMaj;
533 			style = this.majStyle;
534 		} else {
535 			dxs = dxMin;
536 			dys = dyMin;
537 			style = this.minStyle;
538 		}
539         
540 		// This is necessary to compute the correct direction of infinite grid lines
541 		// if unitX!=unitY.
542 		//dxs = dx; //*this.board.unitX;
543 		//dys = dy; //*this.board.unitY;
544 
545 		// For all ticks regardless if of finite or infinite
546 		// tick length the intersection with the canvas border is 
547 		// computed. 
548 		
549 		// horizontal line and vertical tick 
550 		if (Math.abs(dx)<JXG.Math.eps) {
551 			x[0] = c[1];
552 			x[1] = c[1];
553 			y[0] = 0;
554 			y[1] = ch;
555 		// vertical line and horizontal tick
556 		} else if (Math.abs(dy)<JXG.Math.eps) {
557 			x[0] = 0;
558 			x[1] = cw;
559 			y[0] = c[2];
560 			y[1] = c[2];
561         // other
562 		} else {
563 			count = 0;
564 			s = JXG.Math.crossProduct([0,0,1], [-dys*c[1]-dxs*c[2], dys, dxs]); // intersect with top
565 			s[1] /= s[0];
566 			if (s[1]>=0 && s[1]<=cw) {  
567 				x[count] = s[1];
568 				y[count] = 0;
569 				count++;
570 			}
571 			s = JXG.Math.crossProduct([0,1,0], [-dys*c[1]-dxs*c[2], dys, dxs]); // intersect with left
572 			s[2] /= s[0];
573 			if (s[2]>=0 && s[2]<=ch) {  
574 				x[count] = 0;
575 				y[count] = s[2];
576 				count++;
577 			}
578 			if (count<2) {
579 				s = JXG.Math.crossProduct([ch*ch,0,-ch], [-dys*c[1]-dxs*c[2], dys, dxs]); // intersect with bottom
580 				s[1] /= s[0];
581 				if (s[1]>=0 && s[1]<=cw) {  
582 					x[count] = s[1];
583 					y[count] = ch;
584 					count++;
585 				}
586 			}
587 			if (count<2) {
588 				s = JXG.Math.crossProduct([cw*cw, -cw, 0], [-dys*c[1]-dxs*c[2], dys, dxs]); // intersect with right
589 				s[2] /= s[0];
590 				if (s[2]>=0 && s[2]<=ch) {  
591 					x[count] = cw;
592 					y[count] = s[2];
593 				}
594 			}
595 		}
596 		if ((x[0]>=0 && x[0]<=cw && y[0]>=0 && y[0]<=ch ) 
597 			|| 
598 			(x[1]>=0 && x[1]<=cw && y[1]>=0 && y[1]<=ch)
599 		   ) {
600 			isInsideCanvas = true;
601 		} else { 
602 			isInsideCanvas = false;
603 		}
604 		// finite tick length
605 		if (style=='finite') {
606 			x[0] = c[1] + dxs;
607 			y[0] = c[2] - dys;
608 			x[1] = c[1] - dxs;
609 			y[1] = c[2] + dys;
610 		}
611         if (isInsideCanvas) {
612             return [x,y, major];
613         } else { 
614             return [];
615         }
616     },
617 
618     /** 
619      * Create a tick label 
620      * @private
621      **/
622     _makeLabel: function(pos, newTick, board, drawLabels, id, i) {
623         var labelText, label, attr, num = typeof pos === 'number';
624 
625         if (!drawLabels) {
626             return null;
627         }
628 
629         labelText = pos.toString();
630         if (Math.abs(pos) < JXG.Math.eps) {
631             labelText = '0';
632         }
633 
634         if(num && (labelText.length > 5 || labelText.indexOf('e') != -1)) {
635             labelText = pos.toPrecision(3).toString();
636         }
637         if (num && labelText.indexOf('.') > -1) {
638             // trim trailing zeros
639             labelText = labelText.replace(/0+$/, '');
640             // trim trailing .
641             labelText = labelText.replace(/\.$/, '');
642         }
643 
644         if (this.visProp.scalesymbol.length > 0 && labelText === '1') {
645             labelText = this.visProp.scalesymbol;
646         } else if (this.visProp.scalesymbol.length > 0 && labelText === '0') {
647             labelText = '0';
648         } else {
649             labelText = labelText + this.visProp.scalesymbol;
650         }
651 
652         attr = {
653             id: id + i + 'Label',
654             isLabel: true,
655             layer: board.options.layer.line,
656             highlightStrokeColor: board.options.text.strokeColor,
657             highlightStrokeWidth: board.options.text.strokeWidth,
658             highlightStrokeOpacity: board.options.text.strokeOpacity,
659             visible: this.visProp.visible,
660             priv: this.visProp.priv
661         };
662         attr = JXG.deepCopy(attr, this.visProp.label);
663         label = JXG.createText(board, [newTick.usrCoords[1], newTick.usrCoords[2], labelText], attr);
664         label.isDraggable = false;
665         label.dump = false;
666 
667         /*
668          * Ticks have their own label handling which is done below and not
669          * in Text.update().
670          * The reason is that there is no parent element for the labels
671          * which can determine the label position.
672          */
673         //label.distanceX = 4;
674         //label.distanceY = -parseInt(label.visProp.fontsize)+3; //-9;
675         label.distanceX = this.visProp.label.offset[0];
676         label.distanceY = this.visProp.label.offset[1];
677         label.setCoords(newTick.usrCoords[1] + label.distanceX / (board.unitX),
678             newTick.usrCoords[2] + label.distanceY / (board.unitY));
679 
680         label.visProp.visible = drawLabels;
681         //label.prepareUpdate().update().updateRenderer();
682         return label;
683     },
684 
685     /**
686      * Removes the HTML divs of the tick labels
687      * before repositioning
688      * @private
689      */
690     removeTickLabels: function () {
691         var j;
692 
693         // remove existing tick labels
694         if(this.labels != null) {
695             if ((this.board.needsFullUpdate||this.needsRegularUpdate) &&
696                 !(this.board.options.renderer=='canvas'&&this.board.options.text.display=='internal')
697                ) {
698                 for(j=0; j<this.labels.length; j++) {
699                     if(this.labels[j] != null) {
700                         this.board.removeObject(this.labels[j]);
701                     }
702                 }
703             }
704         }
705     },
706 
707     /**
708      * Recalculate the tick positions and the labels.
709      */
710     update: function () {
711         if (this.needsUpdate) {
712             this.calculateTicksCoordinates();
713         }
714         return this;
715     },
716 
717     /**
718      * Uses the boards renderer to update the arc.
719      */
720     updateRenderer: function () {
721         if (this.needsUpdate) {
722             if (this.ticks) {
723                 this.board.renderer.updateTicks(this, this.dxMaj, this.dyMaj, this.dxMin, this.dyMin, this.minStyle, this.majStyle);
724             }
725             this.needsUpdate = false;
726         }
727         return this;
728     },
729 
730     hideElement: function () {
731         var i;
732 
733         this.visProp.visible = false;
734         this.board.renderer.hide(this);
735 
736         for (i = 0; i < this.labels.length; i++) {
737             if (JXG.exists(this.labels[i]))
738                 this.labels[i].hideElement();
739         }
740 
741         return this;
742     },
743 
744     showElement: function () {
745         var i;
746 
747         this.visProp.visible = true;
748         this.board.renderer.show(this);
749 
750         for (i = 0; i < this.labels.length; i++) {
751             if (JXG.exists(this.labels[i]))
752                 this.labels[i].showElement();
753         }
754 
755         return this;
756     }
757 });
758 
759 /**
760  * @class Ticks are used as distance markers on a line.
761  * @pseudo
762  * @description
763  * @name Ticks
764  * @augments JXG.Ticks
765  * @constructor
766  * @type JXG.Ticks
767  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
768  * @param {JXG.Line,Number} line,_distance The parents consist of the line the ticks are going to be attached to and optional the
769  * distance between two major ticks. If no distance is given the attribute {@link JXG.Ticks#ticksDistance} is used.
770  * @example
771  * // Create an axis providing two coord pairs.
772  *   var p1 = board.create('point', [0, 3]);
773  *   var p2 = board.create('point', [1, 3]);
774  *   var l1 = board.create('line', [p1, p2]);
775  *   var t = board.create('ticks', [l1], {ticksDistance: 2});
776  * </pre><div id="ee7f2d68-75fc-4ec0-9931-c76918427e63" style="width: 300px; height: 300px;"></div>
777  * <script type="text/javascript">
778  * (function () {
779  *   var board = JXG.JSXGraph.initBoard('ee7f2d68-75fc-4ec0-9931-c76918427e63', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false});
780  *   var p1 = board.create('point', [0, 3]);
781  *   var p2 = board.create('point', [1, 3]);
782  *   var l1 = board.create('line', [p1, p2]);
783  *   var t = board.create('ticks', [l1], {ticksDistance: 2});
784  * })();
785  * </script><pre>
786  */
787 JXG.createTicks = function(board, parents, attributes) {
788     var el, dist,
789         attr = JXG.copyAttributes(attributes, board.options, 'ticks');
790 
791     if (parents.length < 2) {
792         dist = attributes.ticksDistance;
793     } else {
794         dist = parents[1];
795     }
796 
797     if ( (parents[0].elementClass == JXG.OBJECT_CLASS_LINE) && (JXG.isFunction(parents[1]) || JXG.isArray(parents[1]) || JXG.isNumber(parents[1]))) {
798         el = new JXG.Ticks(parents[0], dist, attr);
799     } else {
800         throw new Error("JSXGraph: Can't create Ticks with parent types '" + (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'.");
801     }
802     
803     el.isDraggable = true;
804 
805     return el;
806 };
807 
808 JXG.JSXGraph.registerElement('ticks', JXG.createTicks);
809