I recently wrapped up a project where we decided to use the really nice D3.js library to create some graphs the client wanted. Instead of doing everything from scratch, we also chose to add on NVD3.js to supply some prepackaged graphs. That way, we could just add the data and make some minor modifications to get what we want. I couldn’t find a whole lot of articles explaining how to use NVD3, so I thought I’d put something together for those of you getting started.
If you haven’t used D3 before, there are plenty of tutorials and books that can get you started. You can add, select, and modify the html elements in your document similar to the same way you would do it with jQuery. Then you can associate data with those elements even start drawing SVG elements on the page. It’s not just for graphing, but that tends to be a great use for it.
NVD3 is built using D3, so if you know already know anything about using D3, you’ll understand what’s going on. If you don’t already know D3, then NVD3 is a great way to get started. You can get your graph on the page without needing to put it together piece by piece, and learn important parts of D3 as you progress. For our purposes here, we’re going to be doing a very basic line chart so you can see some of the parts working together. You can follow along by cloning the repo and using the tag that corresponds to each section. This is all based on NVD3 v1.1.15-beta and D3 v3.4.3.
There are 4 parts to this:
- Preparing the data
- Setting up the svg element
- Setting up the chart
- Adding options
So let’s get started.
Preparing the data
git tag: data
For a line chart, the data structure can be pretty simple. You’ll need to pass it an array of objects, which I’ll call the “data array”—one object for each line in the chart. Each of those objects should have a key
property, which is a string that will represent that line in the legend and tooltips. The data object also must have a values
property, which will be an array of objects containing the values for each data point—I’ll call this the “values array”. To make it easy, you can give each object in the values array x
and y
properties that will be used to plot it on the graph. Besides key
and values
, you can optionally add a color
property to the data array objects as well. The complete data array should look something like this:
[ { key: "Label for line 1", values: [{x:1 , y:5}, {x:2 , y:10 }, {x:3 , y:15 }], color: "#fff" }, { key: "Label for line 2", values: [{x:1 , y:3 }, {x:2 , y:6 }, {x:3 , y:9 }] }, { key: "Label for line 3", values: [{x:1 , y:4 }, {x:2 , y:8 }, {x:3 , y:12 }] } ]
If you want, you can get a little fancier with the objects in the values array; you’re not required to give them all x
and y
properties. You can put whatever properties you want inside of that object, as long as you’ll be able to pull the x and y coordinates out of it later (I’ll explain how when the time is right).
For our purposes here, I just made a simple function that will spit out the data we need for our three lines, 20 points for each line.
function generateData ( ) { var numberOfPoints = 20, graphData = [], series, i, j; for (i = 0; i < 3; i++) { series = []; for (j = 0; j < numberOfPoints; j++) { series.push({x: j, y: ((i+1) * j)}); } graphData.push({ values: series}) } return graphData; }
Setting up the svg element
git tag: svg
One of the best things about D3 is that you’re using native HTML5 elements. The element most relevant to us here is the <svg>
root element itself, and all of the other SVG elements that you can use inside of it. Here’s some background on all of the different SVG elements. Using D3 you can create and add them to the <svg>
, and then add all of the properties and attributes you need.
Now we can turn our attention to the setupSvg()
function:
var svgAttributes = { width: 500, height: 300, padding: 25, margin : { top: 5, right: 10, bottom: 5, left: 10 } };
I’m starting out with a variable that contains some basic attributes for the <svg>
, just to have them all in one place. Next, we save the <svg>
so that we can call functions on it later.
svg = d3.select('#home svg');
With D3 you can call either .select()
, to save a reference to the first element that matches your css selector, or .selectAll()
, to save references to all of the elements that match. Once you have that reference, you can do things like add/remove attributes, classes, styles, properties, and text, as well as append, insert, or remove nodes. For example, we started off here with the <svg>
already on the page, but we could just as easily only have had <div id="home"></div>
on the page and done the following:
svg = d3.select('#home').append('svg');
This still returns the reference to the <svg>
, and would be useful in a situation where you’re adding SVG to the page after the user has performed some action. Here it would be somewhat unnecessary. Now we just pass a hash to the .style()
function to set any styles we want that aren’t already in the CSS file.
svg.style({ 'width': svgAttributes.width + svgAttributes.margin.left + svgAttributes.margin.right, 'height': svgAttributes.height + svgAttributes.margin.top + svgAttributes.margin.bottom, 'padding': svgAttributes.padding, 'margin': '0 auto' });
Basic stuff there; if you can use jQuery, you can use these parts of D3. The rest of the lines set up other parts of the <svg>
that we’ll need.
svg.datum( generateData() ); svg.transition().duration(500);
Calling .datum()
and passing in a value (in our case, the array created by generateData()
) on a D3 selection sets the data for that selection to use. We could also pass the generateData()
function directly into .datum()
, and it would be executed at the appropriate time to provide the data. If the selection contains more than one element (.selectAll()
was used), then the same value or function will be used to set the data for each of them. The transition and duration functions operate on the selected element, in this case <svg>
. You can set it for as short or as long as you like, the default is 250ms.
Note that all of these calls on svg
return the element, so they can be chained together. Now on to the actual NVD3 portion, setting up the chart.
Setting up the chart
git tag: chart
We’re at the point now where we can setup the actual chart that will be shown on the page. This essentially only requires calling the chart constructor function and setting your desired options.
chart = nv.models.lineChart() chart.options({ x: getX, y: getY, noData: "Not enough data to graph", transitionDuration: 500, showLegend: true, showXAxis: true, showYAxis: true, rightAlignYAxis: false, });
When you call nv.models.lineChart()
, it returns the chart
object you’ll need to build the graph. After that, you can pass in all the functions to set certain options of the chart by calling chart.options()
.
getX: function (point, index) { return point.x; }, getY: function (point, index) { return point.y; }
When you’ve formatted the values objects in the values
array with x
and y
properties the way we have, the x
and y
properties in the hash above are superfluous. The default for NVD3 is to just use any x
and y
properties of the object to determine the points on your chart. You only need the getX()
and getY()
functions when there’s something else that needs to be done to obtain the x or y value. If that’s the case, then you’d pass in your functions the way we did in the .options()
call. The point
argument that gets passed in is an object with a series
property and whatever properties were in the objects in the values array. In this case, since the values array objects had x
and y
properties, there are x
and y
properties on point
with the same values. The series
property is a 0-based number denoting which line that point
belongs to. In this case, since there are three lines, it would equal either 0, 1, or 2. The index
argument is the index of that particular object in that line’s values
array. Each of our lines in this example consists of 20 points, so index
will be a number between 0 and 19. Whatever value you return from these functions is what will be used as the x or y coordinate for that point.
You should be pretty careful with how elaborate these functions are. Just out of curiosity I tested it, and to generate this chart of 3 lines with 20 points each, getX()
was called 363 times and getY()
was called 603 times. So you may want to keep things as simple as possible.
Next we have these lines:
chart.xAxis .tickFormat(xAxisFormatter) .axisLabel("Days since it happened"); chart.yAxis .tickFormat(yAxisFormatter) .axisLabel("Calls per day");
NVD3 has models for each of the separate structures on the chart (xAxis
, yAxis
, legend
, lines
, etc.), which it then assembles inside of each type of chart. It then exposes those structures so that you can access and modify them appropriately, both with D3 and NVD3 functions. The .axisLabel()
NVD3 function should be self-explanatory, it just sets the label for the axis it’s called on. .tickFormat()
is a D3 function to format the tick values that appear on an axis. .tickFormat()
takes a function as an argument, which should return the value to display.
xAxisFormatter: function (xValue) { return d3.format(',d')(xValue) }, yAxisFormatter: function (yValue) { return yValue.toString() }
For .xAxisFormatter()
, we’re using one of D3’s formatting functions, which returns the number in xValue as a string with commas as the thousands separator. The .yAxisFormatter()
function is the same thing that would happen if we provided no function at all. NVD3 would simply convert that number to its string representation.
Lastly, we need to actually trigger rendering of the chart. We do that by just calling it (it’s a function, after all) and passing in the <svg>
that it’s supposed to act on.
renderChart: function ( ) { chart(svg); },
At this point, you’re done. The chart should render onscreen.
Additional options
git tag: optional
Now even though you’ve reached the point where you can see the graph, you’re not necessarily done with it. There are some additional things that you can do with an NVD3 chart to improve the experience.
Callbacks
NVD3 offers some functions you can pass a callback function to as an argument, which will be executed when the specified action is completed. One is nv.addGraph()
:
nv.addGraph(graph.initialize, myCallback)
nv.addGraph()
takes two functions as arguments. The first is the function that creates the graph, and the second is your callback function. It adds both functions to its own queue and executes them. Another function that takes a callback is nv.utils.windowResize()
:
nv.utils.windowResize( resizeCallback );
With this one your callback gets triggered every time the window is resized. Typically you’d pass in chart.update so the chart can shrink along with the window.
Events
The standard NVD3 graphs come with a lot of different events that you can trigger. If you hover over a point, then a tooltip will appear. If you click on one of the labels in the legend, you can toggle the visibility of that line. That’s not the end of it though. You can tap into those events and others in order to add your own customizations through the various dispatchers. Each chart, as well as some of its constituent models has an event dispatcher added using d3.dispatcher()
that you can add your own callbacks to.
chart.dispatch.on('stateChange', stateChangeCallback); chart.legend.dispatch.on('legendMouseover', legendMouseoverCallback);
You’ll need to look at each of the NVD3 model declaration to see which events they send out, and you can even create your own if necessary.
Configuration functions
There are a lot functions that you can call on the NVD3 models and the D3 objects underlying them. The best way to figure out what is available to you to modify is to look at the actual models in the NVD3 source code. The first thing to look for is the call to d3.rebind()
, you can see this example from nv.models.lineChart()
.
d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'useVoronoi','id', 'interpolate');
What this d3.rebind does is take the specified methods from the source object (lines
) and place them on the target object (chart
) so that you can call them on the target object, but the context of the function that is executed will still be the source object. So when you call any of those functions listed above on the chart returned by nv.models.lineChart()
, it is the same as calling that function directly on the chart.lines object. You’ll see this pattern repeated throughout the models in NVD3.
The other way to see what can be called on each of the NVD3 models is to look at the public methods exposed in that model’s source code. The defaults for the model are declared immediately at the top of the model’s constructor, and all the way at the end there is a list of the methods to change those defaults. Most of the methods look similar to this:
chart.showLegend = function(_) { if (!arguments.length) return showLegend; showLegend = _; return chart; };
The default for showLegend is true, so if you call chart.showLegend()
then it will just return that value (some of the defaults are functions that the chart uses, so if you call them, it will just return the function). However, if you pass in a new value then that value will be set for the chart and used whenever it gets rendered. If the chart is already rendered, you’d have to call chart.update()
so that it re-renders itself with the change.
Conclusion
Those are the main aspects of creating a graph using NVD3. It’s a really good solution for the times when you need to add a chart to a page quickly, and don’t want to be bothered creating the whole thing from scratch. It’s also a good starting point for getting more in-depth with D3 and getting access to the powerful abilities of that library. You can use it to create some even more amazing visualizations of your data. This is only the beginning.
DevMynd – custom software development services with practice areas in digital strategy, human-centered design, UI/UX, and mobile and web application development.