2005.07.14 09:46 PM

Sparklines

[Updated 8/31/05: Now version 2.3.]

[Updated 8/25/05: Now version 2.2.]

[Updated 8/3/05: Now version 2.1.]

[Updated 7/16/05: Now version 2.0.]

[Updated 7/15/05: Forgot to include the actual service addresses. Though they could be found by inspecting the IMG elements, I thought actual links might help.]

While eating lunch today, I read the article A Bright, Shiny Service: Sparklines by Joe Gregorio over on O'Reilly's XML.com. In the article, Mr. Gregorio describes a sparkline implementation he did in Python and then exposed as a web service.

If you haven't heard of sparklines, their creator, Edward Tufte, describes them this way:

[S]mall, high-resolution graphics embedded in a context of words, numbers, images. Sparklines are data-intense, design-simple, word-sized graphics. ... Sparklines are wordlike graphics, with an intensity of visual distinctions comparable to words and letters.

After looking over Mr. Gregorio's Python source, I was inspired to replicate its functionality in C# on ASP.NET.

Here's my source. It's intended to be accessed with an .ashx extension. ASP.NET files having .ashx extensions are mapped by the default machine.config to the System.Web.UI.SimpleHandlerFactory where they're parsed and compiled just like .aspx files, except they have to implement the IHttpHandler interface directly instead of inheriting it from System.Web.UI.Page.

Here's a table of sparklines, retrieved using HTML IMG elements pointing at both Mr. Gregorio's Python-based web service and my C# ASP.NET service, along with their querystring parameters:

his mine querystring
type=smooth&d=86,66,82,44,64,66,88,96,26,14,0,0,26,8,6,24,52,36,6,10,30&
height=30&min-color=red&max-color=blue&last-color=green&step=2&
last-m=true&max-m=true&min-m=true
type=discrete&d=66,82,44,64,66,88,96,80,24,36,14,15,26,8,6,66,78,72&
height=15&upper=70&above-color=red&below-color=green
type=smooth&d=40,55,12,75,3,34,65,90,50&height=15&min-color=green&
max-color=red&last-color=blue&step=5&last-m=true&max-m=true&min-m=true
type=discrete&d=40,55,12,75,3,34,65,90,50,66,82,44,64,66,88,96,80,24&height=15&
above-color=purple&below-color=blue&upper=60
(missing or bad parameters)

If Python or C# don't float your boat, take a look on Mr. Gregorio's site and you'll find some links to PHP and Ruby implementations as well. You'll also want to look at his site (or his O'Reilly article) for details on the parameters accepted and/or expected, because I'm never going to get around to reproducing them here.

By the way, before you look at my code and start getting all jiggy on me, read the note. I wasn't striving for C# nirvana, but instead wanted to replicate the organization and behavior of Mr. Gregorio's Python code as closely as I could in the few hours I gave myself to get it done. If I'd had the brains to come up with this on my own, I likely would have organized the code differently.

If any of you actually host the code yourself someplace, or just use my service, please leave me a comment if you find any bugs, compatibility issues with Mr. Gregorio's code, or whatnot. I'll try to monitor Mr. Gregorio's on-going changes and keep my version up to date (no promises, though).

So, that was fun. Thanks, Mr. Gregorio, for the inspiration. Maybe someday I'll actually use this stuff somewhere.


Comments

Is there a way to limit the width?

Matt | 2005.08.12 04:09 PM

Matt,

Not explicitly. It's a function of how many data points you wish to map and the gap you specify between them via the step parameter. You could use the IMG element's WIDTH attribute to specify a width and let the browser scale the resulting image, but it will generally look pretty bad.

ewbi.develops | 2005.08.12 04:39 PM

Just a quick question. It isn't anything too hard to do, but how about a little helper function that would take the d parameter and automatically find the high/low point and then scale the other points relative to that? maybe add a new parameter called "doscale" and run the function if set to "true"

i.e. Have a data stream of "1,5,6,3,4,6,7,6,3,5,6,7,8" - normally, this produces a VERY straight line with little variation. the function would pick 1 as low and 8 as high. The 8 would then become 100 and the 1 a 0. Then, each other point would be scaled accordingly.

