The following is a guest post by Chris Scott. Chris has written for us before – always on the cutting edge of things. This time Chris shows us something new in the form of a new charting technique his company offers. But it’s based on something old: the fundamentals of the web itself.
This month my company, Factmint, released its suite of Charts. They’re really cool for a number of reasons (not least their design) but one is of particular interest to web developers: they all transform simple HTML tables into complex, interactive data visualizations. This approach is a perfect example of Progressive Enhancement – for browser compatibility, accessibility and SEO you have a simple table, for modern browsers a beautiful graphic.
I wanted to explore the techniques we used in a bit more detail. So here goes…
A recap on Progressive Enhancement
There are a few core concepts of PE. Here’s the list on Wikipedia:
- basic content should be accessible to all web browsers
- basic functionality should be accessible to all web browsers
- sparse, semantic markup contains all content
- enhanced layout is provided by externally linked CSS
- enhanced behavior is provided by unobtrusive, externally linked JavaScript
- end-user web browser preferences are respected
Basically, implement a simple, cross-browser, pure HTML solution. Once that’s done you have a safe minimum-functionality page. Now, build upon that with CSS and JavaScript.
This article is going to look at using these concepts to produce data visualizations.
Data visualizations are backed by data
It’s painfully obvious but worth stating: data visualizations are based upon some underlying data. That data doesn’t need to be lost when building a graphic (as it would be in a raster image, for example). Neither does the data format have to be JSON, as most charting libraries use.
Going back to the first three of the “core concepts” of PE, the basic functionality should be some kind of markup encoding that data. An HTML table or list, for example.
A working example
To illustrate the idea, we are going to Progressively Enhance a timeline towards an SVG visualization. The data might be something like this:
- 1969: UNICS
- 1971: UNIX Time-Sharing System
- 1978: BSD
- 1980: XENIX OS
- 1981: UNIX System III
- 1982: SunOS
- 1983: UNIX System V
- 1986: GNU (Trix)
- 1986: HP-UX
- 1987: Minix
- 1989: NeXTSTEP
- 1989: SCO UNIX
- 1990: Solaris
- 1991: Linux
- 1993: FreeBSD
- 1995: OpenBSD
- 1999: Mac OS X
Basic content
There are a number of ways that this data could be encoded. I’m going to go with a definition list – I think that is semantically accurate and will display well without much styling. Let’s start with the base (no-enhancement) case:
<dl class="timeline">
<dt>1969</dt><dd>UNICS</dd>
<dt>1971</dt><dd>UNIX Time-Sharing System</dd>
<dt>1978</dt><dd>BSD</dd>
<dt>1980</dt><dd>XENIX OS</dd>
<dt>1981</dt><dd>UNIX System III</dd>
<dt>1982</dt><dd>SunOS</dd>
<dt>1983</dt><dd>UNIX System V</dd>
<dt>1986</dt><dd>GNU (Trix)</dd>
<dd>HP-UX</dd>
<dt>1987</dt><dd>Minix</dd>
<dt>1989</dt><dd>NeXTSTEP</dd>
<dd>SCO UNIX</dd>
<dt>1990</dt><dd>Solaris</dd>
<dt>1991</dt><dd>Linux</dd>
<dt>1993</dt><dd>FreeBSD</dd>
<dt>1995</dt><dd>OpenBSD</dd>
<dt>1999</dt><dd>Mac OS X</dd>
</dl>
That looks something like this:

Okay, so it’s not pretty, but it will be clear and accessible on all browsers and it should be helpful for search engines, too.
Enhancing the layout
Now, let’s use a stylesheet to improve the layout. This could be taken much further, but, for the purpose of this article, let’s just make some simple improvements:
html {
font-family: sans-serif;
}
dl {
padding-left: 2em;
margin-left: 1em;
border-left: 1px solid;
dt:before {
content: '-';
position: absolute;
margin-left: -2.05em;
}
}
Now it renders as:

That definitely looks more like a timeline but there are some clear problems with it. Most importantly, the timeline points should be distributed based upon their relative date (so 1983 and 1986 aren’t next to each other). Also, I’d like the timeline to run horizontally to avoid the need for scrolling (in a production case I’d check for the best orientation).
Enhancing behaviour
Now for the fun bit. We are going to use externally linked, unobtrusive JavaScript to render an SVG timeline and replace the definition list with that. The final visualization will look something like this:

