There is really no need to interpolate your values. You actually can modify the scale of most nvd3 charts, including multiBarCharts, although there is some extra work that needs to be done to make it work.
The basic thing you need to do is this:
var xScale = d3.time.scale();
chart.multibar.xScale(xScale);
Then that should just work! Except it doesn't, because the multiBarChart assumes that the xScale is d3.scale.ordinal()
. So you will need to fake being that type by setting xScale.rangeBands
and xScale.rangeBand
:
xScale.rangeBands = xScale.range;
xScale.rangeBand = function() { return (1 - chart.groupSpacing()) * SOME_VALUE };
The problem now is getting SOME_VALUE. This needs to equal the width of an individual bar, which depends on two things: the width of the whole chart and the number of ticks there would be, including the zero values that are missing in the data.
Here's how nvd3 gets the available width internally:
var container = d3.select('#chart svg'),
availableWidth = (chart.width() || parseInt(container.style('width')) || 960) - chart.margin().left - chart.margin().right;
However, if the window resizes, you will need to refresh this value:
nv.utils.windowResize(function() {
availableWidth = (chart.width() || parseInt(container.style('width')) || 960) - chart.margin().left - chart.margin().right;
});
As for getting the number of ticks, this depends solely on your data. In your case, there will be 11 ticks: every year between 2001 and 2011. So we'll go with that. Therefore, the entire scale definition looks like this:
var container = d3.select('#chart svg'),
availableWidth,
numTicks = 11,
xScale = d3.time.scale();
function updateAvailableWidth() {
availableWidth = (chart.width() || parseInt(container.style('width')) || 960) - chart.margin().left - chart.margin().right;
}
updateAvailableWidth();
nv.utils.windowResize(updateAvailableWidth);
xScale.rangeBands = xScale.range;
xScale.rangeBand = function() { return (1 - chart.groupSpacing()) * availableWidth / numTicks; };
chart.multibar.xScale(xScale);
Finally, you need to set your xDomain manually. If you did this with the ordinal scale it had before, it would fail, but with a linear time scale it will work excellently:
chart.xDomain([new Date().setFullYear('2001'), new Date().setFullYear('2011')]);
Putting it all together, here is your example code (pasteable into http://nvd3.org/livecode/#codemirrorNav):
nv.addGraph(function() {
var chart = nv.models.multiBarChart(),
container = d3.select('#chart svg'),
availableWidth,
numTicks = 11,
xScale = d3.time.scale();
function updateAvailableWidth() {
availableWidth = (chart.width() || parseInt(container.style('width')) || 960) - chart.margin().left - chart.margin().right;
}
updateAvailableWidth();
nv.utils.windowResize(updateAvailableWidth);
xScale.rangeBands = xScale.range;
xScale.rangeBand = function() { return (1 - chart.groupSpacing()) * availableWidth / numTicks; };
chart.multibar.xScale(xScale);
chart.xDomain([new Date().setFullYear('2001'), new Date().setFullYear('2011')]);
chart.xAxis
.tickFormat(function(d){ return d3.time.format('%y')(new Date(d)); });
chart.yAxis
.tickFormat(d3.format(',f'));
chart.reduceXTicks(false);
chart.showControls(false);
var data = [{
'key': 'GB by year',
'values': [
{x: new Date().setFullYear('2001'), y: 0.12},
{x: new Date().setFullYear('2004'), y: 0.03},
{x: new Date().setFullYear('2005'), y: 0.53},
{x: new Date().setFullYear('2006'), y: 0.43},
{x: new Date().setFullYear('2007'), y: 5.5},
{x: new Date().setFullYear('2008'), y: 9.9},
{x: new Date().setFullYear('2009'), y: 26.85},
{x: new Date().setFullYear('2010'), y: 0.03},
{x: new Date().setFullYear('2011'), y: 0.12}
]
}];
container.datum(data).transition().duration(500).call(chart);
nv.utils.windowResize(chart.update);
return chart;
});