      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     // documented in JXG.GeometryElement
    120     hasPoint: function (x, y) {
    121        return false;
    122     },
    123
    124     /**
    125      * (Re-)calculates the ticks coordinates.
    126      */
    127     calculateTicksCoordinates: function() {
    128             // Point 1 of the line
    129         var p1 = this.line.point1,
    130             // Point 2 of the line
    131             p2 = this.line.point2,
    132             // Distance between the two points from above
    133             distP1P2 = p1.Dist(p2),
    134             // Distance of X coordinates of two major ticks
    135             // Initialized with the distance of Point 1 to a point between Point 1 and Point 2 on the line and with distance 1
    136             // this equals always 1 for lines parallel to x = 0 or y = 0. It's only important for lines other than that.
    137             deltaX = (p2.coords.usrCoords[1] - p1.coords.usrCoords[1])/distP1P2,
    138             // The same thing for Y coordinates
    139             deltaY = (p2.coords.usrCoords[2] - p1.coords.usrCoords[2])/distP1P2,
    140             // Distance of p1 to the unit point in screen coordinates
    141             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)),
    142             // Distance between two major ticks in user coordinates
    143             ticksDelta = (this.equidistant ? this.ticksFunction(1) : 1),
    144             // This factor is for enlarging ticksDelta and it switches between 5 and 2
    145             // Hence, if two major ticks are too close together they'll be expanded to a distance of 5
    146             // if they're still too close together, they'll be expanded to a distance of 10 etc
    147             factor = 5,
    148             // Coordinates of the current tick
    149             tickCoords,
    150             // Coordinates of the first drawn tick
    151             startTick,
    152             // a counter
    153             i,
    154             // the distance of the tick to p1. Is displayed on the board using a label
    155             // for majorTicks
    156             tickPosition,
    157             // infinite or finite tick length
    158             style,
    159             // new position
    160             nx = 0,
    161             ny = 0,
    162             ti,
    163 			dirs = 2, dir = -1,
    164 			center, d, bb, perp,
    165
    166             // the following variables are used to define ticks height and slope
    167             eps = JXG.Math.eps, pos, lb, ub,
    168             distMaj = this.visProp.majorheight/2,
    169             distMin = this.visProp.minorheight/2,
    170             dxMaj, dyMaj,
    171             dxMin, dyMin;
    172         // END OF variable declaration
    173
    174
    175         // Grid-like ticks
    176         if (this.visProp.minorheight < 0)  {
    177             this.minStyle = 'infinite';
    178         } else {
    179             this.minStyle = 'finite';
    180         }
    181
    182         if(this.visProp.majorheight < 0) {
    183             this.majStyle = 'infinite';
    184         } else {
    185             this.majStyle = 'finite';
    186         }
    187
    188         // Set lower and upper bound for the tick distance.
    189         // This is necessary for segments.
    190         if (this.line.visProp.straightfirst) {
    191             lb = Number.NEGATIVE_INFINITY;
    192         } else {
    193             lb = 0 + eps;
    194         }
    195
    196         if (this.line.visProp.straightlast) {
    197             ub = Number.POSITIVE_INFINITY;
    198         } else {
    199             ub = distP1P2 - eps;
    200         }
    201
    202         // this piece of code used to be in AbstractRenderer.updateAxisTicksInnerLoop
    203         // and has been moved in here to clean up the renderers code.
    204         //
    205         // The code above only calculates the position of the ticks. The following code parts
    206         // calculate the dx and dy values which make ticks out of this positions, i.e. from the
    207         // position (p_x, p_y) calculated above we have to draw a line from
    208         // (p_x - dx, py - dy) to (p_x + dx, p_y + dy) to get a tick.
    209         dxMaj = this.line.stdform[1];
    210         dyMaj = this.line.stdform[2];
    211         dxMin = dxMaj;
    212         dyMin = dyMaj;
    213         d = Math.sqrt(dxMaj*dxMaj + dyMaj*dyMaj);
    214         dxMaj *= distMaj/d;
    215         dyMaj *= distMaj/d;
    216         dxMin *= distMin/d;
    217         dyMin *= distMin/d;
    218
    219         // Begin cleanup
    220         this.removeTickLabels();
    221
    222         // initialize storage arrays
    223         // ticks stores the ticks coordinates
    224         this.ticks = [];
    225
    226         // labels stores the text to display beside the ticks
    227         this.labels = [];
    228         // END cleanup
    229
    230         // we have an array of fixed ticks we have to draw
    231         if(!this.equidistant) {
    232             for (i = 0; i < this.fixedTicks.length; i++) {
    233                 nx = p1.coords.usrCoords[1] + this.fixedTicks[i]*deltaX;
    234                 ny = p1.coords.usrCoords[2] + this.fixedTicks[i]*deltaY;
    235                 tickCoords = new JXG.Coords(JXG.COORDS_BY_USER, [nx, ny], this.board);
    236
    237                 ti = this._tickEndings(tickCoords, dxMaj, dyMaj, dxMin, dyMin, /*major:*/ true);
    238 				// Compute the start position and the end position of a tick.
    239 				// If both positions are out of the canvas, ti is empty.
    240                 if (ti.length==2 && this.fixedTicks[i]>=lb && this.fixedTicks[i]<ub) {
    241                     this.ticks.push(ti);
    242                 }
    243                 this.labels.push(this._makeLabel(this.fixedTicks[i], tickCoords, this.board, this.visProp.drawlabels, this.id, i));
    244                 // visibility test missing
    245             }
    246             return;
    247         }
    248
    249         // ok, we have equidistant ticks and not special ticks, so we continue here with generating them:
    250         // adjust distances
    251         if (this.visProp.insertticks && this.minTicksDistance > JXG.Math.eps) {
    252             ticksDelta = this._adjustTickDistance(ticksDelta, distScr, factor, p1.coords, deltaX, deltaY);
    253             this.ticksDelta = ticksDelta;
    254         }
    255
    256         if (!this.visProp.insertticks) {
    257             ticksDelta /= this.visProp.minorticks+1;
    258         }
    259
    260 		// We shoot into the middle of the canvas
    261 		// to the tick position which is closest to the center
    262 		// of the canvas. We do this by an orthogonal projection
    263 		// of the canvas center to the line and by rounding of the
    264 		// distance of the projected point to point1 of the line.
    265 		// This position is saved in
    266 		// center and startTick.
    267         bb = this.board.getBoundingBox();
    268         nx = (bb[0]+bb[2])*0.5;
    269         ny = (bb[1]+bb[3])*0.5;
    270
    271 		// Project the center of the canvas to the line.
    272 		perp = [nx*this.line.stdform[2]-ny*this.line.stdform[1],
    273 				-this.line.stdform[2],
    274 				 this.line.stdform[1]];
    275 		center = JXG.Math.crossProduct(this.line.stdform, perp);
    276 		center[1] /= center[0];
    277 		center[2] /= center[0];
    278 		center[0] = 1;
    279 		// Round the distance of center to point1
    280         tickCoords = new JXG.Coords(JXG.COORDS_BY_USER, center.slice(1), this.board);
    281         d = p1.coords.distance(JXG.COORDS_BY_USER, tickCoords);
    282         if ((p2.X()-p1.X())*(center[1]-p1.X())<0 || (p2.Y()-p1.Y())*(center[2]-p1.Y())<0) {
    283             d *= -1;
    284         }
    285         tickPosition = Math.round(d/ticksDelta)*ticksDelta;
    286
    287 		// Find the correct direction of center from point1
    288         if (Math.abs(tickPosition)>JXG.Math.eps) {
    289             dir = Math.abs(tickPosition)/tickPosition;
    290         }
    291 		// From now on, we jump around center
    292         center[1] = p1.coords.usrCoords[1] + deltaX*tickPosition;
    293         center[2] = p1.coords.usrCoords[2] + deltaY*tickPosition;
    294         startTick = tickPosition;
    295         tickPosition = 0;
    296
    297         nx = center[1];
    298         ny = center[2];
    299         i = 0;          // counter for label ids
    300 		// Now, we jump around center
    301 		// until we are outside of the canvas.
    302 		// If this is the case we proceed in the other
    303 		// direction until we are out of the canvas in this direction, too.
    304 		// Then we are done.
    305         do {
    306             tickCoords = new JXG.Coords(JXG.COORDS_BY_USER, [nx, ny], this.board);
    307
    308 			// Test if tick is a major tick.
    309             // This is the case if (dir*tickPosition+startTick)/ticksDelta is
    310             // a multiple of the number of minorticks+1
    311             if (Math.round((dir*tickPosition+startTick)/ticksDelta) % (this.visProp.minorticks+1) === 0) {
    312                 tickCoords.major = true;
    313             } else {
    314                 tickCoords.major = false;
    315             }
    316
    317 			// Compute the start position and the end position of a tick.
    318 			// If both positions are out of the canvas, ti is empty.
    319             ti = this._tickEndings(tickCoords, dxMaj, dyMaj, dxMin, dyMin, tickCoords.major);
    320             if (ti.length==2) {  // The tick has an overlap with the board
    321                 pos = dir*tickPosition+startTick;
    322                 if ( (Math.abs(pos)<=eps && this.visProp.drawzero)
    323                      || (pos>lb && pos<ub)
    324                     ) {
    325                     this.ticks.push(ti);
    326                     if (tickCoords.major) {
    327                         this.labels.push(this._makeLabel(pos, tickCoords, this.board, this.visProp.drawlabels, this.id, i));
    328                     } else {
    329                         this.labels.push(null);
    330                     }
    331                     i++;
    332                 }
    333
    334 				// Toggle direction
    335                 if (dirs==2) {
    336                     dir *= (-1);
    337                 }
    338 				// Increase distance from center
    339                 if (dir==1 || dirs==1) {
    340                     tickPosition += ticksDelta;
    341                 }
    342             } else {
    343                 dir *= (-1);
    344                 dirs--;
    345             }
    346
    347             nx = center[1] + dir*deltaX*tickPosition;
    348             ny = center[2] + dir*deltaY*tickPosition;
    349         } while (dirs>0);
    350     },
    351
    352     _adjustTickDistance: function(ticksDelta, distScr, factor, p1c, deltaX, deltaY) {
    353         var nx, ny;
    354
    355         while (distScr > 4*this.minTicksDistance) {
    356             ticksDelta /= 10;
    357             nx = p1c.usrCoords[1] + deltaX*ticksDelta;
    358             ny = p1c.usrCoords[2] + deltaY*ticksDelta;
    359             distScr = p1c.distance(JXG.COORDS_BY_SCREEN, new JXG.Coords(JXG.COORDS_BY_USER, [nx, ny], this.board));
    360         }
    361
    362         // If necessary, enlarge ticksDelta
    363         while (distScr < this.minTicksDistance) {
    364             ticksDelta *= factor;
    365             factor = (factor == 5 ? 2 : 5);
    366             nx = p1c.usrCoords[1] + deltaX*ticksDelta;
    367             ny = p1c.usrCoords[2] + deltaY*ticksDelta;
    368             distScr = p1c.distance(JXG.COORDS_BY_SCREEN, new JXG.Coords(JXG.COORDS_BY_USER, [nx, ny], this.board));
    369         }
    370         return ticksDelta;
    371     },
    372
    373     _tickEndings: function(coords, dxMaj, dyMaj, dxMin, dyMin, major) {
    374         var i, c,
    375             cw = this.board.canvasWidth,
    376             ch = this.board.canvasHeight,
    377             x = [-1000*cw, -1000*ch],
    378             y = [-1000*cw, -1000*ch],
    379             dx, dy, dxs, dys, d,
    380             s, style,
    381             count = 0,
    382             isInsideCanvas = false;
    383
    384 		c = coords.scrCoords;
    385 		if (major) {
    386 			dx = dxMaj;
    387 			dy = dyMaj;
    388 			style = this.majStyle;
    389 		} else {
    390 			dx = dxMin;
    391 			dy = dyMin;
    392 			style = this.minStyle;
    393 		}
    394 		// This is necessary to compute the correct direction of infinite grid lines
    395 		// if unitX!=unitY.
    396 		dxs = dx*this.board.unitX;
    397 		dys = dy*this.board.unitY;
    398
    399 		// For all ticks regardless if of finite or infinite
    400 		// tick length the intersection with the canvas border is
    401 		// computed.
    402
    403 		// vertical tick
    404 		if (Math.abs(dx)<JXG.Math.eps) {
    405 			x[0] = c[1];
    406 			x[1] = c[1];
    407 			y[0] = 0;
    408 			y[1] = ch;
    409 		// horizontal tick
    410 		} else if (Math.abs(dy)<JXG.Math.eps) {
    411 			x[0] = 0;
    412 			x[1] = cw;
    413 			y[0] = c[2];
    414 			y[1] = c[2];
    415 		// other
    416 		} else {
    417 			count = 0;
    418 			s = JXG.Math.crossProduct([0,0,1], [-dys*c[1]-dxs*c[2], dys, dxs]); // intersect with top
    419 			s[1] /= s[0];
    420 			if (s[1]>=0 && s[1]<=cw) {
    421 				x[count] = s[1];
    422 				y[count] = 0;
    423 				count++;
    424 			}
    425 			s = JXG.Math.crossProduct([0,1,0], [-dys*c[1]-dxs*c[2], dys, dxs]); // intersect with left
    426 			s[2] /= s[0];
    427 			if (s[2]>=0 && s[2]<=ch) {
    428 				x[count] = 0;
    429 				y[count] = s[2];
    430 				count++;
    431 			}
    432 			if (count<2) {
    433 				s = JXG.Math.crossProduct([ch*ch,0,-ch], [-dys*c[1]-dxs*c[2], dys, dxs]); // intersect with bottom
    434 				s[1] /= s[0];
    435 				if (s[1]>=0 && s[1]<=cw) {
    436 					x[count] = s[1];
    437 					y[count] = ch;
    438 					count++;
    439 				}
    440 			}
    441 			if (count<2) {
    442 				s = JXG.Math.crossProduct([cw*cw, -cw, 0], [-dys*c[1]-dxs*c[2], dys, dxs]); // intersect with right
    443 				s[2] /= s[0];
    444 				if (s[2]>=0 && s[2]<=ch) {
    445 					x[count] = cw;
    446 					y[count] = s[2];
    447 				}
    448 			}
    449 		}
    450 		if ((x[0]>=0 && x[0]<=cw && y[0]>=0 && y[0]<=ch )
    451 			||
    452 			(x[1]>=0 && x[1]<=cw && y[1]>=0 && y[1]<=ch)
    453 		   ) {
    454 			isInsideCanvas = true;
    455 		} else {
    456 			isInsideCanvas = false;
    457 		}
    458 		// finite tick length
    459 		if (style=='finite') {
    460 			d = Math.sqrt(this.board.unitX*this.board.unitX+this.board.unitY*this.board.unitY);
    461 			x[0] = c[1] + dx*this.board.unitX/d;
    462 			y[0] = c[2] - dy*this.board.unitY/d;
    463 			x[1] = c[1] - dx*this.board.unitX/d;
    464 			y[1] = c[2] + dy*this.board.unitY/d;
    465 		}
    466         if (isInsideCanvas) {
    467             return [x,y];
    468         } else {
    469             return [];
    470         }
    471     },
    472
    473     /**
    474      * Create a tick label
    475      * @private
    476      **/
    477     _makeLabel: function(pos, newTick, board, drawLabels, id, i) {
    478                 var labelText, label;
    479
    480                 if (!drawLabels) {
    481                     return null;
    482                 }
    483
    484                 labelText = pos.toString();
    485                 if (Math.abs(pos) < JXG.Math.eps) {
    486                     labelText = '0';
    487                 }
    488
    489                 if(labelText.length > 5 || labelText.indexOf('e') != -1) {
    490                     labelText = pos.toPrecision(3).toString();
    491                 }
    492                 if (labelText.indexOf('.') > -1) {
    493                     // trim trailing zeros
    494                     labelText = labelText.replace(/0+$/, '');
    495                     // trim trailing .
    496                     labelText = labelText.replace(/\.$/, '');
    497                 }
    498
    499                 label = JXG.createText(board, [newTick.usrCoords[1], newTick.usrCoords[2], labelText], {
    500                     id: id + i + 'Label',
    501                     isLabel: true,
    502                     layer: board.options.layer.line,
    503                     highlightStrokeColor: board.options.text.strokeColor,
    504                     highlightStrokeWidth: board.options.text.strokeWidth,
    505                     highlightStrokeOpacity: board.options.text.strokeOpacity
    506                 });
    507                 label.isDraggable = false;
    508                 label.dump = false;
    509                 label.distanceX = 4;
    510                 label.distanceY = -parseInt(label.visProp.fontsize)+3; //-9;
    511                 label.setCoords(newTick.usrCoords[1] + label.distanceX / (board.unitX),
    512                                 newTick.usrCoords[2] + label.distanceY / (board.unitY));
    513
    514                 label.visProp.visible = drawLabels;
    515                 label.prepareUpdate().update().updateRenderer();
    516                 return label;
    517      },
    518
    519     /**
    520      * Removes the HTML divs of the tick labels
    521      * before repositioning
    522      */
    523     removeTickLabels: function () {
    524         var j;
    525
    526         // remove existing tick labels
    527         if(this.ticks != null) {
    528             if ((this.board.needsFullUpdate||this.needsRegularUpdate) &&
    529                 !(this.board.options.renderer=='canvas'&&this.board.options.text.display=='internal')
    530                ) {
    531                 for(j=0; j<this.ticks.length; j++) {
    532                     if(this.labels[j]!=null && this.labels[j].visProp.visible) {
    533 //                        this.board.renderer.remove(this.labels[j].rendNode);
    534                         this.board.removeObject(this.labels[j]);
    535                     }
    536                 }
    537             }
    538         }
    539     },
    540
    541     /**
    542      * Recalculate the tick positions and the labels.
    543      */
    544     update: function () {
    545         if (this.needsUpdate) {
    546             this.calculateTicksCoordinates();
    547         }
    548         return this;
    549     },
    550
    551     /**
    552      * Uses the boards renderer to update the arc.
    553      */
    554     updateRenderer: function () {
    555         if (this.needsUpdate) {
    556             if (this.ticks) {
    557                 this.board.renderer.updateTicks(this, this.dxMaj, this.dyMaj, this.dxMin, this.dyMin, this.minStyle, this.majStyle);
    558             }
    559             this.needsUpdate = false;
    560         }
    561         return this;
    562     },
    563
    564     hideElement: function () {
    565         var i;
    566
    567         this.visProp.visible = false;
    568         this.board.renderer.hide(this);
    569
    570         for (i=0; i<this.labels.length; i++) {
    571             if (JXG.exists(this.labels[i]))
    572                 this.labels[i].hideElement();
    573         }
    574
    575         return this;
    576     },
    577
    578     showElement: function () {
    579         var i;
    580
    581         this.visProp.visible = true;
    582         this.board.renderer.show(this);
    583
    584         for (i=0; i<this.labels.length; i++) {
    585             if (JXG.exists(this.labels[i]))
    586                 this.labels[i].showElement();
    587         }
    588
    589         return this;
    590     }
    591 });
    592
    593 /**
    594  * @class Ticks are used as distance markers on a line.
    595  * @pseudo
    596  * @description
    597  * @name Ticks
    598  * @augments JXG.Ticks
    599  * @constructor
    600  * @type JXG.Ticks
    601  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    602  * @param {JXG.Line,Number} line,_distance The parents consist of the line the ticks are going to be attached to and optional the
    603  * distance between two major ticks. If no distance is given the attribute {@link JXG.Ticks#ticksDistance} is used.
    604  * @example
    605  * // Create an axis providing two coord pairs.
    606  *   var p1 = board.create('point', [0, 3]);
    607  *   var p2 = board.create('point', [1, 3]);
    608  *   var l1 = board.create('line', [p1, p2]);
    609  *   var t = board.create('ticks', [l1], {ticksDistance: 2});
    610  * </pre><div id="ee7f2d68-75fc-4ec0-9931-c76918427e63" style="width: 300px; height: 300px;"></div>
    611  * <script type="text/javascript">
    612  * (function () {
    613  *   var board = JXG.JSXGraph.initBoard('ee7f2d68-75fc-4ec0-9931-c76918427e63', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false});
    614  *   var p1 = board.create('point', [0, 3]);
    615  *   var p2 = board.create('point', [1, 3]);
    616  *   var l1 = board.create('line', [p1, p2]);
    617  *   var t = board.create('ticks', [l1], {ticksDistance: 2});
    618  * })();
    619  * </script><pre>
    620  */
    621 JXG.createTicks = function(board, parents, attributes) {
    622     var el, dist,
    623         attr = JXG.copyAttributes(attributes, board.options, 'ticks');
    624
    625     if (parents.length < 2) {
    626         dist = attributes.ticksDistance;
    627     } else {
    628         dist = parents[1];
    629     }
    630
    631     if ( (parents[0].elementClass == JXG.OBJECT_CLASS_LINE) && (JXG.isFunction(parents[1]) || JXG.isArray(parents[1]) || JXG.isNumber(parents[1]))) {
    632         el = new JXG.Ticks(parents[0], dist, attr);
    633     } else
    634         throw new Error("JSXGraph: Can't create Ticks with parent types '" + (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'.");
    635
    636     return el;
    637 };
    638
    639 JXG.JSXGraph.registerElement('ticks', JXG.createTicks);
    640