A List Apart

Menu

Accessible Data Visualization with Web Standards

by Published in CSS, Accessibility · 31 Comments

We’ve been talking about Web 2.0 for so long now it’s already passé to argue about what it means and what it doesn’t. But one thing’s for sure, there’s a lot of data out there on the web these days. And as web designers, we’re designing a lot of data-driven sites.

There are plenty of options out there for data visualization, too. Google’s recently-announced Charts API is a great example, but there are a number of tools and services for creating charts and graphs as images and for making interactive visualizations with Flash.

There are also great standards-based techniques, such as Eric Meyer’s CSS bar graphs which transform good clean semantic table markup into charts.

All of these options are extremely useful when your data is front and center, when all you’re after is a chart. But what if we want to include data visualization as an integral part of the site, not just an isolated figure or an interactive chart? When we’re designing interfaces for browsing data-driven sites, it’s valuable to be able to create navigation elements that are also visualization tools. We can keep the user informed as they explore, so they can make better decisions about what they’re looking at and what they’re clicking on.

We could build that type of navigation with Flash, or generate static images every time the data changes, but that can be a big tradeoff in terms of accessibility and maintainability.

Even with standards-based markup, there are tradeoffs. If we just want to display the data, we can get the most semantic richness and accessibility hooks with a table. But when what we’re really building is navigation, tables are an awkward and often clumsy tool for the job. What we need is something in between—markup that’s appropriate for navigation, but with some extra hooks for semantics and structure.

I’m going to cover three basic techniques for incorporating some simple data visualization into standards-based navigation patterns. All of them start with the building block of HTML navigation: an unordered list of links. We’re going to tweak the markup a bit and add in some data points and a few hooks for styling, but in each case, the basic pattern is the same familiar one.

Since we don’t have the pure data semantics of a table to rely on, we’ll use semantic class names in the tradition of microformats to preserve as much of the data’s structure in our markup as possible. And since we’re using HTML and CSS, we can use em-based measurements throughout to make sure the charts adapt as the user scales the text size.

Horizontal bar charts

This simple technique just adds bars to a list of items behind the text (check out the finished example for an idea of what we’re shooting for). It works for lists of any length. Longer lists benefit from being sorted by count since the relative values of the bars are easier to read when they are sequential. In this example, we’re going to display the count for each item in the list, but you can leave that out if the value is less important on its own and you’re just showing the relative values for comparison.

Let’s start with a basic unordered list of links. (Line wraps marked » —Ed.)

<ul class="chartlist">
  <li>
    <a href="http://www.example.com/fruits/apples/">Apples</a> 
  </li>
  <li>
    <a href="http://www.example.com/fruits/bananas/"> »
    Bananas</a> 
  </li>
  <li>
    <a href="http://www.example.com/fruits/cherries/"> »
    Cherries</a> 
  </li>
  <li>
    <a href="http://www.example.com/fruits/dates/">Dates</a> 
  </li>
</ul>

The first thing to do is add the values for each item in the list. I’ll wrap them in their own span outside the links so we can style them separately. (Line wraps marked » —Ed.)

<ul class="chartlist">
  <li>
    <a href="http://www.example.com/fruits/apples/">Apples</a> 
    <span class="count">420</span>
  </li>
  <li>
    <a href="http://www.example.com/fruits/bananas/"> »
    Bananas</a> 
    <span class="count">280</span>
  </li>
  <li>
    <a href="http://www.example.com/fruits/cherries/"> »
    Cherries</a> 
    <span class="count">200</span>
  </li>
  <li>
    <a href="http://www.example.com/fruits/dates/">Dates</a> 
    <span class="count">100</span>
  </li>
</ul>

In order to create the bars, we need to style the list items as block elements so they take up the full width, and set them to use relative positioning so we can position the bars relative to each item. Since the main purpose of this list is navigation, I’ll set display: block on the link elements so the click target fills up the full width of the list as well.

.chartlist li { 
  position: relative;
  display: block; 
}

I’ll also add in some styles to offset the counts to the right of the list for easy scanning. I’ll use absolute positioning instead of a float so the links and the chart bars can all overlap in the full width of the chart without running into each other.

.chartlist .count { 
  display: block; 
  position: absolute; 
  top: 0; 
  right: 0; 
  margin: 0 0.3em; 
  text-align: right; 
  color: #999; 
  font-weight: bold; 
  font-size: 0.875em; 
}

