Home | Math Display Experiments | JSXGraph axes, ticks and grids

Page by Murray Bourne, IntMath.com. Last updated: 16 Jun 2017

JSXGraph axes, ticks and grids

Axes, ticks and grids in JSXGraph are somewhat mysterious. I sometimes get it, only to find a feature I was using disappears in later versions of JSXGraph, or the documentation is not very clear.

So below are some things I couldn't find easily in the JSXGraph documentation, or are recent changes to how things work, or things I keep forgetting how to do.

I wrote it as a summary for myself, but I hope it's useful for anyone else trying to figure out JSXGraph axes, ticks and grids.

Any questions or comments, please add them here.

Update (May 2017)

JSXGraph has improved many of the default settings since I first wrote this page, so I have updated portions (mostly by removing my earlier gripes).

Later, on this page:

Create your own axes

Fix zoom issues

Fix disappearing axes

Axes-dependent zoom

Special ticks (e.g. multiples of π)

(1) Default axis

Here is a simple JSXGraph board where I have turned "axis" and "zooming" on, so you can see default axis appearance.

In this default view, (which is −5 to 5 for both axes), we cannot actually see those extremities (the graph finishes there, but the axis markers don't show).

You can zoom in and out (hold down <Shift> on the keyboard while using the mouse scroll wheel to zoom).


var board = JXG.JSXGraph.initBoard('jxgbox1', { 
  axis:true,
  zoom: {
	  factorX:1.25,
	  factorY:1.25,
	  wheel:true,
	  needshift:true,
	  eps: 0.1
	 }
  }
); 

(2) grid:true

The default when using grid:true doesn't appear to be all that different, but the zoom possibilities change.


var board = JXG.JSXGraph.initBoard('jxgbox2', { 
  axis:true,
  grid:true,
  zoom: {
	  factorX:1.25,
	  factorY:1.25,
	  wheel:true,
	  needshift:true,
	  eps: 0.1
	 }
  }
); 

You can zoom in and out (press <Shift> on the keyboard and use the mouse wheel to zoom), and see that sometimes the grids make sense, but a lot of the time they don't.

(3) Large scale difference between axes

When there's a big difference between the x- and y-scales, the grids end up being a dense mass.

var board = JXG.JSXGraph.initBoard('jxgbox3', {
  boundingbox:[-80,2.4, 80,-2.4],
  axis:true,
  grid:true,zoom: {
				factorX: 1.25,factorY: 1.25,wheel: true,needshift: true,eps: 0.1
			}
		}
	);

(4) Large scale difference between axes and grid:false

Here's the same one with no grids.

var board = JXG.JSXGraph.initBoard('jxgbox4', {
  boundingbox:[-80,2.4, 80,-2.4],
  axis:true
});

(5) Using JXG.Options to produce desired ticks

JXG.Options.axis.ticks.majorHeight = 60 means the major ticks are 60 px long. (As mentioned above, the default is -1, and this would give ticks which extend the whole width of the board. Of course, 60 is pretty ugly - it's just for demonstration purposes.)

The documentation says:

insertTicks inserts ticks whenever the distance between two ticks is too big.

So we set it to JXG.Options.axis.ticks.insertTicks = false;, so we can get the ticks we want.

If we set it to JXG.Options.axis.ticks.insertTicks = true;, we would have the default behavior like in (4), where we see no indication of the scale.

JXG.Options.axis.ticks.ticksDistance = 50; means the ticks will be 50 units apart on both axes.

But that's a problem, because my y-axis is −2.4 to 2.4, so no scale indication will appear.


JXG.Options.axis.ticks.majorHeight = 60;
JXG.Options.axis.ticks.insertTicks = false;
JXG.Options.axis.ticks.ticksDistance = 50;

Here are the board properties:

var board = JXG.JSXGraph.initBoard('jxgbox5', {
  boundingbox:[-200,2.4, 240,-2.4],
  axis:true,
  grid:false
});

(6) grid:true

Using grid:true gives a thick mass of vertical grids, but at least now we can see horizontal grids at −2, −1, 1 and 2.


JXG.Options.axis.ticks.majorHeight = 60;
JXG.Options.axis.ticks.insertTicks = false;
JXG.Options.axis.ticks.ticksDistance = 100;
var board = JXG.JSXGraph.initBoard('jxgbox6', {
  boundingbox:[-200,2.4, 240,-2.4],
  axis:true,
  grid:true,
  zoom: {
   factorX: 1.25,factorY: 1.25,wheel: true,needshift: true,eps: 0.1
  }
 }
); 

(7) Create your own axes

I now turn off the default axes (with axis:false), and create my own axes.

I remove all the ticks with xaxis.removeAllTicks();

I then create equidistant ticks on the 2 axes (NOTE: The number itself is not in its own separate array. It should be like this: board.create('ticks', [xaxis, 50]);).

Here's the code:


var board = JXG.JSXGraph.initBoard('jxgbox7', {
  boundingbox:[-200,2.4, 240,-2.4],
  axis:false,
  grid:false,
  zoom: {
   factorX: 1.25,factorY: 1.25,wheel: true,needshift: true,eps: 0.1
  }
 }
);
var xaxis = board.create('axis',
	[ [0,0],[1,0] ], {
	 label: {offset: [7, -10]}, // Doesn't do anything here.
	 drawZero:false // Doesn't do anything here.
	}
); 
xaxis.removeAllTicks();
board.create('ticks', [xaxis, 50], { // The number here is the distance between Major ticks
	strokeColor:'#ccc',
	majorHeight:-1, // Need this because the JXG.Options one doesn't apply
	drawLabels:true, // Needed, and only works for equidistant ticks
	label: {offset: [-12, -10]},
	minorTicks:5, // The NUMBER of small ticks between each Major tick
	drawZero:true
 }
);
var yaxis = board.create('axis',	[ [0,0],[0,1] ]);
yaxis.removeAllTicks();
board.create('ticks', [yaxis, 1], {
	strokeColor:'#ccc',
	majorHeight:-1, // Need this because the JXG.Options one doesn't apply
	drawLabels:true, // Only works for equidistant ticks
	label: {offset: [7, -2]},
	minorTicks:1, // The NUMBER of small ticks between each Major tick
	drawZero:false
 }
);

Note: I have only one drawZero:true so the origin zeros don't overlap.

(8) Label x- and y-axes

To put the x and y labels on our axes, we proceed as follows:


var board = JXG.JSXGraph.initBoard('jxgbox8', { 
  boundingbox:[-3,13.5, 5.5,-10],
  axis:false,
  showCopyright:false,
  zoom: {
	  factorX:1.25,
	  factorY:1.25,
	  wheel:true,
	  needshift:true,
	  eps: 0.1
	 }
  }
);
xaxis = board.create('axis', [[0, 0], [1,0]], 
		  {name:'x', 
			withLabel: true, 
			label: {position: 'rt',  // possible values are 'lft', 'rt', 'top', 'bot'
					 offset: [-15, 20]   // (in pixels)
					 }
			});
yaxis = board.create('axis', [[0, 0], [0, 1]], 
		  {name:'y', 
			withLabel: true, 
			label: {
			  position: 'rt',  // possible values are 'lft', 'rt', 'top', 'bot'
			  offset: [-20, 0]   // (in pixels)
				}
			});					

(9) Fix zoom issues

The problem with (7) is that when you zoom in you lose the scale, and when you zoom out, you get too many ticks and labels.

Here, I'm using the function setTicks to change the ticks distance as you zoom in and out. It is triggered by board.on('boundingbox', function() { ... }); (which fires any time the bounding box is changed).

This time, I'm handling the ticks within the "create axis" construction. It doesn't make a lot of difference whether you do this, or create the ticks separately, like I did in (7).

Here's the code (this needed to be in a closure as it was being messed up by an earlier setting):


(function() {
	JXG.Options.axis.ticks.majorHeight = -1;
	// To prevent default tick labels:
	JXG.Options.axis.ticks.insertTicks = false; 
	var board = JXG.JSXGraph.initBoard('jxgbox9', {
		boundingbox: [-220,2.4,220,-2.4],
		axis: false, 
		grid: false,
		zoom: {
		 factorX: 1.25,factorY: 1.25,wheel: true,needshift: true,eps: 0.1
		}	
	 }
	);
	var xaxis = board.create('axis', [ [0,0],[1,0] ], {
		 ticks: { 
		   ticksDistance: 2,
		   label: {
			 offset: [-10, -12] // Must be within 'ticks:' definition
		   }, 
			minorTicks:1, // The NUMBER of small ticks between each Major tick
		   drawZero:true // Must be within 'ticks:' definition  
		 }
		}
	);
	var xTicks, yTicks, bb;

	xaxis.defaultTicks.ticksFunction = function () { return xTicks; };
	board.fullUpdate(); // full update is required
	yaxis = board.create('axis', [[0, 0], [0, 1]], {
		ticks: { 
		  ticksDistance: 2, 
		  label: {
			offset: [7, -2]
		  },
		  minorTicks:4 // The NUMBER of small ticks between each Major tick
		}
	 }
	);
	yaxis.defaultTicks.ticksFunction = function () { return yTicks; };
	board.fullUpdate(); // full update is required
	var setTicks = function() {
		bb = board.getBoundingBox();
		xTicksVal = Math.pow(10, Math.floor((Math.log(0.6*(bb[2]-bb[0])))/Math.LN10));
		if( (bb[2]-bb[0])/xTicksVal > 6) {
		  xTicks = xTicksVal;
		} else {
		  xTicks = 0.5* xTicksVal;
		}
		yTicksVal = Math.pow(10, Math.floor((Math.log(0.6*(bb[1]-bb[3])))/Math.LN10));
		if( (bb[1]-bb[3])/yTicksVal > 6) {
		  yTicks = yTicksVal;
		} else {
		  yTicks = 0.5* yTicksVal;
		}
		board.fullUpdate(); // full update is required
	}
	setTicks();
	board.on('boundingbox', function () { 
		setTicks();
	});	
})();

(10) Fix disappearing axes

The problem with (9) is that a lot of the time, depending on where you have zoomed to, you can't see the axes. So even if you have ticks where you want them, you can't see what their values are.

Here, I introduce "frozen" axes. Actually, the points making the axes are frozen when they get near the edge of the graph, using xaxis.point1.setAttribute({frozen:true});.

Also, the axes change color to indicate they are not in their "normal" position. Sometimes the axis labels overlap, but I don't believe it's a big issue.

Here's the code:


(function() {
	JXG.Options.slider.ticks.majorHeight = 0;
	var board = JXG.JSXGraph.initBoard('jxgbox10',{
	  boundingbox: [-220,2.4,220,-2.4], 
	  axis:false, 
	  showCopyright:false,  
	  showNavigation:false,
	  zoom: {factorX:1.25,factorY: 1.25,wheel:true,needshift:true,eps:0.1} 
	 } 
	);
	var grf = board.create('functiongraph', 
	  function(x){return 0.01*x*Math.sin(0.03*x); },{
		strokeColor:'#066',highlight:false
		}
	  );		  
	var xAxPt0 = board.create('point', [0,0], {
		needsRegularUpdate: false, visible: false});
	var xAxPt1 = board.create('point', [1,0], {
		needsRegularUpdate: false, visible: false});
	var xaxis = board.create('axis', 
		[xAxPt0,xAxPt1], {
		needsRegularUpdate: false, 
		ticks:{
		  label:{offset:[-10,-10]}, precision:5} 
	 }
	);
	var xTicks, yTicks, bb;
	xaxis.defaultTicks.ticksFunction = function () { return xTicks; };
	board.fullUpdate(); // full update is required
	var coords=[];
	var xPt0 = function(offset) {
		coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0, offset], board);	
		return coords.usrCoords;
	}
	var xPt1 = function(offset) {
		coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [board.canvasWidth, offset], board);
		return coords.usrCoords;
	}
	var yAxPt0 = board.create('point', [0,0], {
		needsRegularUpdate: false, visible: false});
	var yAxPt1 = board.create('point', [0,1], {
		needsRegularUpdate: false, visible: false});	
	var yaxis = board.create('axis', [yAxPt0,yAxPt1], {
		needsRegularUpdate: false, 
		ticks:{
		  label:{offset:[10,0],precision:8}
		} 
	  }
	);
	yaxis.defaultTicks.ticksFunction = function () { return yTicks; };
	board.fullUpdate();
	var yPt0 = function(offset) {
		coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [offset,board.canvasHeight], board);
		return coords.usrCoords;
	}
	var yPt1 = function(offset) {
		coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [offset,0], board);
		return coords.usrCoords;
	}
	var setTicks = function() {
		bb = board.getBoundingBox();
		xTicksVal = Math.pow(10, Math.floor((Math.log(0.6*(bb[2]-bb[0])))/Math.LN10));
		if( (bb[2]-bb[0])/xTicksVal > 5) {
		  xTicks = xTicksVal;
		} else {
		  xTicks = 0.5* xTicksVal;
		}
		yTicksVal = Math.pow(10, Math.floor((Math.log(0.6*(bb[1]-bb[3])))/Math.LN10));
		if( (bb[1]-bb[3])/yTicksVal > 5) {
		  yTicks = yTicksVal;
		} else {
		  yTicks = 0.5* yTicksVal;
		}
		board.fullUpdate(); // full update is required
	}
	setTicks();
	var origPt = board.create('point', [0,0],{visible:false});
	board.on('boundingbox', function() {
		bb = board.getBoundingBox();
		mycoordsY = new JXG.Coords(JXG.COORDS_BY_USER, [0,origPt.Y()], board);
		yPixels = mycoordsY.scrCoords[2];
		mycoordsX = new JXG.Coords(JXG.COORDS_BY_USER, [0,origPt.X()], board);
		xPixels = mycoordsX.scrCoords[1];	
		if( 30 > yPixels) {	
			xAxPt0.moveTo (xPt0(4),0);
			xAxPt1.moveTo (xPt1(4),0);
			xaxis.point1.setAttribute({frozen: true});
			xaxis.point2.setAttribute({frozen: true});
			xaxis.setAttribute({strokeColor:'#a00'});
			xaxis.defaultTicks.visProp.label.offset = [-10,-10];
		} else if( yPixels > board.canvasHeight - 30) {
			xAxPt0.moveTo (xPt0(board.canvasHeight - 10),0);
			xAxPt1.moveTo (xPt1(board.canvasHeight - 10),0);
			xaxis.point1.setAttribute({frozen: true});
			xaxis.point2.setAttribute({frozen: true});
			xaxis.setAttribute({color:'#a00'});
			xaxis.setAttribute({strokeColor:'#a00'});
			xaxis.defaultTicks.visProp.label.offset = [-10,9];
		} else {
			xaxis.point1.setAttribute({frozen: false});
			xaxis.point2.setAttribute({frozen: false});
			xaxis.setAttribute({color:'#666'});
			xAxPt0.moveTo ([0,0],0);
			xAxPt1.moveTo ([1,0],0);
		}	
		if( 30 > xPixels) {
			yAxPt0.moveTo (yPt0(5),0);
			yAxPt1.moveTo (yPt1(5),0);
			yaxis.point1.setAttribute({frozen: true});
			yaxis.point2.setAttribute({frozen: true});
			yaxis.setAttribute({strokeColor:' #a00'});
			yaxis.defaultTicks.visProp.label.offset = [7,0];
		} else if( xPixels > board.canvasWidth-30) {
			yAxPt0.moveTo (yPt0(board.canvasWidth-5),0);
			yAxPt1.moveTo (yPt1(board.canvasWidth-5),0);
			yaxis.point1.setAttribute({frozen: true});
			yaxis.point2.setAttribute({frozen: true});			
			yaxis.setAttribute({strokeColor: '#a00'});
			yaxis.defaultTicks.visProp.label.offset = [-28,0];
			yaxis.defaultTicks.visProp.label.align = 'right';
		} else {           
			yaxis.point1.setAttribute({frozen: false});
			yaxis.point2.setAttribute({frozen: false});
			yaxis.setAttribute({strokeColor:'#666'});
			yAxPt0.moveTo ([0,0],0);
			yAxPt1.moveTo ([0,1],0);
		}
		setTicks();
	});
})();

