Home > Tutorials > D3 Tutorials > Positioning axes
Positioning axes
The problem
The worst thing about using axes is they just don’t seem to fit on the page . Let’s say we have two scales:
One for the x-axis with a range
from 0
to width
One for the y-axis with a range
from height
to 0
(it’s inverted, remember!)
That works fine when we’re just displaying the data, but as soon as we add in axes they flop off the bottom and the left-hand sides.
Open example in new window
<style>
.axis path ,
.axis line {
fill : none ;
stroke : black ;
shape-rendering : crispEdges ;
}
.axis text {
font-family : sans-serif ;
font-size : 11px ;
}
</style>
<svg></svg>
<script>
var datapoints = [
{ 'title' : 'Pride and Prejudice' , 'author' : 'Jane Austen' , 'words' : 120000 , 'published' : 1813 },
{ 'title' : 'Cryptonomicon' , 'author' : 'Neal Stephenson' , 'words' : 415000 , 'published' : 1999 },
{ 'title' : 'Great Gatsby' , 'author' : 'F. Scott Fitzgerald' , 'words' : 47094 , 'published' : 1925 },
{ 'title' : 'Song of Solomon' , 'author' : 'Toni Morrison' , 'words' : 92400 , 'published' : 1977 },
{ 'title' : 'White Teeth' , 'author' : 'Zadie Smith' , 'words' : 169000 , 'published' : 2000 }
];
var height = 300 , width = 600 ;
var svg = d3 . select ( "svg" ). attr ( 'height' , height ). attr ( 'width' , width );
var x_scale = d3 . scale . linear (). domain ([ 0 , 500000 ]). range ([ 0 , width ]);
// and ORDINAL scale is for categorical data
var titles = datapoints . map ( function ( d ) { return d [ 'title' ] });
// range bands to the rescue for non-numeric domains
// https://github.com/mbostock/d3/wiki/Ordinal-Scales#ordinal_rangeBands
// http://jaketrent.com/post/use-d3-rangebands/
var y_scale = d3 . scale . ordinal (). domain ( titles ). rangeBands ([ height , 0 ], 0.5 , 0.2 );
var xAxis = d3 . svg . axis ()
. scale ( x_scale )
. orient ( "bottom" );
var yAxis = d3 . svg . axis ()
. scale ( y_scale )
. orient ( "left" );
svg . append ( 'g' ). attr ( 'class' , 'axis' ). call ( xAxis ). attr ( "transform" , "translate(0," + height + ")" );
svg . append ( 'g' ). attr ( 'class' , 'axis' ). call ( yAxis ). attr ( "transform" , "translate(0,0)" );;
svg . selectAll ( 'rect' )
. data ( datapoints )
. enter ()
. append ( 'rect' )
. attr ( 'y' , function ( d ) {
return y_scale ( d [ 'title' ]);
})
. attr ( 'x' , 0 )
. attr ( 'height' , y_scale . rangeBand ())
. attr ( 'width' , function ( d ) {
return x_scale ( d [ 'words' ]);
})
</script>
Open example in new window
The thing is, the axes are still there , they’re just off the sides. If you use the web inspector…
The solution
Remember our friend g
, the element that just holds miscellaneous stuff? One of the benefits of the g
element it acts like an svg
element - suddenly (0,0)
is the top left of the g
, even if you move the g
halfway across the page.
Move the g
into the middle of the svg
and put padding around it.
Put the chart inside of the g
. The 0,0
is no longer the top of the svg
, but rather the top-left of the g
.
The overflow goes outside of the g
, but it’s still inside of the svg
, thanks to the margin.
Partytime!
Confused? Take a look at this image from this margin convention piece .
First, we need to establish our margin:
var margin = 50 ;
Now you need to juggle the difference between the height/width of the svg
, and the height/width of the g
element. There are a lot of ways to do this, and you’ll see many options out there, so this is just one particular method - making separate heights and widths for your chart and your svg.
var svg_height = 300 ,
svg_width = 600 ;
var svg = d3 . select ( "svg" ). attr ( 'height' , svg_height ). attr ( 'width' , svg_width );
// subtract margin for the top AND the bottom
var height = svg_height - margin * 2 ;
// subtract margin for the left AND the right
var width = svg_width - margin * 2 ;
And then add a g
to your svg
, and use translate
/transform
to shift the top-left-hand corner of the g
on over.
var chart = svg . append ( "g" ). attr ( 'translate' , 'transform(' + margin + ',' + margin + ')' );
Now instead of using selectAll/etc on the svg
, append to the g
!
chart . append ( 'g' ). attr ( 'class' , 'axis' ). call ( xAxis ). attr ( "transform" , "translate(0," + height + ")" );
chart . append ( 'g' ). attr ( 'class' , 'axis' ). call ( yAxis ). attr ( "transform" , "translate(0,0)" );;
chart . selectAll ( 'rect' )
. data ( datapoints )
. enter ()
....
Open example in new window
<style>
.axis path ,
.axis line {
fill : none ;
stroke : black ;
shape-rendering : crispEdges ;
}
.axis text {
font-family : sans-serif ;
font-size : 11px ;
}
</style>
<svg></svg>
<script>
var datapoints = [
{ 'title' : 'Pride and Prejudice' , 'author' : 'Jane Austen' , 'words' : 120000 , 'published' : 1813 },
{ 'title' : 'Cryptonomicon' , 'author' : 'Neal Stephenson' , 'words' : 415000 , 'published' : 1999 },
{ 'title' : 'Great Gatsby' , 'author' : 'F. Scott Fitzgerald' , 'words' : 47094 , 'published' : 1925 },
{ 'title' : 'Song of Solomon' , 'author' : 'Toni Morrison' , 'words' : 92400 , 'published' : 1977 },
{ 'title' : 'White Teeth' , 'author' : 'Zadie Smith' , 'words' : 169000 , 'published' : 2000 }
];
var margin = 50 ;
var svg_height = 300 ,
svg_width = 600 ;
var svg = d3 . select ( "svg" ). attr ( 'height' , svg_height ). attr ( 'width' , svg_width );
var chart = svg . append ( "g" ). attr ( 'transform' , 'translate(' + margin + ',' + margin + ')' );
// subtract margin for the top AND the bottom
var height = svg_height - margin * 2 ;
// subtract margin for the left AND the right
var width = svg_width - margin * 2 ;
var x_scale = d3 . scale . linear (). domain ([ 0 , 500000 ]). range ([ 0 , width ]);
// and ORDINAL scale is for categorical data
var titles = datapoints . map ( function ( d ) { return d [ 'title' ] });
// range bands to the rescue for non-numeric domains
// https://github.com/mbostock/d3/wiki/Ordinal-Scales#ordinal_rangeBands
// http://jaketrent.com/post/use-d3-rangebands/
var y_scale = d3 . scale . ordinal (). domain ( titles ). rangeBands ([ height , 0 ], 0.5 , 0.2 );
var xAxis = d3 . svg . axis ()
. scale ( x_scale )
. orient ( "bottom" );
var yAxis = d3 . svg . axis ()
. scale ( y_scale )
. orient ( "left" );
chart . append ( 'g' ). attr ( 'class' , 'axis' ). call ( xAxis ). attr ( "transform" , "translate(0," + height + ")" );
chart . append ( 'g' ). attr ( 'class' , 'axis' ). call ( yAxis ). attr ( "transform" , "translate(0,0)" );;
chart . selectAll ( 'rect' )
. data ( datapoints )
. enter ()
. append ( 'rect' )
. attr ( 'y' , function ( d ) {
return y_scale ( d [ 'title' ]);
})
. attr ( 'x' , 0 )
. attr ( 'height' , y_scale . rangeBand ())
. attr ( 'width' , function ( d ) {
return x_scale ( d [ 'words' ]);
})
</script>
Open example in new window
Okay, that’s better , but we’re going to have to push in a ton on the left hand side in order to make this work. Instead of making a huge margin on all sides, instead we can set the left, right, top and bottom margins individually.
var margins = { 'top' : 20 , 'left' : 125 , 'bottom' : 20 , 'left' : 20 };
Now we just have to make sure that when we specify our margins we pick the right ones between left and right and top and bottom.
var chart = svg . append ( "g" ). attr ( 'transform' , 'translate(' + margin [ 'left' ] + ',' + margin [ 'top' ] + ')' );
var height = svg_height - margin [ 'top' ] - margin [ 'bottom' ];
var width = svg_width - margin [ 'left' ] - margin [ 'right' ];
Open example in new window
<style>
.axis path ,
.axis line {
fill : none ;
stroke : black ;
shape-rendering : crispEdges ;
}
.axis text {
font-family : sans-serif ;
font-size : 11px ;
}
</style>
<svg></svg>
<script>
var datapoints = [
{ 'title' : 'Pride and Prejudice' , 'author' : 'Jane Austen' , 'words' : 120000 , 'published' : 1813 },
{ 'title' : 'Cryptonomicon' , 'author' : 'Neal Stephenson' , 'words' : 415000 , 'published' : 1999 },
{ 'title' : 'Great Gatsby' , 'author' : 'F. Scott Fitzgerald' , 'words' : 47094 , 'published' : 1925 },
{ 'title' : 'Song of Solomon' , 'author' : 'Toni Morrison' , 'words' : 92400 , 'published' : 1977 },
{ 'title' : 'White Teeth' , 'author' : 'Zadie Smith' , 'words' : 169000 , 'published' : 2000 }
];
var margin = { 'top' : 20 , 'left' : 125 , 'bottom' : 20 , 'right' : 20 };
var svg_height = 300 ,
svg_width = 600 ;
var svg = d3 . select ( "svg" ). attr ( 'height' , svg_height ). attr ( 'width' , svg_width );
var chart = svg . append ( "g" ). attr ( 'transform' , 'translate(' + margin [ 'left' ] + ',' + margin [ 'right' ] + ')' );
// subtract margin for the top AND the bottom
var height = svg_height - margin [ 'top' ] - margin [ 'bottom' ];
// subtract margin for the left AND the right
var width = svg_width - margin [ 'left' ] - margin [ 'right' ];
var x_scale = d3 . scale . linear (). domain ([ 0 , 500000 ]). range ([ 0 , width ]);
// and ORDINAL scale is for categorical data
var titles = datapoints . map ( function ( d ) { return d [ 'title' ] });
// range bands to the rescue for non-numeric domains
// https://github.com/mbostock/d3/wiki/Ordinal-Scales#ordinal_rangeBands
// http://jaketrent.com/post/use-d3-rangebands/
var y_scale = d3 . scale . ordinal (). domain ( titles ). rangeBands ([ height , 0 ], 0.5 , 0.2 );
var xAxis = d3 . svg . axis ()
. scale ( x_scale )
. orient ( "bottom" );
var yAxis = d3 . svg . axis ()
. scale ( y_scale )
. orient ( "left" );
chart . append ( 'g' ). attr ( 'class' , 'axis' ). call ( xAxis ). attr ( "transform" , "translate(0," + height + ")" );
chart . append ( 'g' ). attr ( 'class' , 'axis' ). call ( yAxis ). attr ( "transform" , "translate(0,0)" );;
chart . selectAll ( 'rect' )
. data ( datapoints )
. enter ()
. append ( 'rect' )
. attr ( 'y' , function ( d ) {
return y_scale ( d [ 'title' ]);
})
. attr ( 'x' , 0 )
. attr ( 'height' , y_scale . rangeBand ())
. attr ( 'width' , function ( d ) {
return x_scale ( d [ 'words' ]);
})
</script>
Open example in new window
Now that you can adjust each margin individually, you’re all set! If you’d like a little more info, I highly highly recommend this piece .