Better Reporting with Sparklines
Three pens for five dollars, black, red and blue, recommended by scholars. —Buck 65
Two events from January, unrelated but relevant:
- I toured the offices of Highgroove Studios and viewed their upcoming Scout reporting tool. They’ve written their graphing tool to allow easy comparisons between any group of measurements on the system, such as hits to their blog vs. new signups to their webapp. Comparing seemingly unrelated datasets often results in unexpected conclusions.
- Information design guru Edward Tufte posted a video review of the iPhone. One of his widget-related suggestions was to include sparklines to show months of history instead of just a single number.
A third event preceding either of these was a redesign of my internal reporting page to use sparklines extensively.
The Problem
Previously, I used Gruff to generate one or two 400×300px graphs to show a few important metrics. But it kept bugging me that I had all kinds of information in the database that I couldn’t visualize.
I like to use a single reporting page with as much information as possible. I also want to be able to apply a different stylesheet and view these pages on my iPhone or a laptop with a small screen.
The problem with large graphs is that adding more of them means occupying a lot more space on the page. Having a bunch of large graphs makes it hard to get a quick idea of where things are at.
Books such as Information Dashboard Design observe the fact that graphs are best at showing general trends, not specific numbers. Tables are great at showing specific numbers, but not general trends. So why not omit all the labeling and just focus on the graphic?
So I scrapped all the labeled, numbered, scaled graphs and replaced them with purely graphical sparklines that each show about three months of data in 300×30px. When I need to know the exact numbers, I can look at a nearby table that lists the data for the current day or the past week.
Go Small
I started with a graph of the number of dynamic hits to the site, as reported by the Rails analyzer and stored in the database.
This isn’t very useful alone. The graph isn’t scaled from zero, so it’s impossible to make specific conclusions. But it is valuable for finding out about relative changes from day to day.
I also added a white line as a target value for comparison. Specific numbers aren’t relevant, but I can tell whether or not I’ve hit the target for the day.
Where it really starts to get interesting is when you add other values to compare to.
Here’s a whisker graph marking the days when products were released. Today is on the far right. You can see a few correlations between product releases and the number of hits to the site. The conclusion may be obvious (product releases result in hits to the site), but now the graph of hits makes a little more sense. A few recent traffic spikes on the right side can be explained by a corresponding product release.
Next, I added a graph of overall daily performance.
Performance is pretty steady except for some recent fluctuations. This statistic isn’t too meaningful since it goes all the way from the delivery of action cached pages to slower pages that call remote APIs.
In order to track a specific page, I added another sparkline.
Many more could be added: daily revenue, user signups, coupon redemptions, referrers from a specific site, downloads.
Putting these all together, I get a graph that is about half as tall as the single graph shown at the beginning, but shows several hundred numbers.
They can be stacked on a web page, viewed on an iPhone, and compared easily.
Tips
In order to make the most of stacked sparklines, I’ve found it helpful to:
- Use a similar horizontal scale
- Add a text label
- Cache when possible
To get you started, here’s some sample code.
Make a controller that will render the graphics. Include an image_tag in your view that calls the controller.
<%= image_tag(formatted_graph_url('hits_by_day_past_three_months', :format => 'png')) %>
I like to use the show action and check params[:id] for the name of a recognized report (/graphs/hits_by_day_past_three_months.png). And of course, add a before_filter to restrict access!
Most graphs will have similar options, so I wrote a method that returns a hash of default options.
def default_sparkline_options
{
:background_color => 'transparent',
:step => 3,
:height => 30,
:line_color => "#6699cc",
:underneath_color => "#ebf3f6",
}
end
Another method generates the graph and caches the binary result in memcached for an hour.
def hits_by_day_past_three_months
graph = Cache.get("Sparklines:hits_by_day_past_three_months", 1.hour) {
records = LogAnalysisRequestTime.find_recent_by_resource("All")
data = records.map { |r| r.quantity.to_f }
graph = Sparklines.plot_to_image data, default_sparkline_options.merge({
:target => 1_000_000
})
annotate(graph, "Daily Dynamic Hits")
graph = graph.to_blob
}
send_data graph, :type => "image/png", :disposition => "inline"
end
The annotate method adds a white box and label to the bottom of the sparkline, using a pixel font.
Two things confused me at first:
- The data is displayed with the newest items at the right, but only shows the most recent three months. So the SQL query needs to sort DESC but use Ruby to
reversethe results. - The production_log_analyzer reports the fraction of a second that it took to serve a request, but I like to view the data as requests per second. So I wrote a method in the model to invert the performance values (
1/seconds_per_request).
Resources
- Sparklines
- Tidy Table gem
- Pixel fonts
- Book: Information Dashboard Design by Stephen Few.
- Book: Show Me the Numbers also by Stephen Few.
- Book: Beautiful Evidence by Edward Tufte.
Recently Published at PeepCode
PeepCode Screencasts – Learn Ruby on Rails and Javascript! Hour-long screencasts for $9.
- Person:



Recent comments
1 year 23 weeks ago
1 year 23 weeks ago
1 year 25 weeks ago
1 year 27 weeks ago
1 year 42 weeks ago
1 year 45 weeks ago
1 year 45 weeks ago
1 year 45 weeks ago
1 year 46 weeks ago
1 year 48 weeks ago