NVD3.js multiChart x-axis labels is aligned to multiple lines, but not multiple bars

The problem here is that nv.models.multiChart uses a linear scale for its x-axis, and then when it draws the bars it calls nv.models.multiBar, which uses an ordinal scale with .rangeBands().

You can follow this mess through the source code:

First lets look at multiChart.js

HERE is where it sets the x-scale to be a linear scale.

HERE it calls the nv.models.multiBar model to create the bars.

If we jump over to have a look at multiBar.js

HERE it creates an ordinal scale, and HERE it sets the range of the scale using .rangeBands()

The result is that the ordinal scale used for placing the bars, and the linear scale used for the chart's axis do not align. Here's what the two scales look like on their own if plotted on an axis:

d3 ordinal vs linear scale

The solution would be to force the chart to render the line graphs and the x-axis in terms of the ordinal scale used by the bars. This would work in your case because the bars and the lines all use the same data for the x-axis. This is very simple to do if you are making your own chart and not relying on nvd3, as I showed in my answer to your previous question HERE. This is extraordinarily complicated to do if you're trying to work within nvd3, and many others have tried and failed to switch out the default scales used by nvd3 charts. Have a look at this issue on the nvd3 github page that has been open since January, 2013 for example.

I've tried a number of approaches myself to reuse the bars' ordinal scale, but with little success. If you want to poke around and try to brute-force it yourself, I can tell you that from my experiments I came closest when using chart.bars1.xScale().copy() to make a copy of the bars' scale, and set its domain and rangeBands. Unfortunately, since the chart's width is computed at render time, and I can't seem to create a hook into the chart.update function, it is impossible to set the rangeBands' extent to the correct values.

In short, if you can't live with the labels being offset, you're probably going to need to code up your own chart without nvd3, or else find a different type of layout for your visualization.


After playing around with the NVD3 v1.7.1 source code with the immensely helpful guidance offered by jshanley's answer, I think I've managed to come up with an answer (perhaps more of a kludge than a good solution).

  1. What I did was to have the x-axis labels align with the bars, and have the line data points align with the bars.

    1.1. To align the x-axis label, I shifted the x-axis to the right so that the first label appears underneath the middle of the first bar. I then shifted the last label to the left, so that it appears underneath the middle of the last bar. See code here. The amount to shift by is computed at drawing time using .rangeBand() and saved in a rbcOffset variable (I had to modify multiBar.js for this to work).

    1.2. To align the line data points with the bars, a similar shift is also required. Luckily, this part is easy because scatter.js (which is used by line chart) comes with a padData boolean variable that does what we want already. So basically, I just set padData to true and the lines shift to align with the bars, see here.

  2. In order to properly integrate with NVD3 and make everything look good, some additional changes are required. I've forked NVD3 on GitHub so you can see the complete solution there. Of course, contributions are welcome.


I use last solution and it runs. So, you can specify

lines1.padData(true)

in order to align lines too.

Tags:

D3.Js

Nvd3.Js