Credit: Some of the above is based on this Fiddle: http://jsfiddle.net/migerh/DWpAw/

(11) Axes-dependent zoom

The default zoom function moves the same amount in each direction, (a 25% move) as given by this default code: zoom: {factorX:1.25, factorY:1.25...}.

But often we want to only zoom in (or out) along one axis only.

Say your graph has a lot of interesting features near the x-axis (like the one shown), but the y-range is too high. That is, we only want to zoom the y-axis, and leave the domain (the x- values) as is.

Here's the relevant snippet from the setZoom function: board.attr.zoom.factorx = factX;.

When zooming this one, hold your mouse near the origin for best results.


var board = JXG.JSXGraph.initBoard('jxgbox11',{
  boundingbox: [-220,22,250,-22], 
  axis:false, 
  showCopyright:false,  
  showNavigation:false,
  zoom: {wheel: true,needshift: true,eps: 0.1} 
 } 
);
var factX = 1.25, factY = 1.0;
var setZoom = function() {
	board.attr.zoom.factorx = factX;
	board.attr.zoom.factory = factY;
}
setZoom();
var grf = board.create('functiongraph', 
  function(x){return 0.01*x*Math.abs(Math.sin(0.03*x)); },{
	strokeColor:'#066', highlight:false
	}
  );		  
