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) [](https://travis-ci.org/tonyday567/chart-unit)
===
scratchpad
---
My newest chart `padsvg $ linesXY def [[(0,0),(1,1)],[(0,0),(1,2)]]`

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

Histogram

Line

Lines

Labelled Bar Chart

rasterific png renders
---

Scatter

Histogram

Line

Lines

Labelled Bar Chart

> {-# 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.

Axes break this scale invariance. Ticks and tick labels can hide this to some extent and look almost the same across scales.

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) [](https://travis-ci.org/tonyday567/chart-unit)
===
scratchpad
---
My newest chart `padsvg $ linesXY def [[(0,0),(1,1)],[(0,0),(1,2)]]`

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

Histogram

Line

Lines

Labelled Bar Chart

rasterific png renders
---

Scatter

Histogram

Line

Lines

Labelled Bar Chart

> {-# 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.

Axes break this scale invariance. Ticks and tick labels can hide this to some extent and look almost the same across scales.

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
~~~