packages feed

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

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Charts.Internal.Chart where

import qualified Data.Text as T
import Data.Aeson as A

-- | Valid Column types. Each "data" column also accepts a column header.
-- "Role" columns do not.
--
-- See https://developers.google.com/chart/interactive/docs/roles
data Column =
        NumberColumn T.Text
      | StringColumn T.Text
      | BoolColumn T.Text
      | DateColumn T.Text
      | DateTimeColumn T.Text
      | TimeOfDayColumn T.Text
      | AnnotationColumn
      | AnnotationTextColumn
      | CertaintyColumn
      | EmphasisColumn
      | IntervalColumn
      | ScopeColumn
      | StyleColumn
      | TooltipColumn
      | DomainColumn
    -- ^ This is typically inferred and explicitly
      | DataColumn
    -- ^ This is typically inferred and explicitly
  deriving (Show, Eq)

-- Types from https://developers.google.com/chart/interactive/docs/reference#DataTable_addColumn
instance ToJSON Column where
  toJSON col =
              case col of
                  StringColumn a -> object [ "label" .= a, "type" ~= "string"]
                  NumberColumn a -> object [ "label" .= a, "type" ~= "number"]
                  BoolColumn a -> object [ "label" .= a, "type" ~= "boolean"]
                  DateColumn a -> object [ "label" .= a, "type" ~= "date"]
                  DateTimeColumn a -> object [ "label" .= a, "type" ~= "datetime"]
                  TimeOfDayColumn a -> object [ "label" .= a, "type" ~= "timeofday"]
                  AnnotationColumn -> object ["role" ~= "annotation"]
                  AnnotationTextColumn -> object ["role" ~= "annotationText"]
                  CertaintyColumn -> object ["role" ~= "certainty"]
                  EmphasisColumn -> object ["role" ~= "emphasis"]
                  IntervalColumn -> object ["role" ~= "interval"]
                  ScopeColumn -> object ["role" ~= "scope"]
                  StyleColumn -> object ["role" ~= "style"]
                  TooltipColumn -> object ["role" ~= "tooltip"]
                  DomainColumn -> object ["role" ~= "domain"]
                  DataColumn -> object ["role" ~= "data"]
    where
      (~=):: T.Text -> T.Text -> (T.Text, Value)
      (~=) = (.=)

-- | Supported chart types
-- See https://developers.google.com/chart/interactive/docs/gallery
data ChartStyle = LineChart
                | Histogram
                | BarChart
                | ColumnChart
                | ScatterChart
                | AreaChart
                | PieChart
                | BubbleChart
                | SteppedAreaChart
                | CandlestickChart
                  deriving (Show, Eq, Ord)

instance ToJSON ChartStyle where
  toJSON = \case
    LineChart -> "line"
    Histogram -> "histogram"
    BarChart -> "bar"
    ColumnChart -> "column"
    ScatterChart -> "scatter"
    AreaChart -> "area"
    PieChart -> "pie"
    BubbleChart -> "bubble"
    SteppedAreaChart -> "steppedarea"
    CandlestickChart -> "candlestick"

-- | I plan to make this typesafe for each partiular chart type
-- but that's a LOT of work, so for now it's just free-form, if you'd actually
-- use this, please make an issue on Github so I know folks need this behaviour :)
--
-- Find your chart in the chart gallery to see which options it will accept.
--
-- https://developers.google.com/chart/interactive/docs/gallery
data ChartOptions = ChartOptions Value

instance ToJSON ChartOptions where
  toJSON (ChartOptions v) = v

-- | Empty chart options
defaultChartOptions :: ChartOptions
defaultChartOptions = ChartOptions (object [])

-- | The primary chart type. Build using 'buildChart'.
data Chart =
    Chart { options :: ChartOptions
          , style :: ChartStyle
          , columns :: [Column]
          , dataTable :: [[Value]]
          , dynamic :: Bool
          }

instance ToJSON Chart where
  toJSON (Chart{..}) =
      object [ "rows" .= dataTable
             , "options" .= options
             , "columns" .= columns
             , "style" .= style
             , "dynamic" .= dynamic
             ]

-- | Construct a chart.
--
-- e.g.
--
-- @
-- buildChart defaultChartOptions BarChart
--   [StringColumn "Year", NumberColumn "Population"]
--   [ [ String "2004", Number 1000 ]
--   , [ String "2005", Number 1170 ]
--   , [ String "2006", Number 660 ]
--   , [ String "2007", Number 1030 ]
--   ]
-- @
buildChart :: ChartOptions -> ChartStyle -> [Column] -> [[Value]] ->  Chart
buildChart opts style columns vals = Chart opts style columns vals True