I am new to NVD3 (as of yesterday) and somewhat experienced with D3 but by no means a power user. I have been dealing with the same question have found a happy solution for myself that might help.
First of all, since you mention using a similar focus/context interface for a "scatter" graph, you should know that the nvd3.models.line
object which the LineWithFocus chart is built on, is in itself actually built on nvd3.models.scatter
. I found this out because in order to get a line chart to show x-axis labels for a time scale you actually have to use: chart.lines.scatter.xScale(d3.time.scale());
Now, as for displaying the line chart with filled area, you can as @AmeliaBR commented, use the built in chart.isArea(true)
(actually I found it better to set lines.isArea(true); lines2.isArea(true);
. This generates lines fully-filled underneath, but as @AmeliaBR mentions, the interactions cause some problems, and for me the lines filled over one another. I wanted something that looked more like a stream graph. It ended up being very simple. My lines were well ordered (tracking minimums, averages, and maximums of the same data at the same points), which might not be the same case for you so maybe you'll need to add some additional work in setting your data up correctly to fill in how you want. When reading my data in, I set y and y0 values for each line, like so:
var line_data = [
{key: "minimum", values: []},
{key: "average", values: []},
{key: "maximum", values: []}];
data.forEach(function(d) {
var date = new Date(+d.epoch*1000); //new Date(+d.epoch*1000);
line_data[0]["values"].push({x: date, y: +d.minimum, y0: +d.average });
line_data[1]["values"].push({x: date, y: +d.average, y0: +d.average });
line_data[2]["values"].push({x: date, y: +d.maximum, y0: +d.average });
});
This would set it up so that the data was there to draw two shapes to be filled in between the three lines, one between minimum and average (colored like minimum) and one between average and maximum (colored like maximum). In order to get the code to use that data, I then overloaded the nv.models.line object definition by copying over the full definition from the nv.d3.js source code to my own file and made the following changes:
At the top, I added a getY0 function modeled after the getX and getY functions:
getX = function(d) { return d.x }
getY = function(d) { return d.y }
getY0 = function(d) { return d.y0 }
A little lower, you'll find the definition for areaPaths = groups.selectAll('path.nv-area')
and just below that another block that starts areaPaths.transition()
. The same attr('d',...)
call is made in both places. In both places you need to make the same change.
Instead of: .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
Use: .y1(function(d,i) { return nv.utils.NaNtoZero(y(getY0(d,i))) })
You'll notice that the line now looks almost exactly the same as the one above it:
.y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
Change the y0
there to y
:
'.y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
Explanation:
Your .y0
definition for the area is the d.y
value from the line data
Your .y1
definition for the area is the d.y0
value from the line data
The y0()
call that wraps getY
is actually a reference to the scale being used. If you look up a little from areaPaths
in the nv.models.line
definition , you'll see that there is a comment where these are defined: var x0, y0 //used to store previous scales
If I originally left in the y0() wrappers, I found that the graph originally appeared how I wanted but then didn't update correctly. When I changed all of the y0
wrappers in these functions to y
and that kept them updated with the current scale rather than the previous one. Have not had any problems since.