packages feed

charter-0.1.1.0: src/Charts/Internal/Auto.hs

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
module Charts.Internal.Auto where

import Charts.Internal.Chart
import Generics.OneLiner
import Data.Aeson
import Data.Proxy
import qualified Data.Text as T
import Data.Scientific

-- | Class representing types for which a column type can be inferred.
--
-- You can implement your own instances for this if you like, but generally shouldn't need to.
class ChartColumn a where
  columnHeader :: Proxy a -> Column

instance ChartColumn Int where
  columnHeader _ = NumberColumn ""

instance ChartColumn Float where
  columnHeader _ = NumberColumn ""

instance ChartColumn Double where
  columnHeader _ = NumberColumn ""

instance ChartColumn Scientific where
  columnHeader _ = NumberColumn ""

instance ChartColumn T.Text where
  columnHeader _ = StringColumn ""

instance ChartColumn String where
  columnHeader _ = StringColumn ""

instance ChartColumn Bool where
  columnHeader _ = BoolColumn ""

-- | A constraint representing a type which can be converted into a chart row
--
-- This constraint is satisfied by tuple types containing JSON serializable types.
--
-- E.g. data rows of type @(Text, Int, Float)@ will infer a chart with a String column and two number
-- columns.
--
-- You shouldn't need to implement any instances of this constraint yourself.
type ChartRowAuto a = (ADT a, Constraints a ToJSON)

-- | A constraint that a column-type can be determined for all elements of the provided tuple
-- type.
type ChartRowHeaderAuto a = (Constraints a ChartColumn)


-- | Create a chart from the provided options, style, and data by inferring column header
-- types.
--
-- Prefer 'autoChartWithHeaders' when you know your column headers up front.
--
-- E.g. The following generates a 2-series bar chart with 4 sections, A, B, C, D
--
-- @
-- myAutoChart :: Chart
-- myAutoChart = autoChart defaultChartOptions BarChart myData
--   where
--     myData :: [(T.Text, Float, Int)]
--     myData = [ ("A", 16, 20)
--              , ("B", 11, 23)
--              , ("C", 9, 25)
--              , ("D", 8, 34)
--              ]
-- @
autoChart :: forall row. (ChartRowHeaderAuto row, ChartRowAuto row) => ChartOptions -> ChartStyle -> [row] -> Chart
autoChart opts styl xs@[] = autoChartWithHeaders opts styl [] xs
autoChart opts styl (x:xs) = autoChartWithHeaders opts styl cols (x:xs)
  where
    cols :: [Column]
    cols = gfoldMap @ChartColumn toColumn x
    toColumn :: forall a. ChartColumn a => a -> [Column]
    toColumn _ = [columnHeader (Proxy @a)]

-- | Create a chart from the provided options, style, and data, but use the explicitly
-- provided column headers and types.
--
-- E.g. The following generates a 2-series bar chart with 4 sections, A, B, C, D
--
-- @
-- myAutoChart :: Chart
-- myAutoChart = autoChartWithHeaders defaultChartOptions BarChart headers myData
--   where
--     headers :: [Column]
--     headers = [StringColumn "Series", NumberColumn "Successes", NumberColumn "Inconclusive"]
--     myData :: [(T.Text, Float, Int)]
--     myData = [ ("A", 16, 20)
--              , ("B", 11, 23)
--              , ("C", 9, 25)
--              , ("D", 8, 34)
--              ]
-- @
autoChartWithHeaders :: forall row. (ChartRowAuto row) => ChartOptions -> ChartStyle -> [Column] -> [row] -> Chart
autoChartWithHeaders opts styl cols [] = buildChart opts styl cols []
autoChartWithHeaders opts styl cols (x:xs) = buildChart opts styl cols rows
  where
    rows :: [[Value]]
    rows = rowToJSON <$> (x:xs)
    rowToJSON :: row -> [Value]
    rowToJSON row = gfoldMap @ToJSON singleToRow row
    singleToRow :: forall a. ToJSON a => a -> [Value]
    singleToRow = (:[]) . toJSON