Before you read this, I suggest you take a second to get familiar with sparklines. They are like tiny, embeddable plots, as originally coined by my statistics hero, Edward Tufte, in his book Beautiful Evidence.
In short, a sparkline is a small plot, generally a line plot, that is about the same height as the text around it. It should also have a dot at its maximum point, if it’s trying to be a real, true sparkline, and it should have a greyish line. However, there is a lot of variation here, and people even make sparkhistograms and sparkpiecharts.
Now that you know what sparklines are, I bet you want to make some. Well…okay! There are a lot of ways to do this. There are (presumably good) sparklines libraries for PHP, Ruby, jQuery (this one looks especially interesting), and even Excel. Or, if you’re a bit crazy and/or like doing things yourself, you could make your own sparkline library! I chose the latter option.
Thus, I’ve created a working, not-yet-perfect sparkline maker for Squeak Smalltalk. Squeak makes graphics (called Morphs) ridiculously easy, and thus makes sparklines pretty simple. Before we begin, a quick note: Squeak represents (x,y) positions as x@y.
Step 1: Create a Form for drawing. I’m going to make an ImageMorph, which has an empty ColorForm as its image. The ColorForm is given a width and height based on various properties of the SparkLinePlot instance.
morph := (ImageMorph new) openInWorld. morph isOpaque: false. morph image: (ColorForm extent: (((data size) + 2) * (pointSpace))@(height * 2) depth: 8).
Step 2: Prepare a few variables for use. x and y are going to refer to the (x,y) coordinates of each point, while scale is going to be multiplied against each data point to “shrink” the data to fit into the desired…well, scale.
scale := (height) / (data range). x := y := 0.
Step 3: Next, we’re going to make a Pen object that’s going to draw the lines for us. Pen objects are remarkably simple to operate: just create one and move it around with the place:, go:, and goto: messages (e.g. “pen goto: 4@4″). The place: method doesn’t draw lines on its way, so it’s good for initial positioning.
pen := (Pen newOnForm: (morph image)) defaultNib: lineWidth. pen place: x@y.
Step 4: Finally, we get the pen to draw! The do: message iterates over a collection, calling the associated block once per item in the collection (That is to say, the block runs once per item in the collection, each time setting the variable “dp” to be equal to that item). The y variable is negated because, in Squeak, positive y is down.
data do: [:dp | y := height + ((dp * scale) negated). (x = 0) ifFalse: [pen goto: x@y] ifTrue: [pen place: x@y]. x := x + pointSpace.]
Step 5: Let’s make this plot pretty. I want to see a little indicator where the max data point is, and maybe even have it change colors from positive to negative. Not so hard! Inside the “data do: []” block, we could add stuff so it looks like the following:
data do: [:dp |
y := height + ((dp * scale) negated).
(x = 0) ifFalse: [pen goto: x@y] ifTrue: [pen place: x@y].
(dp = data max) ifTrue: [ pen color: Color yellow lighter.
pen goto: (x + (lineWidth * 2))@
(y - (lineWidth * 2)).
pen goto: (x - (lineWidth * 2))@
(y - (lineWidth * 2)).
pen place: x@y.].
(dp >= 0) ifTrue: [pen color: colorPositive]
ifFalse: [pen color: colorNegative].
x := x + pointSpace.]
Of course, all this stuff, the meat of the sparkline-generation, is wrapped up in a message called drawPlot. The message belongs to SparkLinePlot, a class that provides storage for variables and some convenience messages.
The point of having a SparkLinePlot class is that you can be both succinct:
plot := SparkLinePlot newWithData #(1 2 3 4 3 2 1 4 4). plot drawPlot.
Or specific:
plot := SparkLinePlot newWithData #(1 2 3 4 3 2 1 4 4). plot height: 10; negativeColor: Color black; lineWidth: 2; pointSpace: 20. plot drawPlot.
One final note: These sparklines are created “in the world”: they just pop into being in the Squeak window alongside your code. However, you can drag them around and stuff, and if you right-click on them, you can export them into various image formats including JPEG and PNG.
