Richard
Richard

Reputation: 65600

D3.js: calculate x-axis time scale for bar graph?

I have the following dataset:

var data = [
  {
    "air_used": 0.660985, 
    "datestr": "2012-12-01 00:00:00", 
    "energy_used": 0.106402
  }, 
  {
    "air_used": 0.824746, 
    "datestr": "2013-01-01 00:00:00", 
    "energy_used": 0.250462
  } ...
]

And I want to draw a bar graph (for air_used) and line graph (for energy_used) that look like this:

enter image description here

My problem is that at the moment, with the x-scale I'm using, the graph looks like this - basically the bars are in the wrong position, and the last bar is falling off the chart:

enter image description here

Here is a JSFiddle with full code and working graph: http://jsfiddle.net/aWJtJ/4/

To achieve what I want, I think I need to amend the x-scale so that there is extra width before the first data point and after the last data point, and so that the bars are all shifted to the left by half the width of each bar.

Can anyone help me figure out what I need to do with the x-scale?

I've tried adding an extra month to the domain - that stops the last bar falling off the end of the graph, but it also adds an extra tick that I don't want, and it doesn't fix the position of the line graph and ticks.

If possible I want to continue to a time scale for the x-axis, rather than an ordinal scale, because I want to use D3's clever time-based tick formatters and date parsers, e.g. xAxis.ticks(d3.time.weeks, 2).

Upvotes: 4

Views: 6003

Answers (2)

jcollado
jcollado

Reputation: 40424

As pointed out in the comments, I think the best approach is to use d3.scale.ordinal. Note that using it doesn't prevent you from using d3.time parsers, but you need to take into account the bar width to align the line with the bars.

An example solution is here: http://jsfiddle.net/jcollado/N8tuR/

Relevant code from the solution above is as follows:

// Map data set to dates to provide the whole domain information
var x = d3.scale.ordinal()
    .domain(data.map(function(d) {
        return d.date;
    }))
    .rangeRoundBands([0, width], 0.1);

...

// Use x.rangeBand() to align line with bars
var line = d3.svg.line()
    .x(function(d) { return x(d.date) + x.rangeBand() / 2; })
    .y(function(d) { return y(d.energy_used); });

...

// Use x.rangeBand() to set bar width
bars.enter().append("rect")
    .attr("class", "air_used")
    .attr("width", x.rangeBand())
    ...

Note that date parsing code has been moved up to have d.date available when creating the x scale. Aside from that, d3.time statements have not been modified at all.

Upvotes: 0

reblace
reblace

Reputation: 4195

Expand your domain to be +1 and -1 month from the actual extent of your data. That will pad the graph with the extra months on either side and then update the bar width to add 2 to the count of data elements.

var barRawWidth = width / (data.length + 2);

See this fiddle: http://jsfiddle.net/reblace/aWJtJ/6/

If you want to hide the lower and upper boundary months, you can hack it like this: http://jsfiddle.net/reblace/aWJtJ/7/ by just adding and subtracting 20 days instead of a whole month, but there are probably more elegant ways to do it.

var xExtent = d3.extent(data, function(d) { return d.date; });
var nxExtent = [d3.time.day.offset(xExtent[0], -20), d3.time.day.offset(xExtent[1], 20)];
x.domain(nxExtent);

Upvotes: 4

Related Questions