3

NVD3 is a javascript library wrapper built over D3. It offers creating a brush effect with Crossfilter for Line chart.

Reference below : http://nvd3.org/ghpages/lineWithFocus.html

Am curious to know if it is possible to build a similar brushing/focus effect for Area chart or scatter as well ? Please help me out.

Shabeer Mothi
  • 137
  • 1
  • 13
  • Yes, but not without modifying/extending the NVD3 source code. You could use the line with focus implementation as a guide. – Lars Kotthoff Jan 17 '14 at 13:08
  • Do you have any Fiddle where i can check it out ? – Shabeer Mothi Jan 17 '14 at 13:39
  • I'm not aware of any implementation of that? – Lars Kotthoff Jan 17 '14 at 14:37
  • I discovered that you can turn an NVD3 line chart into an area chart just by setting `chart.isArea(true)`, but the results aren't acceptable on the lineWithFocus example -- the lines and areas don't move together smoothly. You might want to compare notes with @caiuspb, who is [working on implementing the same type of graph](http://stackoverflow.com/q/21190421/3128209). – AmeliaBR Jan 18 '14 at 23:31

1 Answers1

4

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:

  1. 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 }

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

cheepychappy
  • 468
  • 3
  • 12