I used CairoGraphics to do it. Those that know me are likely not surprised by this. The code is just a big workspace blob, and is somewhat procedural.
One of the challenges I wrestled with was how to turn numbers the numbers into percent increase or decreases. The easy method of just dividing one value by the other gives values that are not linear when drawn graphically. Or put another way, that is twice as fast draws differently than something that is twice as slow.
I used this fun snippet to convert from aTime and bTime values into such a comparison:
(((originalTime max: newTime) /
(originalTime min: newTime)) - 1.0) * (originalTime - newTime) sign
The large workspace blob that works with a variable called percents follows below, I put comment annotations to delineate the various sections. It may serve as a useful Cairo snippet.
| max min surface bandHeight xInc text extents y |
"compute some dimensions and create the surface"
max := (percents fold: [:a :b | a max: b]) ceiling max: 0.
min := (percents fold: [:a :b | a min: b]) floor min: 0.
surfaceHeight := (max - min) * 75 min: 300.
bandHeight := surfaceHeight / (max - min).
surface := ImageSurface format: CairoFormat argb32
extent: 600 @ surfaceHeight.
xInc := surface width / percents size.
surface newCairoContextWhile:
[:cr | | matrix |
"setup a default font"
cr
selectFontFace: 'Arial'
slant: FontSlant normal
weight: FontWeight bold.
"flip the coordinate system so we draw from the bottom up, rather than normal y is down style"
cr fontSize: 11.
cr translate: 0 @ surface height.
cr scale: 1 @ -1.
matrix := cr fontMatrix.
matrix scale: 1 @ -1.
cr fontMatrix: matrix.
"do rounded clip/border"
cr rectangle: (Point zero corner: surface extent) fillet: 10.
cr clipPreserve.
cr source: ColorValue black.
cr fill.
"shift up to base line"
cr translate: 0 @ min * bandHeight negated.
"draw columns"
percents keysAndValuesDo:
[:size :percent |
| rectangle baseHue x |
cr saveWhile:
[cr
rectangle: (Point zero
extent: surface width @ (percent positive ifTrue: [max] ifFalse: [min])
* bandHeight)
regular.
cr clip.
rectangle := Rectangle
left: (size - 1) * xInc + 3
right: size * xInc - 3
top: 5
bottom: -5.
percent > 0
ifTrue: [rectangle top: percent * bandHeight]
ifFalse: [rectangle bottom: percent * bandHeight].
cr rectangle: rectangle regular fillet: 5.
baseHue := (1 / 6 + (percent / 3) min: 1 / 3) max: 0.
cr source: (ColorValue hue: baseHue saturation: 1 brightness: 0.85).
cr fillPreserve.
cr source: (ColorValue hue: baseHue saturation: 0.85 brightness: 1).
cr strokeWidth: 4.
cr stroke].
"draw the column label"
text := size printString.
size = 1 ifTrue: [text := 'size=' , text].
extents := cr textExtents: text.
x := (size - 0.5) * xInc - extents width half.
y := percent positive ifTrue: [extents height negated - 2] ifFalse: [2].
y := y max: min * bandHeight + 2.
cr moveTo: x @ y.
cr source: ColorValue white.
cr showText: text].
"draw axis lines"
min to: max
by: 1 / 2
do:
[:n |
cr moveTo: 0 @ (n * bandHeight).
cr lineTo: surface width @ (n * bandHeight)].
cr source: ColorValue white.
cr strokeWidth: 0.25.
cr stroke.
"draw axis"
cr source: ColorValue white.
min to: max
do:
[:n |
n isZero
ifFalse:
[text := (n + n sign) printString , 'x'.
extents := cr textExtents: text.
y := ((n * bandHeight max: min * bandHeight + 4 + extents height half)
min: max * bandHeight - 4 - extents height half) - extents height half.
cr moveTo: 4 @ y.
cr showText: text.
cr moveTo: (surface width - 4 - extents width) @ y.
cr showText: text]].
cr moveTo: Point zero.
cr lineTo: surface width @ 0.
cr strokeWidth: 1.
cr stroke.
"draw label"
text := '#at:put: - Chain vs CompactDictionary'.
extents := cr textExtents: text.
cr moveTo: (surface width half - extents width half)
@ (max * bandHeight - 4 - extents height).
cr showText: text].
"write the file"
surface writeToPng: 'chart.png'
No comments:
Post a Comment