Since we used absolute positioning for the counts, I’ll add some padding to the links to make sure there is room for the numbers to fit next to the link text without overlapping. I’ll also need to add a couple of styles to the links to make sure they always appear above the bars we’re going to position below them. I’ll set their position to relative so they will accept a z-index value, and then set their z-index to something above zero so they’ll stay at the top of the chart.

.chartlist li a { 
  display: block; 
  padding: 0.4em 4.5em 0.4em 0.5em;
  position: relative; 
  z-index: 2; 
}

Before I style the bars, I need to add an element to the markup for each item to store the information that the bars will convey. In this case we want to show the relative value of each item in the list compared to the total count for the whole list. So the information we’ll be communicating with the bars will be a percentage calculated from those two values. I’ll add an element to the markup and give it an appropriate class so we can pick it up in the styles. I’ll use a class of index to represent the ratios represented by the chart bars. (Line wraps marked » —Ed.)

<ul class="chartlist">
  <li>
    <a href="http://www.example.com/fruits/apples/">Apples</a> 
    <span class="count">420</span>
    <span class="index">(42%)</span>
  </li>
  <li>
    <a href="http://www.example.com/fruits/bananas/"> »
    Bananas</a> 
    <span class="count">280</span>
    <span class="index">(28%)</span>
  </li>
  <li>
    <a href="http://www.example.com/fruits/cherries/"> »
    Cherries</a> 
    <span class="count">200</span>
    <span class="index">(20%)</span>
  </li>
  <li>
    <a href="http://www.example.com/fruits/dates/">Dates</a> 
    <span class="count">100</span>
    <span class="index">(10%)</span>
  </li>
</ul>

A note about the data

In this example chart, we’re showing each item as a percentage of the whole (like a pie chart), but you can use the same technique to show percentages relative to the highest value in the list—so there is always at least one 100% bar and everything else is a percentage of that maximum value. You can also calculate the percentages relative to a baseline value that you set at a fixed percentage (say, 50%), and all the other percentages will be higher or lower based on their deviation from the baseline; performance data for new computer chips and similar products is often displayed in this way.

The math to accomplish this is outside the scope of this article, but the point is that you can use the same markup and style techniques to chart different types of data. Just be sure to use clear titles and descriptions for your charts so the values themselves make sense in context.

In these examples, I’m using made-up data, but there are a variety of ways to calculate the percentages on a real site. You can calculate them on the backend and display them in a template (I’m a big fan of the Django framework’s widthratio template tag, which makes calculating ratios between two variables very easy). You can also use JavaScript to extract the percentage value from the markup and apply it as a width style to the bars. For the purposes of these examples, I’m assuming that all the necessary math is already done, so I’ll use hand-coded inline styles to represent the widths of the bars as percentages. (Line wraps marked » —Ed.)

<ul class="chartlist">
  <li>
    <a href="http://www.example.com/fruits/apples/">Apples</a> 
    <span class="count">420</span>
    <span class="index" style="width: 42%">(42%)</span>
  </li>
  <li>
    <a href="http://www.example.com/fruits/bananas/"> »
    Bananas</a> 
    <span class="count">280</span>
    <span class="index" style="width: 28%">(28%)</span>
  </li>
  <li>
    <a href="http://www.example.com/fruits/cherries/"> »
    Cherries</a> 
    <span class="count">200</span>
    <span class="index" style="width: 20%">(20%)</span>
  </li>
  <li>
    <a href="http://www.example.com/fruits/dates/">Dates</a> 
    <span class="count">100</span>
    <span class="index" style="width: 10%">(10%)</span>
  </li>
</ul>

Finishing up the bar chart

The final step for this chart is to add the styling for the bars. I’m going to hide the actual percentage (using the text-indent trick from Mike Rundle’s trusty image replacement technique) since the precise value isn’t as important as the relative scale and the bar will do nicely on its own. If you need to show the percentage, you can choose to display the percentage within the bar or use separate elements for the bar and the percentage.

I’ll set the height of the bars to 100%, and since we’ll be setting the actual width later, I’ll set a default width of 0 so the bars won’t show up at all if we don’t give them an explicit value. I’ve chosen a light blue for the bar backgrounds, so the link text will be readable when it overlaps the bar, but the bars will still be easily visible.

.chartlist .index { 
  display: block; 
  position: absolute; 
  top: 0; 
  left: 0; 
  height: 100%; 
  background: #B8E4F5; 
  text-indent: -9999px; 
  overflow: hidden; 
}