Just a thought :)

Matt | 2005.08.13 09:53 PM

Matt,

I'm booked for a few days with other work, but I'll give it a look when I get a break. I'll probably pass it over to Mr. Gregorio as well, to see if he has any interest in adding it to his Python. Thanks.

ewbi.develops | 2005.08.15 09:03 AM

This is a really great tool. But I'm new to this, and awfly daft. I've been trying to raise the web service for about an hour now, with no hope in sight. Could you give me a 5 second blurb on how to implement this either as a strandalone asmx or an ashx in VS2005?

Thank you very, very much in advance.


HELP! | 2005.11.22 09:04 PM

Dear HELP!,

ASMX isn't really an option for this code, at least not without a lot of additional work to properly package the binary image to be returned.

However, the link above to my source represents the contents of a standalone ASHX file (the IMG links themselves actually point at the ASHX file represented in the source).

I haven't yet tried this code in VS2005 (or ASP.NET 2.0), but in VS2003 (or Notepad, for that matter) you can simply create a new ASHX file, paste my source into it, and deploy the ASHX file into an ASP.NET-enabled web directory (or virtual directory) and then cruise over to it either directly (using an URL with the necessary querystring parameters) or using some HTML that includes IMG elements with SRC URLs having the necessary querystring parameters.

If that doesn't make sense or something isn't working, please let me know what you've tried and what you're seeing. Good luck!

ewbi.develops | 2005.11.22 09:45 PM

Matt,

First thanks for the work. I have one suggestion and that would be to offer alternatives to color. I say this for a couple of reasons, color blind people and clarity (no clarity if you printout in bw). An option to the Red or Green high/low points might be an up triangle and a down triangle. If you're sticky to the source then I guess I need to put my question to Mr. Gregorio.

Doug | 2006.08.07 05:32 AM

Hi Doug,

I assume you're referring to me, the author, not Matt, who left some comments above? If so, I think a shape alternative is a fine idea. I don't have time to add this right now, but feel free to modify the source yourself. As the license in the source reads, you can "use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software" as you see fit, with only an attribution restriction. When I get some time I'll look at adding this myself.

Good luck.

ewbi.develops | 2006.08.07 10:12 AM

Thanks so much for releasing this. It is exactly what I was looking for. FYI, under ASP.NET 2.0 the error image does not appear, and a "The image “foo” cannot be displayed, because it contains errors." message appears instead. Any idea how to rectify this?

Jaxon Rice | 2006.08.25 01:46 AM

Hi Jaxon,