var xAxPt0 = board.create('point', [0,0], {needsRegularUpdate: false, visible: false});
var xAxPt1 = board.create('point', [1,0], {needsRegularUpdate: false, visible: false});
var xaxis = board.create('axis', 
	[xAxPt0,xAxPt1], {
	needsRegularUpdate: false, 
	ticks:{
	  label:{offset:[-10,-10]}} 
 }
);
var xTicks,yTicks,bb;
//xaxis.defaultTicks.ticksFunction = function () { return xTicks; };
board.fullUpdate(); // full update is required
var coords=[];
var xPt0 = function(offset) {
	coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0, offset], board);	
	return coords.usrCoords;
}
var xPt1 = function(offset) {
	coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [board.canvasWidth, offset], board);
	return coords.usrCoords;
}
var yAxPt0 = board.create('point', [0,0], {needsRegularUpdate: false, visible: false});
var yAxPt1 = board.create('point', [0,1], {needsRegularUpdate: false, visible: false});	
var yaxis = board.create('axis', [yAxPt0,yAxPt1], {
	needsRegularUpdate: false, 
	ticks:{
	  label:{offset:[10,0]}
	} 
  }
);
//yaxis.defaultTicks.ticksFunction = function () { return yTicks; };
board.fullUpdate(); // full update is required
var yPt0 = function(offset) {
	coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [offset,board.canvasHeight], board);
	return coords.usrCoords;
}
var yPt1 = function(offset) {
	coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [offset,0], board);
	return coords.usrCoords;
}
var origPt = board.create('point', [0,0],{visible:false});
var setTicks = function() {
	bb = board.getBoundingBox();
	xTicksVal = Math.pow(10, Math.floor((Math.log(0.6*(bb[2]-bb[0])))/Math.LN10));
	if( (bb[2]-bb[0])/xTicksVal > 6) {
	  xTicks = xTicksVal;
	} else {
	  xTicks = 0.5* xTicksVal;
	}
	yTicksVal = Math.pow(10, Math.floor((Math.log(0.6*(bb[1]-bb[3])))/Math.LN10));
	if( (bb[1]-bb[3])/yTicksVal > 6) {
	  yTicks = yTicksVal;
	} else {
	  yTicks = 0.5* yTicksVal;
	}
	board.fullUpdate(); // full update is required
}
setTicks();
board.on('boundingbox', function() {
	bb = board.getBoundingBox();
	mycoordsY = new JXG.Coords(JXG.COORDS_BY_USER, [0,origPt.Y()], board);
	yPixels = mycoordsY.scrCoords[2];
	mycoordsX = new JXG.Coords(JXG.COORDS_BY_USER, [0,origPt.X()], board);
	xPixels = mycoordsX.scrCoords[1];	
	if( 30 > yPixels) {	
		xAxPt0.moveTo (xPt0(4),0);
		xAxPt1.moveTo (xPt1(4),0);
		xaxis.point1.setAttribute({frozen: true});
		xaxis.point2.setAttribute({frozen: true});
		xaxis.setAttribute({strokeColor:'#a00'});
		xaxis.defaultTicks.visProp.label.offset = [-10,-10];
	} else if( yPixels > board.canvasHeight - 30) {
		xAxPt0.moveTo (xPt0(board.canvasHeight - 10),0);
		xAxPt1.moveTo (xPt1(board.canvasHeight - 10),0);
		xaxis.point1.setAttribute({frozen: true});
		xaxis.point2.setAttribute({frozen: true});
		xaxis.setAttribute({color:'#a00'});
		xaxis.setAttribute({strokeColor: '#a00'});
		xaxis.defaultTicks.visProp.label.offset = [-10,9];
	} else {
		xaxis.point1.setAttribute({frozen: false});
		xaxis.point2.setAttribute({frozen: false});
		xaxis.setAttribute({color:'#666'});
		xAxPt0.moveTo ([0,0],0);
		xAxPt1.moveTo ([1,0],0);
	}	
	if( 30 > xPixels) {
		yAxPt0.moveTo (yPt0(5),0);
		yAxPt1.moveTo (yPt1(5),0);
		yaxis.point1.setAttribute({frozen: true});
		yaxis.point2.setAttribute({frozen: true});
		yaxis.setAttribute({strokeColor: '#a00'});
		yaxis.defaultTicks.visProp.label.offset = [7,0];
	} else if( xPixels > board.canvasWidth-30) {
		yAxPt0.moveTo (yPt0(board.canvasWidth-5),0);
		yAxPt1.moveTo (yPt1(board.canvasWidth-5),0);
		yaxis.point1.setAttribute({frozen: true});
		yaxis.point2.setAttribute({frozen: true});			
		yaxis.setAttribute({strokeColor:'#a00'});
		yaxis.defaultTicks.visProp.label.offset = [-23,0];
		yaxis.defaultTicks.visProp.label.align = 'right';
	} else {           
		yaxis.point1.setAttribute({frozen: false});
		yaxis.point2.setAttribute({frozen: false});
		yaxis.setAttribute({strokeColor: '#666'});
		yAxPt0.moveTo ([0,0],0);
		yAxPt1.moveTo ([0,1],0);
	}
	setTicks();
});