I’ll add in some hover styles for the chart rows and some borders and padding for readability, and we’ve got ourselve a chart (see the finished example). One thing to note is how it looks with styles turned off. Because we made sure to put the percentages in the markup, the information we’re conveying with a fancy chart is still accessible when that presentation degrades.

Timeline charts

Once you’ve established the basic recipe of good old semantic navigation with a sprinkling of data and some CSS to turn the data into a chart, there are lots of variations you can apply to different types of data. One easy trick is to turn the chart on its side to make a timeline (see the second chart in the finished example).

I’ll start out with markup that’s very similar to the first chart. In this case, it’s a list of days, with a value for each day. Since I have limited space in the horizontal format to show labels for each bar, I’m just going to show the day of the month in the label and use a header above the list to indicate the month. Since this is meant to be navigation, and the chart will convey the relative values (which is what we’re after), it’s okay that we’re not showing all the information here. As an extra convenience, I’ll add a title to the links with the full date and the count for that day, so that detail will show up in a tooltip for each bar. (Line wraps marked » —Ed.)

<ul class="timeline">
  <li>
    <a href="http://www.example.com/2007/dec/1/" »
    title="December 1, 2007: 40">
      <span class="label">1</span>
      <span class="count">(40)</span>
    </a>
  </li>
  <li>
    <a href="http://www.example.com/2007/dec/2/" »
    title="December 2, 2007: 100">
      <span class="label">2</span>
      <span class="count">(100)</span>
    </a>
  </li>
</ul>

Repeat as necessary. In this example, I’ve created a 30-day chart with a bar for each day, but you could make the chart any length and size you need. If you’re making a chart with fewer bars, you can make them larger and make room for longer labels underneath.

In order to make sure the labels fit in the space underneath the bars, I’m going to size the entire chart using ems so it will flex up and down with the text size. To make things simpler, I’ll go ahead and set the text size I want to use for the labels on the main .timeline element, so I can size everything relative to that. I’ll set the height of the whole chart to 10 ems, which leaves 8 ems for the bars and 2 ems for the labels with a little padding.

.timeline { 
  font-size: 0.75em; 
  height: 10em;
}
.timeline li { 
  height: 8em;
}

As before, I’ll set the list items to use relative positioning, since I’ll be using absolute positioning to pin the bars to the bottom of each one. (If I didn’t use absolute positioning relative to each list item, the bars would just dangle from the top, which isn’t quite the look we’re going for.) I’ll float the list items to the left so the bars will line up next to each other horizontally. I’ll set the width of each bar to 1.5 em and the height to 8 em to give us enough room for the two-digit labels below, and put just a little padding on either side to separate the bars from each other. I’m using ems for all these dimensions so the bars and the labels will scale up together when the text size changes.

.timeline li { 
  height: 8em;
  position: relative;
  float: left;
  width: 1.5em; 
  margin: 0 0.1em;
}

Since I want the entire area of the bar and label to be clickable, I’ll set the links to display: block and height: 100% so they fill the available space we’ve created for the list items.

.timeline li a { 
   display: block;
   height: 100%;  
}

I want the labels to display below the bars, but since I want both the bars and the labels to be clickable, I’ve placed them both inside the a tag. In order to move the labels out of the way, I’ll use absolute positioning with a negative bottom value. Since the bars have a height of 8 em and the chart is 10 em I’ll move the labels down from the bottom of the bars by -2 em. I’ll give them a line-height of 2 em as well, so no matter what the text size, they’ll have plenty of room right in the middle of that vertical space. We’ll pin them to the left side of the bars and set them to take up the full width with the text aligned in the center.

.timeline li .label { 
  display: block; 
  position: absolute; 
  bottom: -2em; 
  line-height: 2em;
  left: 0;
  width: 100%;
  text-align: center;
}

Since we’re not going to be displaying the counts in this chart, I’ll just use the count element to display the bar, using the same combination of text-indent and overflow to hide the text. I’ll give the bars a solid background and position them from the bottom left of the list item. Reversing what we did for the horizontal bar chart, I’ll set the width of the bars to 100% and the height to zero.

.timeline li a .count { 
  display: block; 
  position: absolute; 
  bottom: 0; 
  left: 0; 
  text-indent: -9999px; 
  overflow: hidden;
  width: 100%; 
  height: 0;
  background: #AAA; 
}

