3

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).

Richard
  • 62,943
  • 126
  • 334
  • 542
  • I've got a basic fix working, but (1) it feels quite hacky and (2) the x-axis line now doesn't go where it should: http://jsfiddle.net/aWJtJ/5/ – Richard Sep 17 '13 at 15:34
  • Also, the months are different widths, as in this question: http://stackoverflow.com/questions/12186366/d3-js-evenly-spaced-bars-on-a-time-scale – Richard Sep 17 '13 at 16:01
  • 2
    To fix the months are diff widths issue, you should consider making the axis ordinal based on the month/year portion of the timestamp and then using a custom formatter to specify the tick label. Then, you can use range bands to deal with the bar widths: https://github.com/mbostock/d3/wiki/Ordinal-Scales#wiki-ordinal_rangeBands – reblace Sep 17 '13 at 16:20

2 Answers2

4

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);
reblace
  • 4,115
  • 16
  • 16
0

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.

jcollado
  • 39,419
  • 8
  • 102
  • 133
  • 2
    Doesn't this have the big disadvantage that missing values are not accounted for in the scale? For example, if I remove February from your example data, the graph goes directly from January to March. – explunit Apr 21 '14 at 17:47
  • @explunit Yes, that's correct. With the ordinal scale you need to pass all data even if the value is 0. – jcollado Apr 22 '14 at 11:34