```include other/header.md ``` [chart-unit](https://tonyday567.github.io/chart-unit.html) [![Build Status](https://travis-ci.org/tonyday567/chart-unit.png)](https://travis-ci.org/tonyday567/chart-unit) === scratchpad --- My newest chart `padsvg $ linesXY def [[(0,0),(1,1)],[(0,0),(1,2)]]` ![](other/scratchpad.svg) This slowly growing collection of charts: - renders nicely over a wide chart size range, svg and png formats. - render similarly at different scale - are opinionated minimalism - are unit shapes in the spirit of the [diagrams](http://projects.haskell.org/diagrams/doc/quickstart.html) design space. - can be quickly integrated into ad-hoc haskell data analytics, providing a visual feedback loop. charts --- Scatter ![](other/scatter.svg) Histogram ![](other/hist.svg) Line ![](other/line.svg) Lines ![](other/lines.svg) Labelled Bar Chart ![](other/bar.svg) rasterific png renders --- ![](other/scratchpad.png) Scatter ![](other/scatter.png) Histogram ![](other/hist.png) Line ![](other/line.png) Lines ![](other/lines.png) Labelled Bar Chart ![](other/bar.png) > {-# OPTIONS_GHC -Wall #-} > {-# OPTIONS_GHC -fno-warn-type-defaults #-} > {-# OPTIONS_GHC -fno-warn-missing-signatures #-} > import Protolude > import Control.Monad.Primitive (unsafeInlineIO) > import Diagrams.Prelude hiding ((<>)) > import qualified Control.Foldl as L > import qualified Data.Random as R > import qualified Data.Map.Strict as Map > import qualified Data.Text as Text > > import Chart.Unit some test data --- Standard normal random variates. Called ys to distinguish from the horizontal axis of the chart (xs) which are often implicitly [0..] > ys :: Int -> IO [Double] > ys n = > replicateM n $ R.runRVar R.stdNormal R.StdRandom > A bunch of ys, accumulated. > yss :: (Int, Int) -> [[Double]] > yss (n,m) = unsafeInlineIO $ do > yss' <- replicateM m $ ys n > pure $ (drop 1 . L.scan L.sum) <$> yss' > xys is a list of X,Y pairs, correlated normal random variates to add some shape to chart examples. > rXYs :: Int -> Double -> [(Double,Double)] > rXYs n c = unsafeInlineIO $ do > s0 <- replicateM n $ R.runRVar R.stdNormal R.StdRandom > s1 <- replicateM n $ R.runRVar R.stdNormal R.StdRandom > let s1' = zipWith (\x y -> c * x + sqrt (1 - c * c) * y) s0 s1 > pure $ zip s0 s1' > > xys = rXYs 1000 0.8 > XY random walk > rwxy = L.scan (L.Fold (\(x,y) (x',y') -> (x+x',y+y')) (0.0,0.0) identity) (take 100 xys) > xysHist is a histogram of 10000 one-dim random normals. The data out is a (X,Y) pair list, with mid-point of the bucket as X, and bucket count as Y. > xysHist :: [(Double,Double)] > xysHist = unsafeInlineIO $ do > ys' <- replicateM 10000 $ R.runRVar R.stdNormal R.StdRandom :: IO [Double] > let (f,s,n) = mkTicks' (range1D ys') 100 > let cuts = (\x -> f+s*fromIntegral x) <$> [0..n] > let mids = (+(s/2)) <$> cuts > let count = L.Fold (\x a -> Map.insertWith (+) a 1 x) Map.empty identity > let countBool = L.Fold (\x a -> x + if a then 1 else 0) 0 identity > let histMap = L.fold count $ (\x -> L.fold countBool (fmap (x >) cuts)) <$> ys' > let histList = (\x -> Map.findWithDefault 0 x histMap) <$> [0..n] > return (zip mids (fromIntegral <$> histList)) > Scale Robustness --- xys rendered on the XY plane as dots - a scatter chart with no axes - is invariant to scale. The data could be multiplied by any scalar, and look exactly the same. ![](other/dots.svg) Axes break this scale invariance. Ticks and tick labels can hide this to some extent and look almost the same across scales. ![](other/scatter.svg) This chart will look the same on a data scale change, except for tick magnitudes. main --- > > main :: IO () > main = do See develop section below for my workflow. > padsvg $ > linesXY def [[(0,0),(1,1)],[(0,0),(1,2)]] > fileSvg "other/line.svg" (200,200) $ > (lineXY def rwxy) > filePng "other/line.png" (200,200) $ > (lineXY def rwxy) > fileSvg "other/lines.svg" (200,200) $ > (linesXY def $ zip [0..] <$> yss (1000, 10)) > filePng "other/lines.png" (200,200) $ > (linesXY def $ zip [0..] <$> yss (1000, 10)) > fileSvg "other/dots.svg" (200,200) $ > (scatter def xys) > filePng "other/dots.png" (200,200) $ > (scatter def xys) > fileSvg "other/scatter.svg" (200,200) $ > (scatterXY def xys) > filePng "other/scatter.png" (200,200) $ > (scatterXY def xys) > fileSvg "other/bar.svg" (200,200) $ > barLabelled def (unsafeInlineIO $ ys 10) (fmap Text.pack <$> take 10 $ (:[]) <$> ['a'..]) > filePng "other/bar.png" (200,200) $ > barLabelled def (unsafeInlineIO $ ys 10) (fmap Text.pack <$> take 10 $ (:[]) <$> ['a'..]) > fileSvg "other/hist.svg" (200,200) $ > barRange def xysHist > filePng "other/hist.png" (200,200) $ > barRange def xysHist diagrams development recipe --- In constructing new `units`: - diagrams go from abstract to concrete - start with the unitSquare: 4 points, 1x1, origin in the center - work out where the origin should be, given the scaling needed. - turn the pointful shape into a Trail - close the Trail into a SVG-like loop - turn the Trail into a QDiagram 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 workflow --- > padsvg :: ChartSvg -> IO () > padsvg t = > fileSvg "other/scratchpad.svg" (400,400) t > > padpng :: ChartPng -> IO () > padpng t = > filePng "other/scratchpad.png" (400,400) t > Create a markdown version of readme.lhs: ~~~ pandoc -f markdown+lhs -t html -i readme.lhs -o index.html ~~~ Then fire up an intero session, and use padq to display coding results on-the-fly, mashing the refresh button on a browser pointed to readme.html. or go for a compilation loop like: ~~~ stack install && readme && pandoc -f markdown+lhs -t html -i readme.lhs -o index.html --mathjax --filter pandoc-include && pandoc -f markdown+lhs -t markdown -i readme.lhs -o readme.md --mathjax --filter pandoc-include ~~~