prettychart-0.2.2.0: src/Prettychart/Charts.hs
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
-- | Various common chart patterns.
module Prettychart.Charts
( simpleLineChart,
xify,
yify,
timeXAxis,
titles3,
histChart,
scatterChart,
blendMidLineStyles,
quantileNames,
quantileChart,
digitChart,
quantileHistChart,
digitSurfaceChart,
)
where
import Chart hiding (abs)
import Data.Bifunctor
import Data.Foldable
import Data.Map.Strict qualified as Map
import Data.Maybe
import Data.Text (Text)
import Data.Time (UTCTime (..))
import NumHask.Space
import Optics.Core
-- $setup
--
-- >>> :set -Wno-type-defaults
-- >>> import Chart
-- >>> import Prettychart.Charts
-- >>> import Data.Text (pack, Text)
-- >>> import qualified Data.Text as Text
-- >>> import qualified Data.Text.IO as Text
-- | convert from [a] to [Point a], by adding the index as the x axis
--
-- >>> xify [1..3]
-- [Point 0.0 1.0,Point 1.0 2.0,Point 2.0 3.0]
xify :: [Double] -> [Point Double]
xify ys =
zipWith Point [0 ..] ys
-- | convert from [a] to [Point a], by adding the index as the y axis
--
-- >>> yify [1..3]
-- [Point 1.0 0.0,Point 2.0 1.0,Point 3.0 2.0]
yify :: [Double] -> [Point Double]
yify xs =
zipWith Point xs [0 ..]
-- | interpret a [Double] as a line with x coordinates of [0..]
simpleLineChart :: Double -> Colour -> [Double] -> Chart
simpleLineChart w c xs =
LineChart
(defaultLineStyle & #color .~ c & #size .~ w)
[xify xs]
-- | Create a hud that has time as the x-axis, based on supplied UTCTime list.
timeXAxis :: Int -> [UTCTime] -> AxisOptions
timeXAxis nticks ds =
defaultXAxisOptions
& #ticks
% #tick
.~ TickPlaced
(first (* fromIntegral (length ds)) <$> placedTimeLabelContinuous PosInnerOnly Nothing nticks (unsafeSpace1 ds))
-- | common pattern of chart title, x-axis title and y-axis title
titles3 :: Double -> (Text, Text, Text) -> [Priority TitleOptions]
titles3 p (t, x, y) =
[ Priority p (defaultTitleOptions t & #style % #size .~ 0.08),
Priority p (defaultTitleOptions x & #place .~ PlaceBottom & #style % #size .~ 0.05),
Priority p (defaultTitleOptions y & #place .~ PlaceLeft & #style % #size .~ 0.05)
]
-- | histogram chart
histChart ::
Range Double ->
Int ->
[Double] ->
ChartOptions
histChart r g xs =
mempty
& #chartTree
.~ named "histogram" [RectChart defaultRectStyle rects]
& #hudOptions
% #axes
.~ [Priority 5 (defaultXAxisOptions & #ticks % #lineTick .~ Nothing & #ticks % #tick .~ TickRound (FormatN FSCommaPrec (Just 2) 4 True True) 5 NoTickExtend)]
& #hudOptions
% #frames
.~ [Priority 20 (defaultFrameOptions & #buffer .~ 0.05)]
where
hcuts = gridSensible OuterPos False r g
h = fill hcuts xs
rects =
filter (\(Rect _ _ _ y') -> y' /= 0) $
makeRects (IncludeOvers (NumHask.Space.width r / fromIntegral g)) h
-- | scatter chart
scatterChart ::
[[Point Double]] ->
[Chart]
scatterChart xss = zipWith (\(s, sh) ps -> GlyphChart (s & set #glyphShape sh) ps) (gpaletteStyle 0.04 0.01) xss
-- | GlyphStyle palette
gpaletteStyle :: Double -> Double -> [(Style, GlyphShape)]
gpaletteStyle s bs = zipWith (\c g -> (defaultGlyphStyle & #size .~ s & #color .~ palette c & #glyphShape .~ g & #borderSize .~ bs, g)) [0 ..] (gpalette <$> [0 .. 8])
-- | Chart template for quantiles.
quantileChart ::
[Text] ->
[Style] ->
[[Double]] ->
ChartOptions
quantileChart names ls xs = mempty & #hudOptions .~ h & #chartTree .~ unnamed c
where
h =
defaultHudOptions
& ( #legends
.~ [ Priority 10 $
defaultLegendOptions
& #textStyle
% #size
.~ 0.1
& #vgap
.~ 0.05
& #innerPad
.~ 0.2
& #place
.~ PlaceRight
& #legendCharts
.~ zip names ((: []) <$> c)
]
)
c =
zipWith
(\l x -> LineChart l [x])
ls
(zipWith Point [0 ..] <$> xs)
-- | Format quantile-style numbers
--
-- >>> quantileNames [0.01, 0.5, 0.99]
-- ["1%","50%","99%"]
quantileNames :: (Functor f) => f Double -> f Text
quantileNames qs = percent commaSF Nothing <$> qs
-- | @blendMidLineStyle n w@ produces n lines of width w interpolated between two colors.
blendMidLineStyles :: Int -> Double -> (Colour, Colour) -> [Style]
blendMidLineStyles l w (c1, c2) = lo
where
m = (fromIntegral l - 1) / 2 :: Double
cs = (\x -> 1 - abs (fromIntegral x - m) / m) <$> [0 .. (l - 1)]
bs = (\x -> mix x c1 c2) <$> cs
lo = (\c -> defaultLineStyle & #size .~ w & #color .~ c) <$> bs
-- | A histogram based on quantile information
quantileHistChart ::
-- | quantile names
Maybe [Text] ->
-- | quantiles
[Double] ->
-- | quantile values
[Double] ->
ChartOptions
quantileHistChart names qs vs = mempty & #chartTree .~ unnamed [chart'] & #hudOptions .~ hudOptions
where
hudOptions =
defaultHudOptions
& #axes
.~ [ Priority 5 $
maybe
( axis0
& #ticks
% #tick
.~ TickRound (FormatN FSDecimal (Just 3) 4 True True) 6 TickExtend
)
( \x ->
axis0
& #ticks
% #tick
.~ TickPlaced (zip vs x)
)
names
]
axis0 = defaultXAxisOptions & #ticks % #lineTick .~ Nothing & set (#ticks % #textTick %? #style % #size) 0.03
chart' = RectChart defaultRectStyle hr
hr =
zipWith
(\(y, w) (x, z) -> Rect x z 0 ((w - y) / (z - x)))
(zip qs (drop 1 qs))
(zip vs (drop 1 vs))
-- | A chart drawing quantiles of a time series
digitChart ::
[UTCTime] ->
[Double] ->
[Text] ->
ChartOptions
digitChart utcs xs labels =
mempty & #chartTree .~ unnamed [c] & #hudOptions .~ hudOptions
where
hudOptions =
defaultHudOptions
& #axes
.~ [Priority 5 (timeXAxis 8 utcs), Priority 5 (decileYAxis labels)]
c =
GlyphChart
( defaultGlyphStyle
& #color
.~ Colour 0 0 1 1
& #size
.~ 0.01
& #glyphShape
.~ CircleGlyph
)
(xify xs)
decileYAxis :: [Text] -> AxisOptions
decileYAxis labels =
defaultYAxisOptions
& #ticks
% #tick
.~ TickPlaced (zip ((+ 0.5) <$> [0 ..]) labels)
& #ticks
% #lineTick
.~ Nothing
& #ticks
% #textTick
%? #style
% #size
.~ 0.03
-- | Surface chart of quantile vs quantile counts
digitSurfaceChart ::
SurfaceStyle ->
SurfaceLegendOptions ->
(Text, Text, Text) ->
[Text] ->
[(Int, Int)] ->
ChartTree
digitSurfaceChart pixelStyle _ ts names ps =
runHudWith one hs0 (unnamed cs1)
where
l = length names - 1
pts = Point l l
gr :: Rect Double
gr = fromIntegral <$> Rect 0 l 0 l
mapCount = foldl' (\m x -> Map.insertWith (+) x 1.0 m) Map.empty ps
f :: Point Double -> Double
f (Point x y) = fromMaybe 0 $ Map.lookup (floor (1 + x), floor (1 + y)) mapCount
(_, hs0) = toHuds (qvqHud ts names) gr
(cs1, _) =
surfacef
f
(SurfaceOptions pixelStyle pts gr)
qvqHud :: (Text, Text, Text) -> [Text] -> HudOptions
qvqHud ts labels =
defaultHudOptions
& #titles
.~ titles3 5 ts
& #axes
.~ ( Priority 3
<$> [ defaultYAxisOptions
& #ticks
% #tick
.~ TickPlaced (zip [0 ..] labels)
& #ticks
% #lineTick
.~ Nothing
& #ticks
% #textTick
%? #style
% #size
.~ 0.03
& #place
.~ PlaceLeft,
defaultXAxisOptions
& #ticks
% #tick
.~ TickPlaced (zip [0 ..] labels)
& #ticks
% #lineTick
.~ Nothing
& #ticks
% #textTick
%? #style
% #size
.~ 0.03
]
)