var options = document.getElementsByName("chooseDir");
if (options) {
    for (var i = 0; options.length > i; i++) {
		options[i].addEventListener("change", function() {
			if(this.value == 'zoomInX') {
				factX = 1.25;
				factY = 1.0;
			} else if(this.value == 'zoomInY') {
				factX = 1.0;
				factY = 1.25;
			} else {
				factX = 1.25;
				factY = 1.25;	
			}
			setZoom();			
		});		
    }
}

x-Directon zoom y-Direction zoom

Both directons zoom

(12) Special ticks (e.g. multiples of π)

Often we have multiples of π or other constants for one (or more) of our axes. You can produce such special ticks using the following.

Changes from (11) include:

The "scale" and "scaleSymbol" definitions in the xaxis definition: scale: Math.PI, scaleSymbol: '&#x3c0;' (the normal HTML way of entering lower case pi (&pi;), does not work as of current writing).

The multiple 0.2 in this line: xTicksVal = Math.pow(10, Math.floor((Math.log(0.2*(bb[2]-bb[0])))/Math.LN10));

Here's the code:


JXG.Options.slider.ticks.majorHeight = 0;
var board = JXG.JSXGraph.initBoard('jxgbox12',{
  boundingbox: [-7,2.4,20,-2.4], 
  axis:false, 
  showCopyright:false,  
  showNavigation:false,
  zoom: {factorX: 1.25,factorY: 1.25,wheel: true,needshift: true,eps: 0.1} 
 } 
);
var grf = board.create('functiongraph', 
  function(x){return 2*Math.sin(x); },{
	strokeColor:'#066', highlight:false
	}
  );		  
