scratch $ pad 1.1 $ beside (r2 (-1,0)) (beside (r2 (0,-1)) (((unitSquare # fcA (color $ Color 0.2 0.2 0.2 0.05) # lw 0) <> (centerXY $ scaleX (1/1.5) $ scaleY (1/10) $ (clipped (pathFromTrail $ (closeTrail $ fromVertices (p2 <$> [(0.5,0),(0.5,10),(2,10),(2,0)]))) $ ((mconcat . zipWith line1 (zipWith LineConfig [0.01,0.02,0.03] (opacs 0.5 palette1)))) (fmap r2 <$> [ [(0.0,1.0),(1.0,1.0),(2.0,5.0)], [(0.0,0.0),(3.0,3.0)]]))))) (axis def (Range (0.5,2)))) (axis (axisOrientation .~ Y $ def) (Range (0,2)))
This is my manual hack of what should happen to the line chart example when drawn using a scale different to the data scale.
Next step is to refactor back into the chartHub
This slowly growing collection of charts:
Labelled Bar Chart
Most of the api can be seen in the chartWith sig:
chartWith :: (Traversable f, Traversable g, R2 r) => ChartConfig -> (g (f (r Double)) -> Chart b) -- double-containered R? chart renderer -> V2 (Range Double) -- render range -> (V2 (Range Double) -> g (f (r Double)) -> g (f (r Double))) -- scaler -> g (f (r Double)) -- data -> Chart b chartWith (ChartConfig p axes) renderer range' scaler ms =
Double-containered dimensioned data covers what a chart charts - one or more data sets with at least an x and y dimension (called R2 in the linear library).
Most chart variations are about what to do with the extra dimensions in the data. A rectangle, for example, is built from 4 dimensions, an anchoring of position in XY space, with a width (W) and height (Z). A contour map is three dimensional data (V3 in linear), with placement as XY and color as Z.
A range represents boundaries of a space. Diagrams often uses unit to refer to a
V2 (Range (-0.5,0.5)) (Range (-0.5,0.5)).
A scaler converts data to other ranges, and helps get everything on the same page (literally).
Starting with the lowest level scatter chart (see examples/examples.hs for complete code):
scatter1 def (xys 1000 0.7)
The size of the dots scale with the data, so to bring it back, we run the data through a scaling routine, which normalises the data according to the diagrams unit, which is
V2 (-0.5,0.5) (-0.5,0.5):
scatter1 def $ ((\x -> scaleR2 (rangeR2 x) x) <$> (xys 1000 0.7))
This scaling ensures that configuration paramters such as dot size, and axis look-n-feel is invariant to data scale. Hiding the scaling and axes creation in a more general function:
scatter def [def] [xys 1000 0.7]
The chart is then robust to a wide range of magnitudes:
scatter def [def] $ fmap (1e-8 *) <$> [xys 1000 0.7]
It's useful to separate a chart into two distinct bits:
the Chart. A concrete manifestation of data on the XY plane, with extra dimensions as aethestics (color, size, time, 1D or 3D projections into 2D)
the HUD. A Heads-Up-Display aka decoration around the chart designed to help users interpret and navigate the data representation.
The code below, for example, draws an X and Y axis without any data:
chartHud def (const mempty) (V2 (Range (-1e8,1e8)) (Range (-1e-8,1e-8))) rescaleR2s ([] :: [[V2 Double]])
HUDs can be built with chart routines. Here's a grid overlay that can be attached to any unit chart. Note how the axis ticks line up exactly with middle of the dots. I now think of the axes as a small subset of possible HUDs to help users interpret data.
unitScatter def [def] [V2 <$> [0..10] <*> [0..10]]
In constructing new chart units, I keep this list around:
You can slide up and down the various diagrams abstraction levels creating transformations at each level. For example, here's something I use to work at the point level:
unitp f = unitSquare # f # fromVertices # closeTrail # strokeTrail
scratch :: Chart SVG -> IO () scratch = fileSvg "other/scratchpad.svg" (400,400)
I tend to work in ghci a lot, using scratch to try code out, and mashing the refresh button in the browser.
As new charts emerge I then includ ethem in examples and integrate them with readme.md:
stack install && chart-unit-examples && pandoc -f markdown -t html -i readme.md -o index.html --mathjax --filter pandoc-include
Until I can work out a space leak, I'm letting png slide.
-- import Diagrams.Backend.Rasterific (Rasterific) -- scratchPng :: Chart Rasterific -> IO () -- scratchPng = filePng "other/scratchpad.png" (400,400) -- filePng "other/bar.png" s exampleBar