Deprecated
In favour of
chart-unit
A set of native haskell charts.
https://github.com/tonyday567/chart-unit
Version on this page: | 0.1.0.0 |
LTS Haskell 12.26: | 0.7.0.0 |
Stackage Nightly 2018-07-09: | 0.7.0.0 |
Latest on Hackage: | 0.7.0.0 |
BSD-3-Clause licensed by Tony Day
Maintained by [email protected]
This version can be pinned in stack with:
chart-unit-0.1.0.0@sha256:015e966949d1c50942abec0fdbddfd42ffb71ad276518ba3e361a3ba7e898814,2708
Module documentation for 0.1.0.0
- Chart
Depends on 16 packages(full list with versions):
Used by 1 package in lts-7.24(full list with versions):
```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
~~~
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
~~~