Three pens for five dollars, black, red and blue, recommended by scholars. —Buck 65
Two events from January, unrelated but relevant:
A third event preceding either of these was a redesign of my internal reporting page to use sparklines extensively.
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.
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.
In order to make the most of stacked sparklines, I’ve found it helpful to:
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:
reverse the results.1/seconds_per_request).
I saw a link to the ‘TidyTable’ gem and was interested, but seattlerb has nothing and a web search turns up nothing as well. What is that?
Tidy table, by our author: http://rubyforge.org/frs/shownotes.php?release_id=16368
I really like the new style of the blog!
@Yudi: Thanks! I’m trying to mix it up a bit and experiment with some ideas.
TopFunky, this post has been very insightful, I am at the moment experimenting with graphing data. Thankyou!
I have been watching with keen eyes for the upcoming launch of Scout. Anybody have any idea of launch dates and also cost of service?
may I ask how you did the ‘product releases’ graph? I’ve been looking for something like this and I didn’t find any graph library that allowed displaying ‘binary’ values…
thanks a lot!
Thanks for the post. I was at the Big Nerd Ranch a few weeks ago with CBQ, and he briefly showed Scout to us one evening. This post on top of that week triggered some ideas on how to get one of my clients to let go of his hit counter and give him some nice trends. Something a little more client-friendly than the hosting stats page. Now I just need to get that site out of PHP and into Rails…..
This sparklines stuff is very good and useful in many ways. Can put a lot of graphs in my backoffice. May I ask about the annotate code ? :p
thanks
I am getting a “undefined method `plot_to_image’ for Sparklines:Class”
@Chris: Yes, it’s a new feature in the latest Sparklines gem.
Ah I see, I was using the rails plugin, switching to the gem worked wonders!
The plugin has now been updated.
Mr. Topfunky,
Here is a write-up which at its core is about info presentation.
http://www.gaffneyware.com/the_boxtone_dashboard_and_the_blackberry_outage.htm
Please note that I work at boxtone and work on the dashboard mentioned in the write-up.
However the use of sparklines to present complex (my choice) data is the reason for this post. A practical application – so to speak.
Cheers! sinclair
Why is the gem so different from the plugin? I thought the plugin was just a wrapper over the gem, but digging in revealed significantly different codebases.
For instance plugin bar graph is normalized from MIN to MAX value whereas gem is normalized from 0 to MAX. Plugin also has a bug where the graph can go out of range of the window for certain values.
Gabe: I'm in the middle of moving everything to GitHub and haven't updated the plugin. In the meantime, it's easiest to just @gem unpack sparklinesinto your plugin directory.