{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
module DataFrame.Display.Web.Chart (
Chart,
chart,
Mark (..),
Channel (..),
FieldType (..),
Agg (..),
mark,
enc,
encAs,
aggregateOn,
binX,
binY,
facet,
row,
column,
layer,
regression,
density,
logScale,
includeZero,
title,
size,
toVegaSpec,
toHtml,
showChart,
scatter,
bar,
histogram,
line,
pie,
box,
) where
import Data.Aeson (Value)
import Data.Function ((&))
import qualified Data.Text as T
import DataFrame.Display.Internal.Common (Agg (..))
import DataFrame.Display.Internal.VegaLite (
Channel (..),
ChannelEnc (..),
FieldType (..),
Mark (..),
ResolvedField (..),
ScaleSpec (..),
ScaleType (..),
Transform (DensityT, RegressionT),
VLSpec (..),
chanEnc,
channelName,
emptySpec,
resolveField,
rowCountWarning,
specHtml,
specToValue,
)
import DataFrame.Display.Web.Plot (showInDefaultBrowser)
import DataFrame.Internal.Column (Columnable)
import DataFrame.Internal.DataFrame (DataFrame)
import DataFrame.Internal.Expression (Expr)
data Chart = Chart
{ Chart -> DataFrame
chDf :: DataFrame
, Chart -> Mark
chMark :: Mark
, Chart -> [ChannelEnc]
chEncs :: [ChannelEnc]
, Chart -> [ResolvedField]
chFields :: [ResolvedField]
, Chart -> [Transform]
chTransforms :: [Transform]
, Chart -> Maybe Text
chTitle :: Maybe T.Text
, Chart -> Int
chW :: Int
, Chart -> Int
chH :: Int
, Chart -> [Chart]
chLayers :: [Chart]
}
chart :: DataFrame -> Chart
chart :: DataFrame -> Chart
chart DataFrame
df = DataFrame
-> Mark
-> [ChannelEnc]
-> [ResolvedField]
-> [Transform]
-> Maybe Text
-> Int
-> Int
-> [Chart]
-> Chart
Chart DataFrame
df Mark
Point [] [] [] Maybe Text
forall a. Maybe a
Nothing Int
600 Int
400 []
mark :: Mark -> Chart -> Chart
mark :: Mark -> Chart -> Chart
mark Mark
m Chart
c = Chart
c{chMark = m}
enc :: (Columnable a) => Channel -> Expr a -> Chart -> Chart
enc :: forall a. Columnable a => Channel -> Expr a -> Chart -> Chart
enc Channel
ch Expr a
e Chart
c =
let rf :: ResolvedField
rf = DataFrame -> Text -> Expr a -> ResolvedField
forall a.
Columnable a =>
DataFrame -> Text -> Expr a -> ResolvedField
resolveField (Chart -> DataFrame
chDf Chart
c) (Channel -> Text
channelName Channel
ch) Expr a
e
ce :: ChannelEnc
ce = Channel -> Text -> FieldType -> ChannelEnc
chanEnc Channel
ch (ResolvedField -> Text
rfName ResolvedField
rf) (ResolvedField -> FieldType
rfType ResolvedField
rf)
in Chart
c{chEncs = setChannel ce (chEncs c), chFields = chFields c ++ [rf]}
encAs :: (Columnable a) => Channel -> Expr a -> FieldType -> Chart -> Chart
encAs :: forall a.
Columnable a =>
Channel -> Expr a -> FieldType -> Chart -> Chart
encAs Channel
ch Expr a
e FieldType
ft Chart
c =
let rf :: ResolvedField
rf = DataFrame -> Text -> Expr a -> ResolvedField
forall a.
Columnable a =>
DataFrame -> Text -> Expr a -> ResolvedField
resolveField (Chart -> DataFrame
chDf Chart
c) (Channel -> Text
channelName Channel
ch) Expr a
e
ce :: ChannelEnc
ce = Channel -> Text -> FieldType -> ChannelEnc
chanEnc Channel
ch (ResolvedField -> Text
rfName ResolvedField
rf) FieldType
ft
in Chart
c{chEncs = setChannel ce (chEncs c), chFields = chFields c ++ [rf]}
aggregateOn :: Channel -> Agg -> Chart -> Chart
aggregateOn :: Channel -> Agg -> Chart -> Chart
aggregateOn Channel
ch Agg
a =
Channel -> (ChannelEnc -> ChannelEnc) -> Chart -> Chart
withChannel
Channel
ch
( \ChannelEnc
e ->
ChannelEnc
e
{ ceAggregate = Just (aggVegaName a)
, ceField = if a == Count then "" else ceField e
}
)
binX, binY :: Chart -> Chart
binX :: Chart -> Chart
binX = Channel -> (ChannelEnc -> ChannelEnc) -> Chart -> Chart
withChannel Channel
X (\ChannelEnc
e -> ChannelEnc
e{ceBin = True})
binY :: Chart -> Chart
binY = Channel -> (ChannelEnc -> ChannelEnc) -> Chart -> Chart
withChannel Channel
Y (\ChannelEnc
e -> ChannelEnc
e{ceBin = True})
logScale :: Channel -> Chart -> Chart
logScale :: Channel -> Chart -> Chart
logScale Channel
ch = Channel -> (ScaleSpec -> ScaleSpec) -> Chart -> Chart
withScale Channel
ch (\ScaleSpec
s -> ScaleSpec
s{scaleType = Just LogS})
includeZero :: Channel -> Bool -> Chart -> Chart
includeZero :: Channel -> Bool -> Chart -> Chart
includeZero Channel
ch Bool
b = Channel -> (ScaleSpec -> ScaleSpec) -> Chart -> Chart
withScale Channel
ch (\ScaleSpec
s -> ScaleSpec
s{scaleZero = Just b})
facet :: (Columnable a) => Expr a -> Chart -> Chart
facet :: forall a. Columnable a => Expr a -> Chart -> Chart
facet = Expr a -> Chart -> Chart
forall a. Columnable a => Expr a -> Chart -> Chart
column
column, row :: (Columnable a) => Expr a -> Chart -> Chart
column :: forall a. Columnable a => Expr a -> Chart -> Chart
column = Channel -> Expr a -> Chart -> Chart
forall a. Columnable a => Channel -> Expr a -> Chart -> Chart
enc Channel
Column
row :: forall a. Columnable a => Expr a -> Chart -> Chart
row = Channel -> Expr a -> Chart -> Chart
forall a. Columnable a => Channel -> Expr a -> Chart -> Chart
enc Channel
Row
title :: T.Text -> Chart -> Chart
title :: Text -> Chart -> Chart
title Text
t Chart
c = Chart
c{chTitle = Just t}
size :: Int -> Int -> Chart -> Chart
size :: Int -> Int -> Chart -> Chart
size Int
w Int
h Chart
c = Chart
c{chW = w, chH = h}
layer :: [Chart] -> Chart
layer :: [Chart] -> Chart
layer [] = [Char] -> Chart
forall a. HasCallStack => [Char] -> a
error [Char]
"DataFrame.Display.Web.Chart.layer: empty layer list"
layer cs :: [Chart]
cs@(Chart
c0 : [Chart]
_) =
Chart
c0
{ chLayers = map (\Chart
c -> Chart
c{chLayers = []}) cs
, chFields = concatMap chFields cs
}
regression :: (Columnable a, Columnable b) => Expr a -> Expr b -> Chart -> Chart
regression :: forall a b.
(Columnable a, Columnable b) =>
Expr a -> Expr b -> Chart -> Chart
regression Expr a
xE Expr b
yE Chart
c =
let rfx :: ResolvedField
rfx = DataFrame -> Text -> Expr a -> ResolvedField
forall a.
Columnable a =>
DataFrame -> Text -> Expr a -> ResolvedField
resolveField (Chart -> DataFrame
chDf Chart
c) Text
"x" Expr a
xE
rfy :: ResolvedField
rfy = DataFrame -> Text -> Expr b -> ResolvedField
forall a.
Columnable a =>
DataFrame -> Text -> Expr a -> ResolvedField
resolveField (Chart -> DataFrame
chDf Chart
c) Text
"y" Expr b
yE
withData :: Chart
withData = Chart
c{chFields = chFields c ++ [rfx, rfy]}
points :: Chart
points = Chart
withData{chLayers = []}
lineLayer :: Chart
lineLayer =
Chart
withData
{ chMark = Line
, chEncs =
[ chanEnc X (rfName rfx) Quantitative
, chanEnc Y (rfName rfy) Quantitative
]
, chTransforms = [RegressionT (rfName rfy) (rfName rfx)]
, chLayers = []
}
in Chart
withData{chLayers = [points, lineLayer]}
density :: (Columnable a) => Expr a -> Chart -> Chart
density :: forall a. Columnable a => Expr a -> Chart -> Chart
density Expr a
e Chart
c =
let rf :: ResolvedField
rf = DataFrame -> Text -> Expr a -> ResolvedField
forall a.
Columnable a =>
DataFrame -> Text -> Expr a -> ResolvedField
resolveField (Chart -> DataFrame
chDf Chart
c) Text
"value" Expr a
e
in Chart
c
{ chFields = chFields c ++ [rf]
, chMark = Area
, chTransforms = chTransforms c ++ [DensityT (rfName rf)]
, chEncs =
[ chanEnc X "value" Quantitative
, chanEnc Y "density" Quantitative
]
}
toVegaSpec :: Chart -> Value
toVegaSpec :: Chart -> Value
toVegaSpec Chart
c = [ResolvedField] -> VLSpec -> Value
specToValue (Chart -> [ResolvedField]
allFields Chart
c) (Chart -> VLSpec
toVLSpec Chart
c)
toHtml :: Chart -> String
toHtml :: Chart -> [Char]
toHtml Chart
c = Text -> [Char]
T.unpack (Text -> [ResolvedField] -> VLSpec -> Text
specHtml Text
"vis" (Chart -> [ResolvedField]
allFields Chart
c) (Chart -> VLSpec
toVLSpec Chart
c))
showChart :: Chart -> IO ()
showChart :: Chart -> IO ()
showChart Chart
c = do
[ResolvedField] -> IO ()
rowCountWarning (Chart -> [ResolvedField]
allFields Chart
c)
[Char] -> IO ()
showInDefaultBrowser (Chart -> [Char]
toHtml Chart
c)
scatter ::
(Columnable a, Columnable b) => Expr a -> Expr b -> DataFrame -> IO ()
scatter :: forall a b.
(Columnable a, Columnable b) =>
Expr a -> Expr b -> DataFrame -> IO ()
scatter Expr a
xE Expr b
yE DataFrame
df =
Chart -> IO ()
showChart
( DataFrame -> Chart
chart DataFrame
df
Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Mark -> Chart -> Chart
mark Mark
Point
Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Channel -> Expr a -> Chart -> Chart
forall a. Columnable a => Channel -> Expr a -> Chart -> Chart
enc Channel
X Expr a
xE
Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Channel -> Expr b -> Chart -> Chart
forall a. Columnable a => Channel -> Expr a -> Chart -> Chart
enc Channel
Y Expr b
yE
Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Channel -> Bool -> Chart -> Chart
includeZero Channel
X Bool
False
Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Channel -> Bool -> Chart -> Chart
includeZero Channel
Y Bool
False
)
bar :: (Columnable a) => Expr a -> DataFrame -> IO ()
bar :: forall a. Columnable a => Expr a -> DataFrame -> IO ()
bar Expr a
xE DataFrame
df = Chart -> IO ()
showChart (DataFrame -> Chart
chart DataFrame
df Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Mark -> Chart -> Chart
mark Mark
Bar Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Channel -> Expr a -> Chart -> Chart
forall a. Columnable a => Channel -> Expr a -> Chart -> Chart
enc Channel
X Expr a
xE Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Channel -> Agg -> Chart -> Chart
aggregateOn Channel
Y Agg
Count)
histogram :: (Columnable a) => Expr a -> DataFrame -> IO ()
histogram :: forall a. Columnable a => Expr a -> DataFrame -> IO ()
histogram Expr a
xE DataFrame
df =
Chart -> IO ()
showChart (DataFrame -> Chart
chart DataFrame
df Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Mark -> Chart -> Chart
mark Mark
Bar Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Channel -> Expr a -> Chart -> Chart
forall a. Columnable a => Channel -> Expr a -> Chart -> Chart
enc Channel
X Expr a
xE Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Chart -> Chart
binX Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Channel -> Agg -> Chart -> Chart
aggregateOn Channel
Y Agg
Count)
line :: (Columnable a, Columnable b) => Expr a -> Expr b -> DataFrame -> IO ()
line :: forall a b.
(Columnable a, Columnable b) =>
Expr a -> Expr b -> DataFrame -> IO ()
line Expr a
xE Expr b
yE DataFrame
df =
Chart -> IO ()
showChart
( DataFrame -> Chart
chart DataFrame
df
Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Mark -> Chart -> Chart
mark Mark
Line
Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Channel -> Expr a -> Chart -> Chart
forall a. Columnable a => Channel -> Expr a -> Chart -> Chart
enc Channel
X Expr a
xE
Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Channel -> Expr b -> Chart -> Chart
forall a. Columnable a => Channel -> Expr a -> Chart -> Chart
enc Channel
Y Expr b
yE
Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Channel -> Bool -> Chart -> Chart
includeZero Channel
X Bool
False
Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Channel -> Bool -> Chart -> Chart
includeZero Channel
Y Bool
False
)
pie :: (Columnable a) => Expr a -> DataFrame -> IO ()
pie :: forall a. Columnable a => Expr a -> DataFrame -> IO ()
pie Expr a
cE DataFrame
df = Chart -> IO ()
showChart (DataFrame -> Chart
chart DataFrame
df Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Mark -> Chart -> Chart
mark Mark
Arc Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Channel -> Expr a -> Chart -> Chart
forall a. Columnable a => Channel -> Expr a -> Chart -> Chart
enc Channel
Color Expr a
cE Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Channel -> Agg -> Chart -> Chart
aggregateOn Channel
Theta Agg
Count)
box :: (Columnable a) => Expr a -> DataFrame -> IO ()
box :: forall a. Columnable a => Expr a -> DataFrame -> IO ()
box Expr a
yE DataFrame
df = Chart -> IO ()
showChart (DataFrame -> Chart
chart DataFrame
df Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Mark -> Chart -> Chart
mark Mark
Boxplot Chart -> (Chart -> Chart) -> Chart
forall a b. a -> (a -> b) -> b
& Channel -> Expr a -> Chart -> Chart
forall a. Columnable a => Channel -> Expr a -> Chart -> Chart
enc Channel
Y Expr a
yE)
setChannel :: ChannelEnc -> [ChannelEnc] -> [ChannelEnc]
setChannel :: ChannelEnc -> [ChannelEnc] -> [ChannelEnc]
setChannel ChannelEnc
ce [ChannelEnc]
encs = (ChannelEnc -> Bool) -> [ChannelEnc] -> [ChannelEnc]
forall a. (a -> Bool) -> [a] -> [a]
filter ((Channel -> Channel -> Bool
forall a. Eq a => a -> a -> Bool
/= ChannelEnc -> Channel
ceChannel ChannelEnc
ce) (Channel -> Bool) -> (ChannelEnc -> Channel) -> ChannelEnc -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ChannelEnc -> Channel
ceChannel) [ChannelEnc]
encs [ChannelEnc] -> [ChannelEnc] -> [ChannelEnc]
forall a. [a] -> [a] -> [a]
++ [ChannelEnc
ce]
withChannel :: Channel -> (ChannelEnc -> ChannelEnc) -> Chart -> Chart
withChannel :: Channel -> (ChannelEnc -> ChannelEnc) -> Chart -> Chart
withChannel Channel
ch ChannelEnc -> ChannelEnc
f Chart
c =
let encs :: [ChannelEnc]
encs = Chart -> [ChannelEnc]
chEncs Chart
c
in if (ChannelEnc -> Bool) -> [ChannelEnc] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any ((Channel -> Channel -> Bool
forall a. Eq a => a -> a -> Bool
== Channel
ch) (Channel -> Bool) -> (ChannelEnc -> Channel) -> ChannelEnc -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ChannelEnc -> Channel
ceChannel) [ChannelEnc]
encs
then Chart
c{chEncs = map (\ChannelEnc
e -> if ChannelEnc -> Channel
ceChannel ChannelEnc
e Channel -> Channel -> Bool
forall a. Eq a => a -> a -> Bool
== Channel
ch then ChannelEnc -> ChannelEnc
f ChannelEnc
e else ChannelEnc
e) encs}
else Chart
c{chEncs = encs ++ [f (chanEnc ch "" Quantitative)]}
withScale :: Channel -> (ScaleSpec -> ScaleSpec) -> Chart -> Chart
withScale :: Channel -> (ScaleSpec -> ScaleSpec) -> Chart -> Chart
withScale Channel
ch ScaleSpec -> ScaleSpec
f = Channel -> (ChannelEnc -> ChannelEnc) -> Chart -> Chart
withChannel Channel
ch (\ChannelEnc
e -> ChannelEnc
e{ceScale = f (ceScale e)})
aggVegaName :: Agg -> T.Text
aggVegaName :: Agg -> Text
aggVegaName Agg
a = case Agg
a of
Agg
Count -> Text
"count"
Agg
Sum -> Text
"sum"
Agg
Mean -> Text
"mean"
Agg
Median -> Text
"median"
Agg
Min -> Text
"min"
Agg
Max -> Text
"max"
allFields :: Chart -> [ResolvedField]
allFields :: Chart -> [ResolvedField]
allFields Chart
c = Chart -> [ResolvedField]
chFields Chart
c [ResolvedField] -> [ResolvedField] -> [ResolvedField]
forall a. [a] -> [a] -> [a]
++ (Chart -> [ResolvedField]) -> [Chart] -> [ResolvedField]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Chart -> [ResolvedField]
allFields (Chart -> [Chart]
chLayers Chart
c)
toVLSpec :: Chart -> VLSpec
toVLSpec :: Chart -> VLSpec
toVLSpec Chart
c
| [Chart] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null (Chart -> [Chart]
chLayers Chart
c) =
(Mark -> VLSpec
emptySpec (Chart -> Mark
chMark Chart
c))
{ vlEncodings = chEncs c
, vlTransforms = chTransforms c
, vlTitle = chTitle c
, vlWidth = chW c
, vlHeight = chH c
}
| Bool
otherwise =
(Mark -> VLSpec
emptySpec (Chart -> Mark
chMark Chart
c))
{ vlLayers = map toVLSpec (chLayers c)
, vlTitle = chTitle c
, vlWidth = chW c
, vlHeight = chH c
}