packages feed

chart-unit-0.6.2.0: examples/gallery.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NegativeLiterals #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -Wno-type-defaults #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
{-# OPTIONS_GHC -Wno-unused-top-binds #-}
 
import Chart
import Control.Lens hiding (beside)
import Control.Monad.Primitive (PrimState)
import Data.Generics.Labels ()
import Data.List ((!!), head, zipWith3)
import Data.Time
import Data.Time.Calendar.WeekDate
import qualified Diagrams.Prelude as D
import qualified Diagrams.TwoD.Text
import Formatting
import NumHask.Histogram
import NumHask.Prelude as P
import System.Random.MWC
import System.Random.MWC.Probability

import qualified Data.Text as Text

-- * example data generation
-- Standard normal random variates in one dimension.
rvs :: Gen (PrimState IO) -> Int -> IO [Double]
rvs gen n = samples n standardNormal gen

-- This generates n V2 random variates where the x and y parts are correlated.
rvsCorr :: Gen (PrimState IO) -> Int -> Double -> IO [Pair Double]
rvsCorr gen n c = do
  s0 <- rvs gen n
  s1 <- rvs gen n
  let s1' = zipWith (\x y -> c * x + sqrt (1 - c * c) * y) s0 s1
  pure $ zipWith Pair s0 s1'

-- * a grid
gridExample :: Chart b
gridExample =
  hud
    (def & #grids .~
     [ GridOptions
         Vert
         (GridExact GridOuterPos 10)
         (LineOptions 0.001 (black `withOpacity` 1))
     , GridOptions
         Hori
         (GridExact GridOuterPos 10)
         (LineOptions 0.001 (black `withOpacity` 1))
     ] &
     #axes .
     ix 0 %~
     ((#tickStyle .~
       TickPlaced
         (zip (grid OuterPos (Range -0.5 0.5) 10) (replicate 11 "abcdef"))) .
      (#label . #text . #alignH .~ AlignLeft) .
      (#gap .~ 0) .
      (#label . #text . #rotation .~ -45)) &
     #axes .
     ix 1 %~
     (#label . #text . #alignH .~ AlignLeft))
    sixbyfour
    one

timeData :: Int -> IO [Day]
timeData n = do
  now <- getCurrentTime
  let (UTCTime today _) = now
  let toWeekDay x =
        let (_, _, d) = toWeekDate x
        in d
  let isWeekend x = toWeekDay x `elem` [6, 7]
  pure $ filter (not . isWeekend) $ take n $ (`addDays` today) <$> [0 ..]

-- * dealing with time
timeExample :: [Day] -> Chart b
timeExample dates =
  hud (#axes .~ [adef, defYAxis] $ def) sixbyfour r <>
  glyphChart
    [#color .~ red `withOpacity` 1 $ #borderSize .~ 0 $ #size .~ 0.01 $ def]
    sixbyfour
    r
    [xs'] <>
  lglyphChart
    [def]
    [ #shape .~ Square $ #color .~ blue `withOpacity` 1 $ #borderSize .~ 0 $
      #size .~ 0.04 $ def
    ]
    sixbyfour
    r
    [ zip
        (Text.pack . formatTime defaultTimeLocale "%a, %d %b" .
         (\x -> dates !! x) .
         fst <$>
         ts)
        ((\x -> xs' !! x) . fst <$> ts)
    ]
  where
    today = Data.List.head dates
    g = 6
    xs = fromIntegral . (`diffDays` today) <$> dates
    xs' = lineOneD xs
    r = range [xs']
    (Ranges rx _) = r
    (ts, _) =
      placedTimeLabelDiscontinuous
        PosInnerOnly
        Nothing
        g
        ((`UTCTime` 0) <$> dates)
    ts' = (\(x, y) -> (fromIntegral x, y)) <$> ts
    (Ranges aspx _) = sixbyfour
    adef = adjustAxis def aspx rx $ #tickStyle .~ TickPlaced ts' $ defXAxis

-- * scatter chart
mkScatterData :: IO [[Pair Double]]
mkScatterData = do
  g <- create
  xys <- rvsCorr g 1000 0.7
  xys1 <- rvsCorr g 1000 -0.5
  pure
    [ (\(Pair x y) -> Pair (x ^^ 2 + 3 * x - 1) (y + 1)) <$> xys
    , (\(Pair x y) -> Pair (x ^^ 2 + 3 * x + 1) y) <$> xys1
    ]

scatterHistExample :: [[Pair Double]] -> Chart b
scatterHistExample xys =
  D.beside
    (D.r2 (1, 0))
    (D.beside (D.r2 (0, -1)) (sc1 <> hud1) (D.reflectY histx))
    (D.reflectY $ D.rotateBy (3 / 4) histy)
  where
    sopts =
      zipWith3
        (\x y z -> GlyphOptions x y (ucolor 0 0 0 0) 0 z)
        [0.01, 0.02, 0.03]
        ((\x -> withOpacity (d3Colors1 x) 0.3) <$> [6, 8])
        [Circle, Triangle, Square]
    mainAspect = Rect -0.5 0.5 -0.5 0.5
    minorAspect = Rect -0.5 0.5 -0.1 0.1
    sc1 = glyphChart_ sopts mainAspect xys
    histx = rectChart_ defHist minorAspect hx
    histy = rectChart_ defHist minorAspect hy
    hud1 =
      hud
        (#axes .~ [#place .~ PlaceTop $ #label . #orientation .~ Pair 0 1 $ def] $
         def)
        mainAspect
        (range xys)
    defHist =
      (\x -> #borderSize .~ 0 $ #color .~ d3Colors1 x `withOpacity` 0.5 $ def) <$>
      [6, 8]
    makeHist n = makeRects IgnoreOvers . regular n
    hx = makeHist 50 . fmap (view D._x) <$> xys
    hy = makeHist 50 . fmap (view D._y) <$> xys

-- * haskell survey example
data SurveyQ = SurveyQ
  { surveyTitle :: Text
  , surveyData :: [(Text, Int)]
  , surveyBarGap :: Double
  , surveyNumberDrop :: Double
  , surveyBarColor :: AlphaColour Double
  , surveyNumberColor :: AlphaColour Double
  , surveyAutoOptions :: AutoOptions
  } deriving (Show, Generic)

q7 :: SurveyQ
q7 =
  SurveyQ
    "How frequently do you use Haskell?"
    [ ("Daily", 469)
    , ("Weekly", 452)
    , ("Monthly", 215)
    , ("Yearly", 36)
    , ("Rarely", 51)
    ]
    0.2
    0.07
    (ucolor 0.341 0.224 0.388 1)
    (ucolor 1 1 0.33 1)
    (#allowDiagonal .~ False $ #maxXRatio .~ 0.16 $ def)
 
q24 :: SurveyQ
q24 =
  SurveyQ
    "Which editors do you use for Haskell?"
    [ ("Vim", 534)
    , ("Emacs", 501)
    , ("VS Code", 202)
    , ("Atom", 169)
    , ("Sublime", 92)
    , ("Notepad++", 28)
    , ("VS", 3)
    , ("Other", 134)
    ]
    0.2
    (-0.03)
    (ucolor 0.341 0.224 0.388 1)
    (ucolor 0.33 0.33 0.33 1)
    def

surveyChart :: SurveyQ -> Chart b
surveyChart (SurveyQ t d bgap ngap bc tc ao) =
  surveyText tc ngap (snd <$> d) <> surveyBars bc bgap (snd <$> d) <>
  surveyHud ao t d

surveyBars :: AlphaColour Double -> Double -> [Int] -> Chart b
surveyBars rc gap d =
  rectChart
    [#borderSize .~ 0 $ #color .~ rc $ def]
    sixbyfour
    (barRange [fromIntegral <$> d])
    [rectBars gap $ fromIntegral <$> d]

surveyHud :: AutoOptions -> Text -> [(Text, Int)] -> Chart b
surveyHud ao t d =
  hud
    (#outerPad .~ 1 $ #titles .~ [(def, t)] $ #axes .~
     [ #tickStyle .~ TickRound 4 $ defYAxis
     , adjustAxis ao aspx rx $ #gap .~ 0 $ #tickStyle .~ TickLabels (fst <$> d) $
       defXAxis
     ] $
     def)
    sixbyfour
    r
  where
    r = barRange [fromIntegral . snd <$> d]
    (Ranges rx _) = r
    (Ranges aspx _) = sixbyfour

surveyText :: AlphaColour Double -> Double -> [Int] -> Chart b
surveyText tc gap ys =
  textChart
    (repeat (#color .~ tc $ def))
    sixbyfour
    (barRange [fromIntegral <$> ys])
    [ zipWith
        (\x y ->
           ( show y
           , Pair
               (x + 0.5)
               ((if y > 0
                   then -ngap
                   else ngap) +
                fromIntegral y)))
        [0 ..]
        ys
    ]
  where
    ngap = gap * fromIntegral (maximum ys) :: Double

-- * one-dim chart example
makeQuantiles :: Double -> IO [Double]
makeQuantiles n = do
  g <- create
  xs <- rvs g 100000
  pure (regularQuantiles n xs)

skinnyExample :: [Double] -> [Double] -> Chart b
skinnyExample qs qs' = hud' <> ticks' <> labels'
  where
    r = Ranges (space qs) (Range 0 0.2)
    hud' =
      hud
      (HudOptions
        1.1
        [#label . #text . #size .~ 0.25 $ def]
        []
        []
        []
        clear)
      skinny
      r
    labels' =
      textChart
      [#alignH .~ AlignLeft $ #rotation .~ 45 $ #size .~ 0.2 $ def]
      skinny
      r
      [ zipWith
        (\x y -> (x, Pair y 0.05))
        ["min", "3rd Q", "median", "1st Q", "max"]
        qs'
      ]
    ticks' = glyphChart [def] skinny r [(`Pair` 0.02) <$> qs]

-- * comparing histograms
makeHistDiffExample :: IO ([Rect Double], [Rect Double])
makeHistDiffExample = do
  g <- create
  xys <- rvs g 1000
  xys1 <- rvs g 1000
  let cuts = grid OuterPos (Range -5.0 5.0) 50
  pure
    ( makeRects IgnoreOvers (fill cuts xys)
    , makeRects IgnoreOvers (fill cuts ((1.5 *) <$> xys1)))

histDiffExample :: ([Rect Double], [Rect Double]) -> Chart b
histDiffExample (h1, h2) =
  let deltah =
        zipWith (\(Rect x y z w) (Rect _ _ _ w') -> Rect x y z (w - w')) h1 h2
      mainAspect = Rect -0.75 0.75 -0.5 0.5
      botAspect = Rect -0.75 0.75 -0.2 0.2
      (Ranges rx ry) = fold $ fold [h1, h2]
      (Ranges _ deltary) = fold (abs <$> deltah)
  in D.pad 1.1 $
     D.beside
       (D.r2 (0, -1))
       (rectChart
          [ #borderColor .~ ucolor 0 0 0 0 $
            #color .~ ucolor 0.365 0.647 0.855 0.2 $
            def
          , #borderColor .~ ucolor 0 0 0 0 $
            #color .~ ucolor 0.88 0.53 0.23 0.8 $
            def
          ]
          mainAspect
          (Ranges rx ry)
          [h1, h2])
       (rectChart
          [ #borderColor .~ ucolor 0 0 0 0 $
            #color .~ ucolor 0.88 0.53 0.23 0.8 $
            def
          ]
          botAspect
          (Ranges rx deltary)
          [deltah]) <>
        hud def botAspect (Ranges rx deltary)

-- * clipping
clip :: Rect Double -> Chart b -> Chart b
clip (Rect xl xu yl yu) c =
  D.clipped
    (D.pathFromLocTrail $ D.moveTo (D.p2 (xl, yl)) $ D.scaleY (yu - yl) $
     D.scaleX (xu - xl) $
     D.moveOriginTo (D.p2 (-0.5, -0.5)) D.unitSquare)
    c

grp :: Int -> [a] -> [[a]]
grp n =
  unfoldr
    (\x ->
       let y = splitAt n x
       in if null (fst y)
            then Nothing
            else Just y)

-- | chop a chart extent into a double list of Rects
chop ::
  ( D.Renderable (D.Path D.V2 Double) b
  , D.Renderable (Diagrams.TwoD.Text.Text Double) b) =>
  Pair Int -> Chart b -> [[Rect Double]]
chop p@(Pair _ n) ch = grp n $ gridSpace (Rect xl xu yl yu) p
  where
    (xl, xu) = fromMaybe (-0.5, 0.5) (D.extentX ch)
    (yl, yu) = fromMaybe (-0.5, 0.5) (D.extentY ch)

clippingExample ::
     RectOptions
  -> Double
  -> Int
  -> Chart b
  -> Chart b
clippingExample rcfg p n ch =
  stack (Pair 0 1) (D.pad p . D.centerXY) $
  hori (\a -> D.pad p $ bound rcfg 1 $ D.centerXY $ clip a ch) <$>
  chop (Pair n n) ch

-- * schoolbook
schoolbookHud :: Chart b
schoolbookHud =
  hud
    (#axes .~ [] $
     #titles .~ [(def, "y = x² - 3")] $
     #grids .~
     [ GridOptions
         Vert
         (GridExact GridOuterPos 10)
         (LineOptions 0.005 schoolBlue)
     , GridOptions
         Hori
         (GridExact GridOuterPos 10)
         (LineOptions 0.005 schoolBlue)
     , GridOptions
         Vert
         (GridExact GridOuterPos 50)
         (LineOptions 0.002 schoolBlue)
     , GridOptions
         Hori
         (GridExact GridOuterPos 50)
         (LineOptions 0.002 schoolBlue)
     ] $
     def)
    asquare
    (Rect -5 5 -5 5)
  where
    schoolBlue = ucolor 0.19 0.74 0.89 0.7

parabola :: Rect Double -> (Double -> Double) -> Int -> Range Double -> Chart b
parabola r f grain xscope =
  lineChart
    [#size .~ 0.01 $ #color .~ ucolor 0.6 0.6 0.6 1 $ def]
    asquare
    r
    [dataXY f xscope grain]

ceptLines ::
    Rect Double
  -> Rect Double
  -> (Double -> Double)
  -> Double
  -> Chart b
ceptLines asp r@(Ranges rx ry) f x =
  mconcat $ lines (#color .~ ucolor 0.2 0.2 0.2 1 $ #size .~ 0.005 $ def) .
  fmap (Chart.project r asp) <$>
  [[Pair (lower rx) (f x), Pair x (f x)], [Pair x (lower ry), Pair x (f x)]]

cepts ::
    Rect Double
  -> Rect Double
  -> (Double -> Double)
  -> Double
  -> Chart b
cepts a r@(Ranges rx ry) f x =
  textChart
    [def, #alignH .~ AlignCenter $ #rotation .~ 0 $ def]
    a
    r
    [ [("x = " <> sformat (fixed 1) x, Pair x (lower ry - 1))]
    , [("y = " <> sformat (fixed 1) (f x), Pair (lower rx - 1.5) (f x))]
    ]

schoolbookExample :: Double -> Chart b
schoolbookExample x =
  bound (#color .~ ucolor 1 1 1 0.1 $ def) 1.05 $ schoolbookHud <>
  parabola r f grain xscope <>
  ceptLines asquare r f x <>
  glyphChart [#color .~ red `withOpacity` 0.5 $ def] asquare r [[Pair x (f x)]] <>
  cepts asquare r f x
  where
    f x = x * x - 3
    r = Rect -5 5 -5 5
    xscope = Range -3 3
    grain = 50

main :: IO ()
main = do
  putStrLn ("gridExample" :: Text)
  fileSvg "other/gridExample.svg" def gridExample
  ts <- timeData 200
  putStrLn ("timeExample" :: Text)
  fileSvg "other/timeExample.svg" def $ timeExample ts
  xys <- mkScatterData
  putStrLn ("scatterHistExample" :: Text)
  fileSvg "other/scatterHistExample.svg" def (scatterHistExample xys)
  putStrLn ("skinnyExample" :: Text)
  qs <- makeQuantiles 20
  qs' <- makeQuantiles 4
  fileSvg "other/skinnyExample.svg" (#size .~ Pair 600 150 $ def) $
    skinnyExample qs qs'
  putStrLn ("histDiffExample" :: Text)
  hs <- makeHistDiffExample
  fileSvg "other/histDiffExample.svg" (#size .~ Pair 600 600 $ def) $
    histDiffExample hs
  putStrLn ("clippingExample" :: Text)
  fileSvg "other/clippingExample.svg" (#size .~ Pair 600 600 $ def) $
    clippingExample
      (#color .~ ucolor 0.3 0.3 0.3 0.1 $ def)
      1.1
      5
      (schoolbookExample -1)
  putStrLn ("schoolbookExample" :: Text)
  fileSvg "other/schoolbookExample.svg" (#size .~ Pair 400 400 $ def)
    (schoolbookExample -1)
  putStrLn ("haskell survey examples" :: Text)
  fileSvg "other/q7Example.svg" def $ surveyChart q7
  fileSvg "other/q24Example.svg" def $ surveyChart q24