var xAxPt0 = board.create('point', [0,0], {needsRegularUpdate: false, visible: false});
var xAxPt1 = board.create('point', [1,0], {needsRegularUpdate: false, visible: false});
var xaxis = board.create('axis', 
	[xAxPt0,xAxPt1], {
	needsRegularUpdate: false, 
	ticks:{
	  label:{offset:[-10,-10]},
	  scale: Math.PI, 
	  scaleSymbol: 'π'
	  } 
 }
);
var xTicks, yTicks, bb;
xaxis.defaultTicks.ticksFunction = function () { return xTicks; };
board.fullUpdate(); // full update is required
var coords=[];
var xPt0 = function(offset) {
	coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0, offset], board);	
	return coords.usrCoords;
}
var xPt1 = function(offset) {
	coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [board.canvasWidth, offset], board);
	return coords.usrCoords;
}
var yAxPt0 = board.create('point', [0,0], {needsRegularUpdate: false, visible: false});
var yAxPt1 = board.create('point', [0,1], {needsRegularUpdate: false, visible: false});	
var yaxis = board.create('axis', [yAxPt0,yAxPt1], {
	needsRegularUpdate: false, 
	ticks:{
	  label:{offset:[10,0]}
	} 
  }
);
yaxis.defaultTicks.ticksFunction = function () { return yTicks; };
board.fullUpdate();
var yPt0 = function(offset) {
	coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [offset,board.canvasHeight], board);
	return coords.usrCoords;
}
var yPt1 = function(offset) {
	coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [offset,0], board);
	return coords.usrCoords;
}
var setTicks = function() {
	bb = board.getBoundingBox();
	xTicksVal = Math.pow(10, Math.floor((Math.log(0.2*(bb[2]-bb[0])))/Math.LN10));
	if( (bb[2]-bb[0])/xTicksVal > 7) {
	  xTicks = xTicksVal;
	} else {
	  xTicks = 0.5* xTicksVal;
	}
	yTicksVal = Math.pow(10, Math.floor((Math.log(0.6*(bb[1]-bb[3])))/Math.LN10));
	if( (bb[1]-bb[3])/yTicksVal > 6) {
	  yTicks = yTicksVal;
	} else {
	  yTicks = 0.5* yTicksVal;
	}
	board.fullUpdate(); // full update is required
}
setTicks();
var origPt = board.create('point', [0,0],{visible:false});
board.on('boundingbox', function() {
	bb = board.getBoundingBox();
	mycoordsY = new JXG.Coords(JXG.COORDS_BY_USER, [0,origPt.Y()], board);
	yPixels = mycoordsY.scrCoords[2];
	mycoordsX = new JXG.Coords(JXG.COORDS_BY_USER, [0,origPt.X()], board);
	xPixels = mycoordsX.scrCoords[1];	
	if( 10 > yPixels) {	
		xAxPt0.moveTo (xPt0(10),0);
		xAxPt1.moveTo (xPt1(10),0);
		xaxis.point1.setAttribute({frozen: true});
		xaxis.point2.setAttribute({frozen: true});
		xaxis.setAttribute({strokeColor:'#a00'});
		xaxis.defaultTicks.visProp.label.offset = [-10,-10];
	} else if( yPixels > board.canvasHeight - 10) {
		xAxPt0.moveTo (xPt0(board.canvasHeight - 10),0);
		xAxPt1.moveTo (xPt1(board.canvasHeight - 10),0);
		xaxis.point1.setAttribute({frozen: true});
		xaxis.point2.setAttribute({frozen: true});
		//xaxis.setAttribute({color:'#a00'});
		xaxis.setAttribute({strokeColor:'#a00'});
		xaxis.defaultTicks.visProp.label.offset = [-10,9];
	} else {
		xaxis.point1.setAttribute({frozen: false});
		xaxis.point2.setAttribute({frozen: false});
		xaxis.setAttribute({color:'#666'});
		xAxPt0.moveTo ([0,0],0);
		xAxPt1.moveTo ([1,0],0);
	}	
	if( 5 > xPixels) {
		yAxPt0.moveTo (yPt0(5),0);
		yAxPt1.moveTo (yPt1(5),0);
		yaxis.point1.setAttribute({frozen: true});
		yaxis.point2.setAttribute({frozen: true});
		yaxis.setAttribute({strokeColor:'#a00'});
		yaxis.defaultTicks.visProp.label.offset = [7,0];
	} else if( xPixels > board.canvasWidth-5) {
		yAxPt0.moveTo (yPt0(board.canvasWidth-5),0);
		yAxPt1.moveTo (yPt1(board.canvasWidth-5),0);
		yaxis.point1.setAttribute({frozen: true});
		yaxis.point2.setAttribute({frozen: true});			
		yaxis.setAttribute({strokeColor:'#a00'});
		yaxis.defaultTicks.visProp.label.offset = [-20,0];
		yaxis.defaultTicks.visProp.label.align = 'right';
	} else {           
		yaxis.point1.setAttribute({frozen: false});
		yaxis.point2.setAttribute({frozen: false});
		yaxis.setAttribute({strokeColor:'#666'});
		yAxPt0.moveTo ([0,0],0);
		yAxPt1.moveTo ([0,1],0);
	}
	setTicks();
});

