The worst thing about using axes is they just don’t seem to fit on the page. Let’s say we have two scales:
range
from 0
to width
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.
<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>
The thing is, the axes are still there, they’re just off the sides. If you use the web inspector…
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.
g
into the middle of the svg
and put padding around it.g
. The 0,0
is no longer the top of the svg
, but rather the top-left of the g
.g
, but it’s still inside of the svg
, thanks to the margin.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()
....
<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>
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'];
<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>
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.