Building a Donut Chart Widget with D3.js and Svidget.js
By: Joe Agster
Tags: Data Visualization, JavaScript, D3, Svidget

D3.js is a very well known and respected JavaScript library that let’s you create complex data visualizations, such as charts and graphs. It has become synonymous with data visualization for the web. Svidget.js is a JavaScript framework that lets you create data visualization widgets in pure SVG. On the surface it sounds like these two would be in competition with each other, but actually it’s quite the opposite. By using D3 to output some amazing data visualizations, and Svidget.js to simplify wrapping for deployment and reuse, we can create really awesome visualization components that can be embedded anywhere with ease. To put it simply, Svidget is the hamburger bun and D3 is the patty.

To get started, let’s first think about a donut chart from a widget standpoint. What would its properties or params be? What actions, or methods, could we invoke? What events would the widget fire? Svidget widgets are made up of 3 artifacts — params, actions, and events. Defining these artifacts is the first step to widgetization. (A complete introduction to building a widget, including these concepts, can be found at svidget.com.)

The hardest of the 3 is figuring out params. To get some inspiration, we could look at existing implementations such as Chartist.js or Google Charts. Keep in mind that we usually don’t want to offer every type of configuration under the sun, but rather, a nice abstraction to get the consumer up and running quickly. Upon initial discovery I have come up with:

data — the data for the widget, expressed as an array

maxSlices — the max number of slices before grouping the rest of the data in an “Other” slice

startAngle — the angle in degrees that the chart should start at, usually 0

sort — how to sort the chart: ‘none’, ‘asc’, or ‘desc’

colors — an array of colors to color the slices

width — the width of the donut (end - start radius)

Note: the final widget may also contain style params that we will cover later.

For widget actions, there is little the chart needs to expose. Charts are relatively passive. But for the purposes of demonstration, let’s add a action called animate that will force the chart to animate itself when invoked. (The same animation is played when the chart is first loaded.)

For widget events, there are two that would be useful — a selection event that tells us when a slice or segment was selected via mouse over (or tap on a mobile device), and an activation event that tells us when the user clicked the slice (on a mobile device they would tap to select, then tap again to activate).

Now that we have all of our widget artifacts figured out, let’s put it all together. In Svidget, you can declaratively define all of this in XML along side the SVG. For more information about this, download the svidget.xsd schema from the GitHub repo. Put together, the widget SVG would initially look like this:



xmlns:svidget="

width="400" height="400" viewBox="0 0 400 400" svidget:version="0.3.4"> http://www.w3.org/2000/svg xmlns:svidget=" http://www.svidget.org/svidget width="400" height="400" viewBox="0 0 400 400" svidget:version="0.3.4"> <svidget:params>

<svidget:param name="data" />

<svidget:param name="maxSlices" />

<svidget:param name="startAngle" />

<svidget:param name="sort" />

<svidget:param name="colors" />

<svidget:param name="width" />

</svidget:params> <svidget:actions>

<svidget:action name="animate" binding="animate">

<svidget:actionparam name="duration" type="number" />

</svidget:action>

</svidget:actions> <svidget:events>

<svidget:event name="sliceSelect" />

<svidget:event name="sliceActivate" />

</svidget:events>

Next let’s layout our SVG. It is just bare bones, as D3 will take care of the rest.

<g id="chart" transform="translate(200 200)"></g>

Finally, let’s code the meat — using D3 to populate the donut chart.

// set up

var outerRadius = CHART_RADIUS;

var innerRadius = outerRadius — _width;

var widget = svidget.$; // get an instance to the current widget // params

// note: data in form of [['Item 1', 3], ['Item 2', 6]]

var data = widget.param('data').value();

var startAngle = widget.param('startAngle').value();

var sort = widget.param('sort').value();

var colors = widget.param('colors').value(); // colors

var colorRange = d3.scale.ordinal().range(colors); // pie layout — for layout out arcs as a pie/donut chart

var pie = d3.layout.pie()

.startAngle(toRadians(startAngle))

.endAngle(toRadians(startAngle + 360))

.sort(sortDataFunc(sort))

.value(function(d) {

return numIt(d[1]); /* second element in data inner array */

}); // the main arc object

var arc = d3.svg.arc()

.outerRadius(outerRadius)

.innerRadius(innerRadius); // create chart and slices

var chart = d3.select('#chart');

var slices = chart.selectAll('.slice')

.data(pie(data))

.enter()

.append('g');

var paths = slices.append('path'); // draw slices

paths.attr('stroke', '#fff')

.attr('stroke-width', 2)

.attr('d', arc)

.style('fill', function(d, i) {

return colorRange(i);

}); // draw labels

slices.append('text')

.attr('dy', '.35em')

.style('text-anchor', 'middle')

.attr('transform', function(d) {

return 'translate(' + arc.centroid(d) + ')';

})

.text(function(d) {

d.data[0]; // d.data == ['Label', 0]

});

Now that our donutChart.svg widget file is complete and ready to go, embedding on a website becomes easy. Just use the HTML <object> tag, passing along your desired param values:

<object id="donutChart" role="svidget" data="path/to/donutChart.svg" type="image/svg+xml" width="400" height="400">

<param name="data" value="[['banana', 7], ['apple', 6], ['cherry', 2], ['orange', 10], ['grape', 3.5]]" />

<param name="maxSlices" value="-1" />

<param name="startAngle" value="0" />

<param name="sort" value="none" />

<param name="colors" value="['#ffb244','#ed723c','#da3333','#b01111','#7a0000']" />

<param name="width" value="60" />

</object>

And the final result:

The resulting donut chart

The full working example with code can be found here.

Although we created a donut chart widget using D3, the usage potential reaches much further than charts. The truth is, libraries like Chartist do charts really well. We could wrap a Chartist chart as a Svidget widget, similarly to how we used D3 to generate a chart, but the advantages may not be readily apparent to someone who is a skilled JavaScript developer. But for everyone else, abstracting the implementation details of using a complex library D3 is highly beneficial.

I’ve been asked, why did I create the Svidget.js library? Simply put, it solves the producer-consumer abstraction problem with interactive SVG. In my industry we use SVG widgets in real-time visual dashboards for data center operations. Consumers can build any of these dashboards by selecting these widgets and wiring them up to data. Conversely, a separate team of producers can build common, reusable widgets. These widgets go far beyond simple charts — they can represent cooling fans, generators, electrical circuits, even doors.

Likewise, anyone can use Svidget to allow non-technical people to embed complex SVG illustrations into their websites.

Joe Agster is a lead software engineer at BASELAYER. Coding is on of my many endeavors. I also wrote a science fiction novel, The Final Six Days, available now on Kindle.