(13) Units on axis labels

This one is similar to (12), except this time we have labelled the axes with different units.

We have scaleSymbol:' s' on the horizontal axis and scaleSymbol:' m' on the vertical axis.

Also, the axes are labelled with t and h.

Here's the code:


JXG.Options.slider.ticks.majorHeight = 0;
var board = JXG.JSXGraph.initBoard('jxgbox13',{
  boundingbox: [-1.2,5,6,-1.4], 
  axis:false, 
  showCopyright:false,  
  showNavigation:false,
  zoom: {factorX:1.25,factorY:1.25, wheel:true,needshift:true,eps:0.1} 
 } 
);
var grf = board.create('functiongraph', [
  function(x){return 4-(x-2)*(x-2); },0,4],
  {
	strokeColor:'#066',highlight:false
	}
  );
  
var xAxPt0 = board.create('point', [0,0], {needsRegularUpdate: false, visible: false});
var xAxPt1 = board.create('point', [1,0], {needsRegularUpdate: false, visible: false});
var xaxis = board.create('axis', 
	[xAxPt0,xAxPt1], {
	needsRegularUpdate: false,
	name:'t', 
	withLabel: true, 
	label: {position: 'rt',  // possible values are 'lft', 'rt', 'top', 'bot'
			 offset: [-15, 20]   // (in pixels)
   },	
	ticks:{
	  label:{offset:[-10,-10]}, 
	  scaleSymbol: ' s'
	  }
	  
 }
);

