packages feed

chart-svg-0.6.0.0: src/Chart/Compound.hs

{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedStrings #-}

-- | Combine charts that share a common canvas.
module Chart.Compound
  ( runHudCompoundWith,
    addHudCompound,
    compoundMerge,
    writeChartOptionsCompound,
    encodeChartOptionsCompound,
    markupChartOptionsCompound,
  )
where

import Chart.Hud
import Chart.Markup
import Chart.Primitive
import Chart.Style
import Data.Bool
import Data.ByteString.Char8 qualified as C
import Data.Foldable
import Data.List qualified as List
import Data.Maybe
import MarkupParse
import Optics.Core
import Prelude

-- | Write multiple charts to a single file sharing the canvas.
writeChartOptionsCompound :: FilePath -> [ChartOptions] -> IO ()
writeChartOptionsCompound fp cs = C.writeFile fp (encodeChartOptionsCompound cs)

-- | Encode multiple charts.
encodeChartOptionsCompound :: [ChartOptions] -> C.ByteString
encodeChartOptionsCompound [] = mempty
encodeChartOptionsCompound cs@(c0 : _) =
  markdown_ (view (#markupOptions % #renderStyle) c0) Xml (markupChartOptionsCompound cs)

-- | Create Markup representing multiple charts sharing a common canvas.
markupChartOptionsCompound :: [ChartOptions] -> Markup
markupChartOptionsCompound [] = mempty
markupChartOptionsCompound cs@(co0 : _) =
  header
    (view (#markupOptions % #markupHeight) co0)
    viewbox
    ( markupCssOptions (view (#markupOptions % #cssOptions) co0)
        <> markupChartTree ctFinal
    )
  where
    viewbox = view safeStyleBox' ctFinal
    ctFinal =
      projectChartCompoundWith
        (view (#markupOptions % #chartAspect) co0)
        (zip (view #hudOptions <$> cs) (view #chartTree <$> cs))

projectChartCompoundWith :: ChartAspect -> [(HudOptions, ChartTree)] -> ChartTree
projectChartCompoundWith asp css = ctFinal
  where
    csAndHud = addHudCompound asp css
    viewbox = finalCanvas asp (Just csAndHud)
    ctFinal = set styleBox' (Just viewbox) csAndHud

-- | Merge a list of ChartOptions, treating each element as charts to be merged. Note that this routine mempties the hud options and converts them to charts.
compoundMerge :: [ChartOptions] -> ChartOptions
compoundMerge [] = mempty
compoundMerge cs@(c0 : _) =
  ChartOptions
    (view #markupOptions c0)
    mempty
    (addHudCompound (view (#markupOptions % #chartAspect) c0) (zip (view #hudOptions <$> cs) (view #chartTree <$> cs)))

-- | Decorate a ChartTree with HudOptions, merging the individual hud options.
addHudCompound :: ChartAspect -> [(HudOptions, ChartTree)] -> ChartTree
addHudCompound _ [] = mempty
addHudCompound asp ts@((_, cs0) : _) =
  runHudCompoundWith
    (initialCanvas asp (Just cs0))
    (zip3 dbs' hss css')
  where
    css :: [ChartTree]
    css = snd <$> ts
    hos = fst <$> ts
    dbs = view safeBox' <$> css
    huds = zipWith toHuds hos dbs
    mdbs = fst <$> huds
    hss = snd <$> huds
    dbs' = zipWith fromMaybe dbs mdbs
    css' :: [ChartTree]
    css' = zipWith3 (\cs mdb db -> cs <> maybe mempty (\r -> bool (named "datapadding" [BlankChart defaultStyle [r]]) mempty (r == db)) mdb) css mdbs dbs

-- | Combine a collection of chart trees that share a canvas box.
runHudCompoundWith ::
  -- | initial canvas
  ChartBox ->
  -- | databox-huds-chart tuples representing independent chart trees occupying the same canvas space
  [(DataBox, [Hud], ChartTree)] ->
  -- | integrated chart tree
  ChartTree
runHudCompoundWith cb ts = hss
  where
    hss =
      ts
        & fmap (\(_, hs, _) -> hs)
        & mconcat
        & prioritizeHuds
        & fmap (fmap (view (#phud % #item)))
        & foldl' (\x a -> makeHuds a x) hc0
        & fromHudChart
    css =
      ts
        & fmap (\(db, _, ct) -> over chart' (projectWith cb db) ct)
        & mconcat
    hc0 = HudChart (css & set styleBox' (Just cb)) mempty

prioritizeHuds :: [Hud] -> [[Hud]]
prioritizeHuds hss =
  hss
    & List.sortOn (view (#phud % #priority))
    & List.groupBy (\a b -> view (#phud % #priority) a == view (#phud % #priority) b)