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

Page by Murray Bourne, IntMath.com. Last updated: 21 Feb 2016

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.

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, we cannot see the positive 5 on the x-axis, or −5 on the y-axis.

You can zoom in and out (hold down <Shift> on the keyboard while using the mouse scroll wheel to zoom), and see that sometimes the default grids disappear, meaning we don't know where we are.


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

(2) grid:true

One way to get a better idea of the scale is to turn on grids.

The default when using grid:true gives a 1×1 grid.

This is fine for some boundary settings, but not so attractive for others (like the one shown, since the default axis grid lines at 2.5 are odd). I have set axis:true and grid:true.


var board = JXG.JSXGraph.initBoard('jxgbox1', { 
  boundingbox:[-1,5.5,5,-1],
  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) Disappearing scale indication

If we have the following bounding box, [-24,24, 24,-24], with grid:true, we get no indication what the scale is (no numbers appear on either axis), and there are too many grid lines.

With or without grids, we have no idea of the scale.


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

(4) Large scale difference between axes

When the scale is quite different for each axis, there can be problems. In this example, the x-axis goes from −200 to 240, while the y-axis is only −2.4 to 2.4.

I've turned off the grids in this case, since it was a dense mass of grid lines. (We'll see them a bit later.)

boundingbox:[-200,2.4, 240,-2.4],
axis:true,
grid:false

We still have no sense of scale.