var xTicks, yTicks, bb;
xaxis.defaultTicks.ticksFunction = function () { return xTicks; };
board.fullUpdate(); // full update is required
var coords=[];
var xPt0 = function(offset) {
	coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [0, offset], board);	
	return coords.usrCoords;
}
var xPt1 = function(offset) {
	coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [board.canvasWidth, offset], board);
	return coords.usrCoords;
}
var yAxPt0 = board.create('point', [0,0], {needsRegularUpdate: false, visible: false});
var yAxPt1 = board.create('point', [0,1], {needsRegularUpdate: false, visible: false});	
var yaxis = board.create('axis', [yAxPt0,yAxPt1], {
	needsRegularUpdate: false,
	name:'h', 
	withLabel: true, 
	label: {position: 'rt',  // possible values are 'lft', 'rt', 'top', 'bot'
			 offset: [-15, 20]   // (in pixels)
   }, 
	ticks:{
	  label:{offset:[10,0]},
	  scaleSymbol: ' m'
	} 
  }
);

yaxis.defaultTicks.ticksFunction = function () { return yTicks; };
board.fullUpdate();
var yPt0 = function(offset) {
	coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [offset,board.canvasHeight], board);
	return coords.usrCoords;
}
var yPt1 = function(offset) {
	coords = new JXG.Coords(JXG.COORDS_BY_SCREEN, [offset,0], board);
	return coords.usrCoords;
}
var setTicks = function() {
	bb = board.getBoundingBox();
	xTicksVal = Math.pow(10, Math.floor((Math.log(0.2*(bb[2]-bb[0])))/Math.LN10));
	if( (bb[2]-bb[0])/xTicksVal > 7) {
	  xTicks = xTicksVal;
	} else {
	  xTicks = 0.5* xTicksVal;
	}
	yTicksVal = Math.pow(10, Math.floor((Math.log(0.6*(bb[1]-bb[3])))/Math.LN10));
	if( (bb[1]-bb[3])/yTicksVal > 6) {
	  yTicks = yTicksVal;
	} else {
	  yTicks = 0.5* yTicksVal;
	}
	board.fullUpdate(); // full update is required
}
setTicks();
var origPt = board.create('point', [0,0],{visible:false});

