Using Dynamic Data and Making Reusable d3.js Charts

In a previous post, I laid out how to use d3.csv to read data into your spreadsheet. I also pointed out that d3.csv is really only one step away from explicitly declaring all your data inside of your JS document, which is what most examples tend to do. To really make a step towards reusable charts, you need to make your data models on the fly based on how your data is structured. Fortunately, d3 does give us the means to make that work, it just takes a little thinking, a little doing, and a fair amount of coding. The benefit, however, is that in the long range it means you will not need to do as much planning for each visualization – deployment time should become condensed.

There is no real reason this approach could not work with other data sources, but I’ll stick with d3.csv for now to keep things focused.

#1 – Change Callbacks to Outside Functions

The biggest change in approach is in how we should structure the d3.csv code and use its own APIs. In its ‘pure’ out of the box form, the structure would work with a function that would return a data model and a callback that would work on the found data. This format works fine, but it ends up with all kinds of scaling issues (imagine trying to manually add 200 columns of data).

1

2

3

4

5

d3.csv("./data.csv",function(d){

// setup the data

},function(error,data){

// do stuff with the data, apply updates to the SVG

});

The principal change I have found is moving away from the callback format and just working inside of the primary
.csv function, calling secondary functions to work on your data and updating your d3 area from there.

#2 – Use d3.csv Data Object to Programmatically Structure Data

The d3 data object is structured in a way that a function can be built to read through its contents, rendering the requirement to explicitly declare everything moot. Let me explain.

According to the spec, d3.csv creates a JavaScript array. This is actually very good for us because when you look at another section of the spec, they basically just point you to MDN. And MDN has a nice section on JavaScript array forEach loop handling. This is a long way of saying, things are very normal here – actually, more normal than any prior example you might encounter. When I first attacked this problem, I was all geared up to take on a major d3.js beast, but then it turned out to just be a JS array forEach loop. This makes it really easy to create a simple function to read in the data for you so that you are no longer looking at writing out lines and lines of column declarations.

Note: I use the term accessor in an attempt to conform to d3.csv’s nomenclature – MDN would refer to this as the index.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

functionprocessData(data){

varmodeledData=[];

data.forEach(function(data,accessor){

varrow;

row={

id:accessor

};

for(varattr indata){

if(data.hasOwnProperty(attr)){

row[attr]=+data[attr];

}

}

modeledData.push(part);

});

returnmodeledData;

}

The essential part of this code is the line:

1

row[attr]=+data[attr];

Using the
+ symbol in front of a variable converts the data into a float or an int, instead of returning a string. d3.csv builds the data object as a collection of strings, which clearly is not helpful if you are wanting to plot a series of points on a graph. Using this paradigm, however, does require that your data be formed as real numbers (’30’, ’49’, etc); if it is a true string (’30ft’, ‘49%’, etc), it will return NaN. How you handle this constraint is a design decision. You can use something like parseInt instead, or you can have some method that cleans your data ahead of working it through d3. Or you can put the onus back on to your user, rejecting the input if it fails the constraint.

Either way, iterating through the data object by way of the .hasOwnProperty method allows you to get your data together, organized by the column headers.

#3 – Redraw The Graph From Outside Function

Because the pattern is a bit different from the typical d3.js examples, you will need to create a function that will redraw the SVG area. It need not be anything overly complicated or different from even the example from my previous post, just compartmentalized into its own method.

Resolving this requires a few more steps that tie into your
processData() function.

First, you will need to declare the values in the function as globals initialized as null,
varxUserVal;varxValue=function(d){returnvarxUserVal;} and so on.

Second, you will need to setup a selection area that
onLoad will be empty.

1

2

3

4

5

6

7

8

<div>

<select id="xSelectionArea">

</select>

</div>

<div>

<select id="ySelectionArea">

</select>

</div>

Third, you need to create a method that will populate these target areas with the values of the property names of your data object, essentially dynamically producing a list from which your users can select their axis data for themselves.

For instance, I added the following to my
processData() function. (Take note that I am adding in jQuery at this point)

Fourth, you need to a) update the global vars to match your selectors that you just created and b) create a listener for your selectors, so that when the user picks a new data set the SVG is updated.

1

2

3

4

$('#xSelectionArea').on('change',function(){

xUserVal=this.value;

redraw(processData);

});

And from there, your users can change the data targets, the chart will update, and with a bit more effort your data source can be made dynamic itself. At the very least, however, this configuration is enough to allow for dynamic loading of CSV files that are anonymous to the system.

I plan to be working up more d3.js explainers and tutorials in the coming months, but if you have anything you’d like to see or have any questions about what I’ve done so far, feel free to reach out in the comments below or on Twitter!