I'm building a bar plot in d3.js in which each bar represents total TB cases during a month. The data essentially consists of a date (initially strings in %Y-%m format, but parsed using d3.time.format.parse) and an integer. I'd like the axis labels to be relatively flexible (show just year boundaries, label each month, etc.), but I'd also like the bars to be evenly spaced.
I can get flexible axis labeling when I use a date scale:
var xScaleDate = d3.time.scale()
.domain(d3.extent(thisstat, function(d) { return d.date; }))
.range([0, width - margin.left - margin.right]);
... but the bars aren't evenly spaced due to varying numbers of days in each month (e.g., February and March are noticeably closer together than other months). I can get evenly-spaced bars using a linear scale:
var xScaleLinear = d3.scale.linear()
.domain([0, thisstat.length])
.range([0, width - margin.left - margin.right]);
... but I can't figure out how to then have date-based axis labels. I've tried using both scales simultaneously and only generating an axis from the xScaleDate
, just to see what would happen, but the scales naturally don't align quite right.
Is there a straightforward way to achieve this that I'm missing?
I'm building a bar plot in d3.js in which each bar represents total TB cases during a month. The data essentially consists of a date (initially strings in %Y-%m format, but parsed using d3.time.format.parse) and an integer. I'd like the axis labels to be relatively flexible (show just year boundaries, label each month, etc.), but I'd also like the bars to be evenly spaced.
I can get flexible axis labeling when I use a date scale:
var xScaleDate = d3.time.scale()
.domain(d3.extent(thisstat, function(d) { return d.date; }))
.range([0, width - margin.left - margin.right]);
... but the bars aren't evenly spaced due to varying numbers of days in each month (e.g., February and March are noticeably closer together than other months). I can get evenly-spaced bars using a linear scale:
var xScaleLinear = d3.scale.linear()
.domain([0, thisstat.length])
.range([0, width - margin.left - margin.right]);
... but I can't figure out how to then have date-based axis labels. I've tried using both scales simultaneously and only generating an axis from the xScaleDate
, just to see what would happen, but the scales naturally don't align quite right.
Is there a straightforward way to achieve this that I'm missing?
Share Improve this question edited Sep 12, 2012 at 12:44 John Conde 220k99 gold badges462 silver badges501 bronze badges asked Aug 29, 2012 at 20:55 Matt ParkerMatt Parker 27.3k7 gold badges57 silver badges73 bronze badges 1-
It sounds like the problem is that D3 is setting xpos from the actual timestamp. How bout you calculate
data.monthsFromJan2000
or similar and use that as your data input for the xpos? That way they will be evenly spaced. You can also storedata.year
anddata.month
for labeling purposes. – Plato Commented Mar 4, 2013 at 15:44
3 Answers
Reset to default 10You can bine ordinal and time scales:
// Use this to draw x axis
var xScaleDate = d3.time.scale()
.domain(d3.extent(thisstat, function(d) { return d.date; }))
.range([0, width - margin.left - margin.right]);
// Add an ordinal scale
var ordinalXScale = d3.scale.ordinal()
.domain(d3.map(thisstat, function(d) { return d.date; }))
.rangeBands([0, width], 0.4, 0);
// Now you can use both of them to space columns evenly:
columnGroup.enter()
.append("rect")
.attr("class", "column")
.attr("width", ordinalXScale.rangeBand())
.attr("height", function (d) {
return height - yScale(d.value);
})
.attr("x", function (d) {
return xScaleDate(d.date);
})
.attr("y", function (d){
return yScale(d.value);
});
I've created an example a while ago to demonstrate this approach: http://codepen.io/coquin/pen/BNpQoO
I had the same problem, I've ended up accepting that some months are longer than others and adjusting the column bar width so that the gap between bars remains constant. So tweaking the barPath function in the crossfilter home page demo (http://square.github./crossfilter/ - uses d3) I got something like this:
var colWidth = Math.floor(x.range()[1] / groups.length) - 1;//9;
if (i < n - 1) {
//If there will be column on the right, end this column one pixel to the left
var nextX = x(groups[i+1].key)
colWidth = nextX - x(d.key) - 1;
}
path.push("M", x(d.key), ",", height, "V", yVal, "h",colWidth,"V", height);
Try d3.scale.ordinal
:
var y = d3.scale.ordinal()
.domain(yourDomain)
.rangeRoundBands([0, chartHeight], 0.2);
Tweek 0.2 parameter.