Unobtrusive JavaScript
We are going to be producing an SVG graphic with this script, so the most important thing we can do – to keep the script unobtrusive – is to check that the browser supports SVG:
function supportsSvg() {
return document.implementation &&
(
document.implementation.hasFeature('http://www.w3.org/TR/SVG11/feature#Shape', '1.0') ||
document.implementation.hasFeature('SVG', '1.0')
);
}
That should give pretty accurate feature detection. Alternatively you could go for Modernizr.
Now, we will check for SVG support before we do anything: if the browser supports SVG we’ll draw a pretty visualization and hide the definition list, otherwise we will leave the list in place.
if (supportsSvg()) {
var timeline = document.querySelector('.timeline');
timeline.style.display = 'none'; // We don't need to show the list
// draw the graphic...
}
Extract the data
The key principle to this approach of PE data visualizations is that the data format is the semantic markup, so let’s parse our data…
function getDataFromDefinitionList(definitionList) {
var children = definitionList.children;
var yearIndex = {};
var data = [];
var currentYear = null;
for (var childIndex = 0; childIndex < children.length; childIndex++) {
var child = children[childIndex];
if (child.nodeName == 'DT') {
currentYear = child.innerText;
} else if (child.nodeName == 'DD' && currentYear !== null) {
if (! yearIndex[currentYear]) {
yearIndex[currentYear] = data.length;
data.push({
year: currentYear,
values: []
});
}
data[yearIndex[currentYear]].values.push(child.innerText);
}
}
return data;
}
There’s quite a lot going on there but the essence is simple: iterate over the children, use the DTs as the year and the DDs as the releases. The fact that definition lists allow more than one DD for each DT makes it slightly more complex, hence the lookup to add additional releases from the same year to the same entry in the data array.
The output from that will be something like:
[
{
"year": "1969",
"values": ["UNICS"]
}
...
]
It’s really useful to have an array, like this, as opposed to an object map. It will be much easier to iterate over the entries later.
Prepare the canvas
To draw this visualization we are going to use SnapSVG. It’s an SVG drawing library by Dmitry Baranovskiy, who also wrote Raphael.js. First, we will need to create an SVG element:
var SVG_NS = 'http://www.w3.org/2000/svg';
function createSvgElement() {
var element = document.createElementNS(SVG_NS, 'svg');
element.setAttribute('width', '100%');
element.setAttribute('height', '250px');
element.classList.add('timeline-visualization');
return element;
}
Snap can then wrap the element. Something like this:
var element = createSvgElement();
var paper = Snap(element);
Drawing the timeline
Now for the fun part!
We’re going to write a method that iterates over our data object and draws onto the SVG element. Those two things (the data and the SVG element) will be the arguments:
function drawTimeline(svgElement, data) {
var paper = Snap(svgElement);
data.forEach(function(datum) {
// draw the entry
});
}
The simplest timeline would just draw a dot for each:
function drawTimeline(svgElement, data) {
var paper = Snap(svgElement);
var distanceBetweenPoints = 50;
var x = 0;
data.forEach(function(datum) {
paper.circle(x, 200, 4);
var x += distanceBetweenPoints;
});
}
That should give 17 evenly distributed dots. But our main objective was to space the dots properly, so let’s do something a little more interesting:
function drawTimeline(svgElement, data) {
var paper = Snap(svgElement);
var canvasSize = paper.node.offsetWidth;
var start = data[0].year;
var end = data[data.length - 1].year;
// add some padding
start--;
end++;
var range = end - start;
paper.line(0, 200, canvasSize, 200).attr({
'stroke': 'black',
'stroke-width': 2
});
data.forEach(function(datum) {
var x = canvasSize * (datum.year - start) / range;
paper.circle(x, 200, 6);
});
}
Cool: now our dots are distributed and there’s a line underneath them. No information yet, though, so let’s add some labels.
function drawTimeline(svgElement, data) {
var paper = Snap(svgElement);
var canvasSize = paper.node.offsetWidth;
var start = data[0].year;
var end = data[data.length - 1].year;
// add some padding
start--;
end++;
var range = end - start;
paper.line(0, 200, canvasSize, 200).attr({
'stroke': 'black',
'stroke-width': 2
});
data.forEach(function(datum) {
var x = canvasSize * (datum.year - start) / range;
paper.circle(x, 200, 6);
paper.text(x, 230, datum.year).attr({
'text-anchor': 'middle'
});
var averageIndex = (datum.values.length - 1) / 2;
var xOffsetSize = 24;
datum.values.forEach(function(value, index) {
var offset = (index - averageIndex) * xOffsetSize;
paper.text(x + offset, 180, value)
.attr({
'text-anchor': 'start'
})
.transform('r -45 ' + (x + offset) + ' 180');
});
});
}
Dealing with years that have more than one entry has a little more complexity, but nothing we can’t handle: the datum.values.forEach
loop was used to spread duplicates out horizontally, centered around the dot. A rotation has also been applied to stop the labels from overlapping (even though that may be considered bad practice as it adds Cognitive Load – a better solution would be to show key releases always and others on hover but that’s not the point of the article).
Finally, let’s add a little style for the SVG elements:
svg.timeline-visualization {
circle {
fill: white;
stroke: black;
stroke-width: 2;
}
}
Bringing it all together
Now we just have to stitch our components together in the if-statement:
if (supportsSvg()) {
var timeline = document.querySelector('.timeline');
timeline.style.display = 'none';
var data = getDataFromDefinitionList(timeline);
var svgElement = createSvgElement();
timeline.parentNode.insertBefore(svgElement, timeline);
drawTimeline(svgElement, data);
}
Here’s the complete code in a Pen:
See the Pen gbYqRW by chrismichaelscott (@chrismichaelscott) on CodePen.
Summary
There are lots of benefits with this approach to data visualizations; the markup is semantic, it’s SEO friendly, it’s accessible to screen-readers and it is progressively enhanced from a simple element supported on most browsers.
There are some things to consider though. If you only have one set of static data, it’s probably not worth the effort – just build an SVG by hand. If you have loads and loads of data this might not be the right approach either, as traversing the DOM tree may not be efficient enough.
If you are trying to do standard charts, like pies, doughnuts, bubble, lines, etc, it’s definitely worth checking out Factmint Charts. They are really beautiful and we’ve put a lot of thought into their design.
Hey there, the CodePen isn´t working!
Oh dear, someone didn’t do cross-browser testing. Here’s the list of changes required to get the example working in Firefox and IE:
Replace
.innerText
(supported in Webkit/Blink only) with.textContent
(standard DOM).Replace
.offsetWidth
(only standard on HTML elements, not SVG) on the SVG node with a call to getComputedExplicitly set the SVG to display mode
block
orinline-block
to avoid errors with getComputedStyle on some versions of Firefox.Working version:
Cool visualization.
I remade it in pure CSS here:
http://dabblet.com/gist/d568ca6c5c3f962b90b8
Not 100% accurate just yet, but it can get there.
I really like that, thanks for sharing!
Great and informative like always.
However, the CodePen isn’t showing the required result.
That’s pretty awesome, thanks for sharing!
Great example. Progressive enhancement and other best practices are often overlooked in the data visualization examples and tutorials.
However, the example as you currently have it is not as good for screen readers as you might think. Modern screen readers read out the displayed content of the web browser, after any modifications from JavaScript and CSS. When you set
display:none
on the definition list, it disappears for screen reader users as well. They’ll still have the text elements in the SVG, but not the semantic structure of the timeline.A few little changes can fix that using ARIA:
Give the definition list an
id
attribute (e.g.,id="timeline-1"
);Give the SVG the
role="img"
ARIA attribute so that screen readers will treat it as a single element, ignoring the text content and other structure of the graphic;Give the SVG an
aria-describedby
attribute pointing to the id of the definition list (without any hash marks, just `aria-describedby=”timeline-1″), so that the screen reader uses the structured HTML content to describe the graphic;Give the SVG an
aria-label
attribute with a short name, like “Timeline of Unix releases” (you should also be able to use the SVG title element for the same purpose, but I suspectaria-label
would have better support currently).Thanks for the tips and the fixes above. Appreciate it. We don’t use innerText or offsetWidth in our products, so slap on the wrist for me ;)
Yes, I was pretty sure that the browser incompatibilities were likely restricted to the demo quickly whipped up for a blog, and that the production-ready code would be well tested!
The charting library as a whole looks great — and I see it already uses
aria-describedby
!I’m glad I read the comments before making the same critic but I’d suggest to go a simpler route. I see ARIA more like a “retro-fitting” tool; in other words, why not taking advantage of the fact that this solution relies on proper/semantic markup to begin with? So rather than implementing ARIA roles or else I’d suggest to offer a button (visually hidden may be) that would let users to unstyle the DL – to reveal it to keyboard users.
Note that I’m not sure Definition Lists are very well supported across screen-readers, but that’s another story.
@Thierry Koblentz
That’s a good point. Even fully-sighted, mouse-using readers sometimes want to see the source data table (or data list). Since it’s already in the markup, it doesn’t take much to reveal it on a button click.
Nice stuff.
If you choose to use a solo feature detect.. You should definitely always pull from what Modernizr uses.
https://github.com/Modernizr/Modernizr/blob/master/feature-detects/svg.js
Just grab that code and make it standalone. Piece of cake.
In this case, the detect was written by an SVG spec editor and has the benefit of being battle tested for years across all browsers. Many alternative SVG detects have falls positives. Search in the Github issues for Modernizr to see all the trials and tribulations.
Yeah! That’s the one I use/love for inline SVG. The one for SVG-as-
<img>
is deliciously simple too:https://github.com/Modernizr/Modernizr/blob/master/feature-detects/svg/asimg.js
As we covered here.
The images alone make for a great comparison to show clients an example of progressive enhancement, this is awesome, the post as a whole is perfect to send my colleagues, thanks.
I love this approach for data visualization.
I built this entire map powered by the HTML data table at the bottom. Permalinks work too so you can link someone to a specific map after changing an option in the select menu.
http://www.pewhispanic.org/interactives/unauthorized-immigrants-2012/
In fact all of our charts found on this page are built with data from an HTML data table
http://www.pewresearch.org/data/