Not sure what's up with that, I haven't looked closely at this in ASP.NET 2.0 (in fact I haven't looked at it at all in months). I'll try to take a look this weekend. Thanks for letting me know.

ewbi.develops | 2006.08.25 09:16 AM

No problem. Just thought you might like to know. Just to be clear, the sparklines work just fine under asp.net 2.0, its just the error trapping that does not seem to work.

Jaxon Rice | 2006.08.25 10:27 AM

Jaxon, I've reproduced the problem but can't tell yet what's up. I've verified that the error image is being generated, as I was able to save it a file. Part of the problem is that this code sucks - its organization (and I'm being generous in suggesting it's organized) makes it difficult to pinpoint the moment of failure. My first thought was that it is related to the explicit Response.End method call, which raises a ThreadAbort exception. Perhaps, I thought, in 2.0 this was now derailing the streaming of the MemoryStream into the Response, but that doesn't appear to be the case. I'm going to have to look closer. Perhaps I'll rewrite this whole thing to eliminate its adherence to the original Python structure in order to make it clearer/easier to work with. Also, .NET 2.0 introduces some things that would make all of this easier.

ewbi.develops | 2006.08.28 04:44 PM

Never mind. The problem appears to be that when ASP.NET 2.0 returns an HTTP 400 response (bad request), it includes the HTTP 1.1 header field "connection" with a value of "close". This appears to cause the browser to ignore and/or not get the actual response body content, which in this case is the image. This is not the case with ASP.NET 1.1. The solution, for now, is to replace this line in the parameterized error() function:

SetResponse(statusCode, statusDescription);

with this line:

SetResponse(HttpStatusCode.OK, "Ok");

Thanks again for bringing this to my attention. I'll try to explore this some more and write it up when I get a chance.

ewbi.develops | 2006.08.28 04:56 PM

Or alternatively just pull the Response.Flush call from the SetResponse routine so that the headers (including the new "Connection: close") aren't delivered before the content.

ewbi.develops | 2006.08.28 05:04 PM

Thanks so much for the fix, and once again for the code.

Jaxon Rice | 2006.08.29 03:43 AM

I noticed that the examples don't process exactly the way the screen shots show (the max/min dots are gone, some fo the graphs are a little more squished).

You're still consistent with the Python script, I'm just wondering why the dots were taken out.

Zack | 2006.09.12 07:45 AM

Hi Zack,

Not sure I understand exactly what you mean - sounds like we're seeing different things. What screen shots do you mean, as the sparklines above are actually rendered on request using Joe's and my services. Do you mean to say that the sparklines you're seeing above, specifically the first one and third ones, aren't showing the min/max/last dots?

ewbi.develops | 2006.09.12 09:00 AM

Hi Eric

Thanks for the code. It's very useful. I would like to suggest an addition.

Currently there is a restriction that requires all the values to be in the 0 - 100 range. I'm converting my values to fit this range in ASP before I pass to the ashx page, but if this is something that many users are encountering it might be better to include it in the SparkHandler class.

In asp I use the following Function to build up 6 values for the line.

Function getValueString(MonthPrior6, MonthPrior5, MonthPrior4, MonthPrior3, MonthPrior2, MonthPrior1)
DIM sArray()
REDIM sArray(5)

DIM maxNumber

sArray(0) = MonthPrior6
sArray(1) = MonthPrior5
sArray(2) = MonthPrior4
sArray(3) = MonthPrior3
sArray(4) = MonthPrior2
sArray(5) = MonthPrior1

FOR i = 0 to 5
IF NOT isnull(sArray(i)) and sArray(i) <> "" THEN
IF IsNumeric(sArray(i)) THEN
if maxNumber "" THEN
IF IsNumeric(sArray(i)) THEN
sTemp = sTemp & "," & FIX(FIX(sArray(i)) / FIX("1" & left("0000000",len(maxNumber)-2)))
ELSE
sTemp = sTemp & ",0"
END IF
ELSE
sTemp = sTemp & ",0"
END IF
NEXT

if len(sTemp) > 0 then sTemp = mid(sTemp,2)
getValueString = sTemp
End Function

Joshua | 2006.10.03 01:09 AM

The function in my last post didn't come out the way I'd written it. I hope it serves as an illustration, but otherwise just ignore it.

Thanks

Joshua | 2006.10.03 01:12 AM

Hi Joshua,

Thanks for posting the code - the comments here always eat the formatting, but I get the jist. Based on a request some time ago I added a scaling feature, but limited the scaling to numbers between 0 and 100, as the original code already included that enforced restriction. Since then I've considered adding a full scaling feature that would accept any set of numbers and scale them to the range 0 and 100 for presentation. There are some other issues that need addressing (e.g., the ASP.NET 2.0 Connection: Close header), and a general clean-up needed (e.g., abandoning its legacy Python-based structure). Perhaps in time I'll get around to it.

Thanks again for the suggestion.

ewbi.develops | 2006.10.03 08:54 AM

Bonjour

Je suis très interressé par le graphique dans une cellule

mais l'anglais et moi.

Si possible d'avoir explications en français

merci beaucoup et bonne journée

Raymond

Eliot raymond | 2009.07.08 06:56 AM

Désolé, mais moi n'ayez pas une traduction française. Le meilleur que je peux offrir est une traduction automatique:

http://tinyurl.com/lsf3ff

Bonne chance!

ewbi.develops | 2009.07.08 07:48 AM


TrackBack

TrackBack URL:  http://www.typepad.com/services/trackback/6a00d8341c7bd453ef00d8349bdfa369e2

Listed below are links to weblogs that reference Sparklines: