Sparklines, as defined by Tufte, are intense, simple, word-sized graphics. Kind of like this: . I seemed to stumble across them at just the right time, as I have regression tests I am adding to on a daily basis. The result is a flood of information. I believe sparklines may be the answer to my information avalanche.
All of my regression scripts are written in Python and the output of those scripts is HTML. Embedding sparklines in that report output sounds like a perfect application of "data: URIs" [RFC 2397], which allow you to take small bits of data, like small images, and instead of serving them up seperately, you embed the data right into the URI. In this case, I'll generate PNG formatted sparklines then encode them as data URIs that can be included directly in my HTML formatted regression test results. So I dashed off to find a sparklines module for Python.
I found none.
I did find one for PHP, http://sparkline.org, but that would not be Python now would it?
Not to be discouraged, I set off to load up the standard image manipulation package for Python.
Explain to me again why doesn't Python have a standard image manipulation package?
I settled on Python Imaging Library (PIL).
Making the sparklines turned out to be incredibly easy, making me think that the reason there wasn't any libraries is that it's just so easy to glue the right pieces together in Python that a library would be overkill. Here is the code:
import Image, ImageDraw import StringIO import urllib 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-r/10-4, i, (im.size-r/10)), fill=color) del draw f = StringIO.StringIO() im.save(f, "PNG") return 'data:image/png,' + urllib.quote(f.getvalue()) if __name__ == "__main__": import random html = """ <html> <body> <p>Does my sparkline <img src="%s"> fit in a nice paragraph of text? </p> </body> </html>""" print html % plot_sparkline([random.randint(0, 100) for i in range(30)])
The example output is just a plot of 30 random values. You should put more meaningful data in there. All the work is done on plot_sparkline, which plots the data as 4 pixel high bars in an image that is 15 pixels high. After the binary PNG image is generated, converting it to a data URI is staggeringly easy, it's done in the return statement of plot_sparkline. The output of the script is a sample HTML file to demonstrate the sparkline embedded in the HTML.
The above code produces output that should look like:
Does my sparkline fit in a nice paragraph of text?
Nota bene: If you are not able to see the image in the above text then that means that you are probably using Internet Explorer, which does not implement data URIs. You might want to get a better browser.
Update: Ooops, I forgot to give Anil proper credit for bringing sparklines to my attention.
Update 2: If you want a continuous plot instead of a series of tick marks it is easy enough to code up. Note that it even has the red dot on the last data point.
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-1, end-1, end+1, end+1], fill="#FF0000") del draw f = StringIO.StringIO() im.save(f, "PNG") return urllib.quote(f.getvalue())
Update 3: Highlighting the minimum point in is a matter of adding two more lines, one to find the minimum, and another to plot a rectangle at that point. While Tufte has pointed out that sparklines are targeted at high resolution printing, there are advantages to working with them on the computer. For example, on a web page we can put the raw data into the title of the image and they will be displayed when the mouse hovers over the sparkline. Try it out, hover your mouse over the image: 1 78. Here is the code that generated that sparkline, which not only generates the 'img' element but also prints the minimum point and the last data value in colors that match the corresponding points in the sparkline.
def plot_sparkline3(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-1, end-1, end+1, end+1], fill="#FF0000") min_pt = coords[results.index(min(results))] draw.rectangle([min_pt-1, min_pt-1, min_pt+1, min_pt+1], fill="#0000FF") del draw f = StringIO.StringIO() im.save(f, "PNG") return """<img src="data:image/png,%s" title="%s"/> <b style="font-size: 10pt;font-family: Verdana, Arial, Helvetica, sans-serif"> <span style="color:#0000FF">%d <span style="color:#FF0000">%d </b>""" % (urllib.quote(f.getvalue()), results, min(results), results[-1] )
Update 4: I am deeply impressed with the work being done on RedHanded, which is not only sparklines in Ruby, but they're generating BMPs and PNGs from scratch. Wow.
Update 5: What's better than sparklines? How about sparklines + imagemaps.
Update 6: If you want to use sparklines beyond just where
data: URIs are
available, please avail yourself of my Sparkline Generator.
It's a web application, and a web service, for generating sparkline images, all with source code.