A Bright, Shiny Service: Sparklines

A Bright, Shiny Service: Sparklines

by Joe Gregorio
June 22, 2005

I really was working on the bookmark web service—really, I was!—but I got distracted. What grabbed my attention was Sparklines.

What are Sparklines?

Sparklines, as defined by Edward Tufte, are intense, simple word-sized graphics. They are small graphics embedded within a context of words or numbers. They are described in a chapter, which he's published on the Web, of his soon to be released book Beautiful Evidence. That article has been up for a while, but looking at the number of new links to it per month as recorded by Technorati 0 6 you can see that interest is ramping up. That little graph showing the links per month is a sparkline.

The BitWorking Sparkline Generator is my contribution to the Web 2.0: it's a web service, web application, and the source code to both. It's also the subject of this article.

Drawing Sparklines in Python

Sparklines are useful for presenting a large volume of information in a small space using a context sensitive manner. I have test routines that I run regularly and the volume of output can be tremendous. I stumbled onto sparklines and found they are a great way to ease the information overload. I started drawing sparklines in Python using the Python Imaging Library. It is easy to get started with a basic template and then modify the code. Here are some examples:

import Image, ImageDraw

import StringIO



def plot_sparkline(results):

  """Returns a sparkline image as a data: URI.

     The source data is a list of values between

     0 and 100. Values greater than 95

     are displayed in red, otherwise they are displayed

     in green"""



  im = Image.new("RGB", (len(results)*2, 15), 'white')

  draw = ImageDraw.Draw(im)

  for (r, i) in zip(results, range(0, len(results)*2, 2)):

      color = (r > 50) and "red" or "gray"

      draw.line((i, im.size[1]-r/10-4, i, (im.size[1]-r/10)), fill=color)

  del draw



  f = StringIO.StringIO()

  im.save(f, .gif")

  return f.getvalue()

        

This will produce a sparkline that looks like this:

This is just one kind of plot; it's not that much work to create a different kind of sparkline, for example, one of the continuous plots:

The code for the image above is the following:

def plot_sparkline2(results):

   im = Image.new("RGB", (len(results)+2, 20), 'white')

   draw = ImageDraw.Draw(im)

   coords = zip(range(len(results)), [15 - y/10 for y in results])

   draw.line(coords, fill="#888888")

   end = coords[-1]

   draw.rectangle([end[0]-1, end[1]-1, end[0]+1, end[1]+1], fill="#FF0000")

   del draw 



   f = StringIO.StringIO()

   im.save(f, .gif")

   return f.getvalue()

A note about the limitations of what we can do. We aren't going to reproduce Galileo's drawings of the moons of Jupiter, nor are we going to get the resolution that can be achieved on paper.

The Discovery of the Galilean Satellites

On the other hand, we can exploit the advantages of the platform of our choice. We can put info or raw data into the "title" attribute of the image. Putting the raw data into the "title" attribute of the image causes the raw data to be displayed when the mouse hovers over the image. Here is how it looks in FireFox:

A popup showing the raw data that informs a sparkline.

We can also make the sparkline clickable, either making the entire image a link, or using an image map to make parts of the sparkline lead to further resources.

Spreading the Joy via Web Services

Now hacking Python is fun, but it's not for everyone. Let's open this up for everyone to use. First, we'll start by creating a web service. What else did you expect me to do?

For simple sparklines we can use query parameters to pass the data into a CGI application that draws the sparkline. Let's start by reviewing with the four questions we ask when building any web service:

  1. What are the resources? The resources are sparklines. To specify how each sparkline will appear we can pass in the data via query parameters. Our sparkline code only takes one parameter, a list of data with values between 0 and 100. That data can be passed in by a query parameter d whose value is a comma separated list of values between 0 and 100. For example: http://bitworking.org/projects/sparklines/spark.cgi?d=10,20,30,40.
  2. What are their representations? The representations can be in an image format:.gif, GIF, JPEG or maybe even SVG.
  3. What methods do those resources support? GET
  4. What errors could be generated? 4XX if the parameters passed in don't correspond to data that can be graphed.

Now remember our follow-up questions about GETs. Is our use of GETs both safe and idempotent? Retrieving an image is certainly safe, and doing so multiple times still returns the same image, so we are using GET correctly.

Here is a first pass at an implementation of our web service. Note that this is not the service I deployed, it is a much simpler version used here just for exposition:

#!/usr/bin/env python

import cgi 

import cgitb 

import sys 

import os 

 

cgitb.enable() 

 

import Image, ImageDraw 

import StringIO 

import urllib 

 

 

def plot_sparkline(f, results): 

  """Returns a sparkline image as a data: URI.

     The source data is a list of values between

     0 and 100. Values greater than 95

     are displayed in red, otherwise they are displayed

     in green"""

  im = Image.new("RGB", (len(results)*2, 15), 'white')

  draw = ImageDraw.Draw(im)

  for (r, i) in zip(results, range(0, len(results)*2, 2)):

     color = (r > 50) and "red" or "gray"

     draw.line((i, im.size[1]-r/10-4, i, (im.size[1]-r/10)), fill=color)

  del draw

 

  im.save(f, .gif")

 

def plot_error(f): 

  im = Image.new("RGB", (40, 15), 'white')

  draw = ImageDraw.Draw(im)

  draw.line((0, 0) + im.size, fill="red")

  draw.line((0, im.size[1], im.size[0], 0), fill="red")

  del draw

  im.save(f, .gif")

 

def error(status="Status: 400 Bad Request"): 

   print "Content-type: image.gif"

   print status

   print ""

   plot_error(sys.stdout)

   sys.exit()

 

def cgi_param(form, name, default): 

   return form.has_key(name) and form[name].value or default

 

if not os.environ['REQUEST_METHOD'] in ['GET', 'HEAD']: 

   error("Status: 405 Method Not Allowed")

form = cgi.FieldStorage() 

raw_data = cgi_param(form, 'd', '') 

if not raw_data: 

   error()

data = [int(d) for d in raw_data.split(",") if d] 

if min(data) < 0 or max(data) > 100: 

   error()

 

print "Content-type: image.gif" 

print "Status: 200 Ok" 

print "" 

plot_sparkline(sys.stdout, data)         

There are a few noteworthy points:

Errors
If some of the parameters are missing, or incorrect, we return an error message that is the same type as a successful response, that is, we return a big red X as a .gif to indicate that there was an error. That's because our service will be used to serve up images that will most likely appear in web pages via the <img/> element. This way if an error occurs there will be visible feedback by the appearance of a large red X.
Methods
Note that we manually restrict our handling of HTTP methods to those of just GET and HEAD. If we don't do this, then our web service will also respond to POST methods. That's because our query parameter parsing library is a little too helpful and will handle POSTed data in an indistinguishable manner from GET requests. In this case it's not really that damaging, but imagine if the tables had been turned and we had created a service that should only respond to POST. Unless we check the incoming method ourselves then our service would gleefully accept both GET and POST requests and treat them as the same. That can lead to ugly problems, particularly if we settled on using POST because the action taken wasn't idempotent or safe.

Full Web Service Description

Here is a full description of the web service as it is deployed today:

            http://bitworking.org/projects/sparklines/spark.cgi

        
d The data for the plot. All data values must be between 0 and 100. height The height of the image in pixels. type "discrete" - One vertical bar per data point.
"smooth" - all the points plotted as a continuous line.
Common Parameters
Parameter Description

If the type is "smooth" then the following parameters apply:

min-m If set to 'true', then place a special marker at the smallest value in the data set. max-m If set to 'true', then place a special marker at the largest value in the data set. last-m If set to 'true', then place a special marker at the last value in the data set. min-color The color of the marker placed at the smallest value in the data set. max-color The color of the marker placed at the largest value in the data set. last-color The color of the marker placed at the last value in the data set. step The points are to be plotted every n'th pixel.
"Smooth" Parameters
Parameter Description

If the type is discrete then the following parameters apply:

upper Data values ≥ upper will be plotted in the above-color, otherwise data points will be plotted in the below-color. above-color The color for data points ≥ upper. below-color The color for data points < upper.
"Discrete" Parameters
Parameter Description

Here are some example sparklines and their URIs to get you started.

http://bitworking.org/projects/sparklines/spark.cgi? type=smooth&d=10,20,30,90,80,70&step=4http://bitworking.org/projects/sparklines/spark.cgi? type=smooth&d=10,20,30,90,80,70&step=4&min-m=true&max-m=truehttp://bitworking.org/projects/sparklines/spark.cgi? type=smooth&d=10,20,30,90,80,70
Examples
Sparkline URI

[1] [2] Next

Close    To Top
  • Prev Article-XML:
  • Next Article-XML:
  • Now: Tutorial for Web and Software Design > XML > Web Service > XML Content
    Photoshop Tutorial
     

    Special Effect

      3D Effect
      Photoshop Articles
    Programming Tutorial
     

    C/C++ Tutorial

      Visual Basic
      C# Tutorial
    Database Tutorial
     

    MySQL Tutorial

      MS SQL Tutorial
      Oracle Tutorial
    Geek Tutorial
     

    Blogging Tutorial

      RSS Tutorial
      Podcasting Tutorial
    Graphic Design Tutorial
      Coreldraw Tutorial
      Illustrator Tutorial
      3D Tutorials
    Webmaster Articles
     

    Domain Service

      Web Hosting
      Site Promotion
    Java Tutorial/ Articles
     

    Java Servlets

      JavaEE Tutorial
     

    JavaBeans Tutorial

    XML Tutorial/ Articles
     

    XML Style

      AJAX Tutorial
      XML Mobile
    Flash Tutorial/ Articles
     

    Flash Video

      Action Script
      Flash Articles
    OS Tutorial/ Articles
      Linux Tutorial
      Symbian Tutorial
      MacOS Tutorial
    Personal Tech
      Hardware Tutorial
      Software Tutorial
      Online Auction