For this chart, I don’t want the each bar to represent a percentage of the total for the whole chart, since there are so many items and the bars would all be tiny. Instead, I want the day with the highest count to be the tallest bar, so I’ll set the height on that to 100% and calculate the heights of all the percentages of that highest day’s count. You can see results (with fake data but real math) in the finished example. I’ve also added a hover style to highlight the bar, label, and background area so it’s clear which item is about to be clicked on.

Sparklines

Now that we’ve tipped our chart on its side to make a timeline, we can shrink it down to make a sparkline—a handy way to show a snippet of data inline with text (see the finished example). You can see from the example that we’ll basically just be shrinking the timeline chart. With bars instead of lines, it won’t be a true sparkline, but it fits the spirit of packing a lot of information in a simple, word-sized chart.

The starting markup is a little different this time. Since we’re creating multiple charts, we’ll still start with an unordered list, but each list item will contain a full chart. Since sparklines are meant as an unobtrusive way to display information inline with text, we can try to reflect that even in the unstyled markup by using inline elements for each of the data points. What we’re shooting for is something that reads reasonably clearly even without the CSS applied. Take a look at this example with the styles removed to get a better idea.

I’ll start with a span as the container for the chart, and use another span with a class of index as the container for each bar. Each number in the series is wrapped in its own span with the count class. This is what we’ll use to style the bar. Since I’ll be hiding all the text within the inner span.count tag anyway, I’ve gone ahead and added commas between each one and parentheses around the whole set to make the unstyled content a little clearer.

<span class="sparkline">
  <span class="index"><span class="count">(60, </span></span>
  <span class="index"><span class="count">220, </span></span>
  <span class="index"><span class="count">140, </span></span>
  <span class="index"><span class="count">80, </span></span>
  <span class="index"><span class="count">110, </span></span>
  <span class="index"><span class="count">90, </span></span>
  <span class="index"><span class="count">180, </span></span>
  <span class="index"><span class="count">140, </span></span>
  <span class="index"><span class="count">120, </span></span>
  <span class="index"><span class="count">160, </span></span>
  <span class="index"><span class="count">175, </span></span>
  <span class="index"><span class="count">225, </span></span>
  <span class="index"><span class="count">175, </span></span>
  <span class="index"><span class="count">125)</span></span>
</span>

I’ll float the whole chart to the left to set it next to the text. Using a float here is a bit of a cheat, since sparklines are supposed to be able to appear directly inline with text. But until we get more consistent support for the inline-block display property (which is the effect we really want), a simple float will have to do. Since we want the chart to be the same size as the text, I’ll give it a height of 1em, and a little margin on either side to give it some breathing room.

.sparkline { 
  float: left; 
  height: 1em;
  margin: 0 0.5em;
}

Since the .index elements are the bar containers this time around, we’ll set position: relative on them, and float them to the left. I’ll make the bars 2px in width, since 1px is a little too small to really register. We want them to take up the full height of the chart, just like in the timeline, so we’ll set the height to 100%.

.sparkline .index { 
    position: relative;
  float: left; 
  width: 2px; 
  height: 1em; 
}

For the bars themselves, we’ll follow almost exactly the same pattern as we did for the timeline, and apply the text-hiding trick.

.sparkline .index .count { 
  display: block; 
  position: absolute; 
  bottom: 0; 
  left: 0; 
  width: 100%; 
  height: 0; 
  background: #AAA;
  overflow: hidden; 
  text-indent: -9999px;
}

I’ll also add some basic styles and padding to the unordered list containing our sparkline set. You can see the results in the finished sparklines example.

Don’t underestimate the Force

You can see all the charts together in the final example. Try adjusting the text size and see how the charts adapt with the rest of the page, or check out the unstyled example to see how the markup degrades for machine readers or environments without full CSS support.

Obviously, these charts are simple, and these techniques aren’t the best option in every situation. When you need semantically rich markup—when you have a large set of data or a lot of categories to compare, nothing beats a table. For fancy visualizations like scatterplots and 3D pie charts, you might choose some kind of server-side tool to generate images. For extra interactivity and animation, Flash is probably the way to go.

It’s certainly not a universal framework for data display, but it does add a lot of context to what would otherwise be blind navigation. And you can start to see how these techniques could be the building blocks for more complex implementations.

So, when you want to build some ambient visualizations that are integrated with the structure of a data-driven site, remember: you can go a long way with accessible, standards-based markup and some simple CSS.

31 Reader Comments

Load Comments