(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:

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('jxgbox5', {
  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('jxgbox6', {
  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('jxgbox7', { 
  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:


JXG.Options.axis.ticks.majorHeight = -1;
// To prevent default tick labels:
JXG.Options.axis.ticks.insertTicks = false; 
var board = JXG.JSXGraph.initBoard('jxgbox8', {
	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 xTicks, yTicks, bb;
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  
	 }
	}
);
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.setProperty({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:


JXG.Options.slider.ticks.majorHeight = 0;
var board = JXG.JSXGraph.initBoard('jxgbox9',{
  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( yPixels < 30) {	
		xAxPt0.moveTo (xPt0(4),0);
		xAxPt1.moveTo (xPt1(4),0);
		xaxis.point1.setProperty({frozen:true});
		xaxis.point2.setProperty({frozen:true});
		xaxis.setProperty({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.setProperty({frozen:true});
		xaxis.point2.setProperty({frozen:true});
		xaxis.strokeColor('#a00');
		xaxis.setProperty({strokeColor:'#a00'});
		xaxis.defaultTicks.visProp.label.offset = [-10,9];
	} else {
		xaxis.point1.setProperty({frozen:false});
		xaxis.point2.setProperty({frozen:false});
		xaxis.strokeColor('#666');
		xAxPt0.moveTo ([0,0],0);
		xAxPt1.moveTo ([1,0],0);
	}	
	if( xPixels < 30) {
		yAxPt0.moveTo (yPt0(5),0);
		yAxPt1.moveTo (yPt1(5),0);
		yaxis.point1.setProperty({frozen:true});
		yaxis.point2.setProperty({frozen:true});
		yaxis.setProperty({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.setProperty({frozen:true});
		yaxis.point2.setProperty({frozen:true});			
		yaxis.setProperty({strokeColor:'#a00'});
		yaxis.defaultTicks.visProp.label.offset = [-28,0];
		yaxis.defaultTicks.visProp.label.align = 'right';
	} else {           
		yaxis.point1.setProperty({frozen:false});
		yaxis.point2.setProperty({frozen:false});
		yaxis.setProperty({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('jxgbox10',{
  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( yPixels < 30) {	
		xAxPt0.moveTo (xPt0(4),0);
		xAxPt1.moveTo (xPt1(4),0);
		xaxis.point1.setProperty({frozen:true});
		xaxis.point2.setProperty({frozen:true});
		xaxis.setProperty({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.setProperty({frozen:true});
		xaxis.point2.setProperty({frozen:true});
		xaxis.strokeColor('#a00');
		xaxis.setProperty({strokeColor:'#a00'});
		xaxis.defaultTicks.visProp.label.offset = [-10,9];
	} else {
		xaxis.point1.setProperty({frozen:false});
		xaxis.point2.setProperty({frozen:false});
		xaxis.strokeColor('#666');
		xAxPt0.moveTo ([0,0],0);
		xAxPt1.moveTo ([1,0],0);
	}	
	if( xPixels < 30) {
		yAxPt0.moveTo (yPt0(5),0);
		yAxPt1.moveTo (yPt1(5),0);
		yaxis.point1.setProperty({frozen:true});
		yaxis.point2.setProperty({frozen:true});
		yaxis.setProperty({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.setProperty({frozen:true});
		yaxis.point2.setProperty({frozen:true});			
		yaxis.setProperty({strokeColor:'#a00'});
		yaxis.defaultTicks.visProp.label.offset = [-23,0];
		yaxis.defaultTicks.visProp.label.align = 'right';
	} else {           
		yaxis.point1.setProperty({frozen:false});
		yaxis.point2.setProperty({frozen:false});
		yaxis.setProperty({strokeColor:'#666'});
		yAxPt0.moveTo ([0,0],0);
		yAxPt1.moveTo ([0,1],0);
	}
	setTicks();
});


$('input[name=chooseDir]').change(function(){
	if($(this).val() == 'zoomInX') {
		factX = 1.25;
		factY = 1.0;
	} else if($(this).val() == '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: 'π'

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('jxgbox11',{
  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( yPixels < 10) {	
		xAxPt0.moveTo (xPt0(10),0);
		xAxPt1.moveTo (xPt1(10),0);
		xaxis.point1.setProperty({frozen:true});
		xaxis.point2.setProperty({frozen:true});
		xaxis.setProperty({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.setProperty({frozen:true});
		xaxis.point2.setProperty({frozen:true});
		xaxis.strokeColor('#a00');
		xaxis.setProperty({strokeColor:'#a00'});
		xaxis.defaultTicks.visProp.label.offset = [-10,9];
	} else {
		xaxis.point1.setProperty({frozen:false});
		xaxis.point2.setProperty({frozen:false});
		xaxis.strokeColor('#666');
		xAxPt0.moveTo ([0,0],0);
		xAxPt1.moveTo ([1,0],0);
	}	
	if( xPixels < 5) {
		yAxPt0.moveTo (yPt0(5),0);
		yAxPt1.moveTo (yPt1(5),0);
		yaxis.point1.setProperty({frozen:true});
		yaxis.point2.setProperty({frozen:true});
		yaxis.setProperty({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.setProperty({frozen:true});
		yaxis.point2.setProperty({frozen:true});			
		yaxis.setProperty({strokeColor:'#a00'});
		yaxis.defaultTicks.visProp.label.offset = [-20,0];
		yaxis.defaultTicks.visProp.label.align = 'right';
	} else {           
		yaxis.point1.setProperty({frozen:false});
		yaxis.point2.setProperty({frozen:false});
		yaxis.setProperty({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('jxgbox12',{
  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( yPixels < 10) {	
		xAxPt0.moveTo (xPt0(10),0);
		xAxPt1.moveTo (xPt1(10),0);
		xaxis.point1.setProperty({frozen:true});
		xaxis.point2.setProperty({frozen:true});
		xaxis.setProperty({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.setProperty({frozen:true});
		xaxis.point2.setProperty({frozen:true});
		xaxis.strokeColor('#a00');
		xaxis.setProperty({strokeColor:'#a00'});
		xaxis.defaultTicks.visProp.label.offset = [-10,9];
	} else {
		xaxis.point1.setProperty({frozen:false});
		xaxis.point2.setProperty({frozen:false});
		xaxis.strokeColor('#666');
		xAxPt0.moveTo ([0,0],0);
		xAxPt1.moveTo ([1,0],0);
	}	
	if( xPixels < 5) {
		yAxPt0.moveTo (yPt0(5),0);
		yAxPt1.moveTo (yPt1(5),0);
		yaxis.point1.setProperty({frozen:true});
		yaxis.point2.setProperty({frozen:true});
		yaxis.setProperty({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.setProperty({frozen:true});
		yaxis.point2.setProperty({frozen:true});			
		yaxis.setProperty({strokeColor:'#a00'});
		yaxis.defaultTicks.visProp.label.offset = [-20,0];
		yaxis.defaultTicks.visProp.label.align = 'right';
	} else {           
		yaxis.point1.setProperty({frozen:false});
		yaxis.point2.setProperty({frozen:false});
		yaxis.setProperty({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