board.on('boundingbox', function() {
	bb = board.getBoundingBox();
	mycoordsY = new JXG.Coords(JXG.COORDS_BY_USER, [0,origPt.Y()], board);
	yPixels = mycoordsY.scrCoords[2];
	mycoordsX = new JXG.Coords(JXG.COORDS_BY_USER, [0,origPt.X()], board);
	xPixels = mycoordsX.scrCoords[1];	
	if( 10 > yPixels) {	
		xAxPt0.moveTo (xPt0(10),0);
		xAxPt1.moveTo (xPt1(10),0);
		xaxis.point1.setAttribute({frozen: true});
		xaxis.point2.setAttribute({frozen: true});
		xaxis.setAttribute({strokeColor:'#a00'});
		xaxis.defaultTicks.visProp.label.offset = [-10,-10];
	} else if( yPixels > board.canvasHeight - 10) {
		xAxPt0.moveTo (xPt0(board.canvasHeight - 10),0);
		xAxPt1.moveTo (xPt1(board.canvasHeight - 10),0);
		xaxis.point1.setAttribute({frozen: true});
		xaxis.point2.setAttribute({frozen: true});
		xaxis.setAttribute({color:'#a00'});
		xaxis.setAttribute({strokeColor:'#a00'});
		xaxis.defaultTicks.visProp.label.offset = [-10,9];
	} else {
		xaxis.point1.setAttribute({frozen: false});
		xaxis.point2.setAttribute({frozen: false});
		xaxis.setAttribute({color:'#666'});
		xAxPt0.moveTo ([0,0],0);
		xAxPt1.moveTo ([1,0],0);
	}	
	if( 5 > xPixels) {
		yAxPt0.moveTo (yPt0(5),0);
		yAxPt1.moveTo (yPt1(5),0);
		yaxis.point1.setAttribute({frozen: true});
		yaxis.point2.setAttribute({frozen: true});
		yaxis.setAttribute({strokeColor:'#a00'});
		yaxis.defaultTicks.visProp.label.offset = [7,0];
	} else if( xPixels > board.canvasWidth-5) {
		yAxPt0.moveTo (yPt0(board.canvasWidth-5),0);
		yAxPt1.moveTo (yPt1(board.canvasWidth-5),0);
		yaxis.point1.setAttribute({frozen: true});
		yaxis.point2.setAttribute({frozen: true});			
		yaxis.setAttribute({strokeColor:'#a00'});
		yaxis.defaultTicks.visProp.label.offset = [-20,0];
		yaxis.defaultTicks.visProp.label.align = 'right';
	} else {           
		yaxis.point1.setAttribute({frozen: false});
		yaxis.point2.setAttribute({frozen: false});
		yaxis.setAttribute({strokeColor:'#666'});
		yAxPt0.moveTo ([0,0],0);
		yAxPt1.moveTo ([0,1],0);
	}
	setTicks();
});

Any questions or comments on these examples, please add them here.

Search IntMath, blog and Forum