BabyNameTrends.netHome

How to embed sparklines inline into web pages with "data" URLs (and a little PHP)

NameTrends.net has quite a few pages that contain lists of hundreds names (e.g. names that start with M, top 100 names of 1975). When I decided to add sparklines (Wikipedia: Sparkline) to each occurrence of a name, I knew delivering pages containing hundreds of images would be problematic.

This article describes how I developed NameTrends.net to deliver pages with hundreds of small images in a single response to a single HTTP GET request, all in a transfer that would be reasonable over dialup.

Top 100 names of 2007 screenshot
Snippet of "Top names of 2007" screenshot

Creating Sparkline Images

Though there is at least one PHP library (and undoubtedly countless more), I was drawn to the Google Charts API because I had recently already used it in this project (for the popular in the same region pages).

Starting from arrays of name data like:

namegenderyearrankpercent
MollyF2007970.1620
MollyF20061050.1502
MollyF2005970.1586
MollyF2004970.1660
MollyF20031000.1700
MollyF20021070.1583
MollyF2001910.1726
MollyF2000950.1706
MollyF19991020.1709
...

I used a small amount of PHP to produce URLs like: http://chart.apis.google.com/chart?cht=ls&chm=B,fe72d1,0,0,0&chds=0,.1726&chd=t:.1709,.1706,.1726,.1583,.1700,.1660,.1586,.1502,.1620&chxr=0,1999,2007|1,0,.1726&chs=20x10&chco=9e129d

Using the URLs to retrieve a sparkline image for each name allowed me to produce something like: Molly

Here's the PHP code that did that:     $data = array(.1709,.1706,.1726,.1583,.1700,.1660,.1586,.1502,.1620);
    $maxval = max($data);
    $chm = 'B,fe72d1,0,0,0';
    $chco = 'de129d';
    $url = 'http://chart.apis.google.com/chart?cht=ls&chm='.$chm.'&chds=0,'.$maxval.'&chd=t:'.implode(',', $data).'&chxr=0,1880,2007|1,0,'.$maxval.'&chs=20x10&chco='.$chco;
    copy($url, 'sparklines/'.$nameids[$i].'.png');

It was quick, easy, and I soon had about 6000 very small PNGs, ready to display next to each name.

It worked wonderfully on the name pages (e.g. Molly) and others with a dozen or so names, but on the "data dump" list pages with hundreds of names (e.g. 'S' names), the visible loading of the sparklines could take upwards of 5-10 seconds. Loading hundreds of resources per page didn't particularly bother me from a server performance perspective, as they were tiny static files which could be cached for months. However, for browsers with unprimed caches, watching sequential requests for hundreds of images created a user experience reminiscent of late 1990s dial-up browsing.

CSS Sprites

Having played a lot with YUI lately, CSS Sprites popped quickly into my mind as a way to reduce the number of GET requests. Essentially, instead of sending 100 small images as individual files, you combine the 100 images into one large composite image. Then, using a bit of CSS positioning wizardry, you display only the desired part of the large composite at each location that needs an image.

While this is a technique that should be in every designer's toolkit for small iconsets, it's not the direction I took. Because there are about 6800 names in the database, a composite sparkline image for all names would have been 1-2Mb.

So, instead of creating a single composite image, performance concerns would have forced me to create a composite image per page. While it undoubtedly would have been possible to do that dynamically from PHP+GD, I kept looking.

Exploring Vector Rendering

People had been buzzing about the promise of vector rendering of graphics in web pages since, well, pretty much since I wrote my first web pages in the mid-90s. However, reality never seemed to catch up with the buzz and I've honestly never attempted to use a vector markup language. So, I had some learning to do.

I imagined that I would be able to replace something like this: <a href="name.php?name=Molly"><img src="sparklines/molly.png">Molly</a>

With something like this: <a href="name.php?name=Molly">
    <vectorImage strokeColor="pink">
        <line path="0,0 1,1 2,1 3,2, 4,2 5,3 6,3 7,2, 8,2 9,2 10,2">
    </vectorImage>
    Molly
</a>

While VML (Vector Markup Language) comes close to that, it turned out that I wasn't the only one who failed to pursue vector rendering in the late 90s, despite the buzz. According to Wikipedia, the only modern browser that supports VML is Internet Explorer.

The VML Wikipedia page suggested I also look into Scalable Vector Graphics (SVG). Though natively supported by Firefox, Opera, and Safari, SVG is not natively supported in Internet Explorer. Further, embedding SVG inline into HTML is apparently somewhat problematic -- SVG can only be included inline in XHTML, not HTML, and all the content-type issues that go with it.

Again, there is probably a viable solution lurking here: browser (or capability) sniff, deliver VML to any browser that supports it, and deliver XHTML+SVG to everyone else. Nonetheless, again, I kept looking.

"Data" URLs

While trying to learn how to better inline SVG (or VML), I stumbled on RFC 2397 - The "data" URL scheme. This scheme can be used to inline any resource into the document, and is supported by all modern browsers (IE support comes in version 8).

It allows you to encode data directly into a URL, so HTML code like this: <li class="nameF">
    <img width="20" height="10" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAABmJLR0QA/wD/AP+gvaeTAAABCElEQVQokWP8//8/Ayng/59/f198Y5HhYWBgYCJJJwMDw6uk/Y/1V/288IaBgYGRJJv///n3QHwBhC3cbkGazZ8X3mRgYOCU5mNgYPh76yMLCTqX3HpbdpyBgUHITI6Vn4MxVZFYm/99/PWu8iQDA4OYkzIrPwdEkCib/334+dx7279vvyV9NdmFueHiLP++/v775CuzOCfDf4aPU678efGNJ0z5z4PP/z7+YmBg+P3o849jL37f/MDAwCBgKI2sk4GBgeXTtKvvO84hC31ZcRvTclFHZW55QTRBFi43WcYvf/9++MnEzcJlI8UqzfPjyltGViYWCS5GJkYGZkYmPjZGZmxBI8MJAFbiXRKXIv/6AAAAAElFTkSuQmCC">&nbsp;<a href="name.php?name=Molly">Molly</a>
</li>

Is rendered like this: Molly

This is just what I was searching for. The top names of 1975 page was 26Kb and loaded 100 image resources, each via its own GET request. Applying the above technique to inline the images with data URLs resulted in a 70Kb page with 100 inlined images, all of which GZips to 33Kb.

The PHP code

Once we have sparklines (using Google Charts as described in Creating Sparklines, or using gnuplot, GD, or your favorite way to turn data into a graph), we use a little bit of PHP to turn the images into data URLs.

I'm starting with some ugly browser sniffing (remember that we wanted a single solution for all browsers, so this is a stopgap until IE8): //determine if sparklines will be inline (data uris) or external images
$inlinesparklines = (strpos($_SERVER['HTTP_USER_AGENT'], 'Gecko') !== false ||
        strpos($_SERVER['HTTP_USER_AGENT'], 'Opera') !== false);

Now either read the sparkline image and write it as a data URL, or include the image itself: <li>
if ($inlinesparklines)
{
    $contents = file_get_contents('sparklines/'.$nameid.'.png');
    $sparklinedata = 'data:image/png;base64,'.base64_encode($contents);
    <img src="$sparklinedata">
}
else
{
    <img src="sparklines/{$nameid}.png">
}
</li>

Epilogue

While I'm pretty pleased with this technique for this application, you probably shouldn't rush out and start moving all your external resources to data URLs.

This application was particularly suitable for data URLs for a few reasons:

To help you decide whether data URLs are appropriate for your application, consider the unique characteristics of your pages against the pros and cons of data URLs:

Pros:

Cons: