chart-cli-0.1.0.0: src/Chart.hs
{-# LANGUAGE RecordWildCards #-}
module Chart where
import Control.Lens
import qualified Data.Text as T
import Data.Colour
import Data.Colour.SRGB
import Data.Colour.RGBSpace.HSL
import Data.Word
import Data.Hashable
import Data.List
import Data.Default.Class
import Graphics.Rendering.Chart
import Types
nameColor :: T.Text -> Colour Double
nameColor name =
let h = fromIntegral (hash name `mod` 255) :: Word8
hue = fromIntegral (hash name `mod` 360)
r = (fromIntegral h / 255)
v = (1 - r) * 0.5 + r*0.9
hslColor = hsl hue 0.5 v
in sRGB (channelRed hslColor) (channelGreen hslColor) (channelBlue hslColor)
toPairs :: [[Value]] -> [[(Value, Double)]]
toPairs = transpose . zipWith go [1..]
where
go i [y] = [(Number (fromIntegral i), toDouble y)]
go _ [x, y] = [(x, toDouble y)]
go _ (x : ys) = [(x, toDouble y) | y <- ys]
toSeries :: [[Value]] -> [(Value, [Double])]
toSeries = zipWith go [1..]
where
go _ (Date dt : ys) = (Date dt, map toDouble ys)
go i ys = (Index (PlotIndex i), map toDouble ys)
toAreas :: [[Value]] -> [[(Value, (Double, Double))]]
toAreas = transpose . go 1
where
go :: Int -> [[Value]] -> [[(Value, (Double, Double))]]
go _ [] = []
go i ([y] : rest) = [(Number (fromIntegral i), (0, toDouble y))] : go (i+1) rest
go i ((x : ys) : rest) =
let runningSums = tail $ scanl (+) 0 $ map toDouble ys
pairs = zip (0 : runningSums) runningSums
in [(x, pair) | pair <- pairs] : go (i+1) rest
mkLineStyle :: T.Text -> LineStyle
mkLineStyle name =
line_color .~ opaque (darken 0.2 $ nameColor name) $ def
mkFillStyle :: T.Text -> FillStyle
mkFillStyle name =
fill_color .~ opaque (nameColor name) $ def
mkPointStyle :: T.Text -> PointStyle
mkPointStyle name =
point_color .~ opaque (nameColor name)
$ point_border_color .~ opaque (darken 0.2 $ nameColor name)
$ point_border_width .~ 1
$ point_radius .~ 4
$ def
tailOrSingle :: [a] -> [a]
tailOrSingle [] = []
tailOrSingle [x] = [x]
tailOrSingle (x:xs) = xs
convertChart :: ChartConfig -> ChartData -> AnyChart
convertChart Line cht =
LineChart [
plot_lines_title .~ T.unpack title
$ plot_lines_style .~ mkLineStyle title
$ plot_lines_values .~ [pairs]
$ def
| (pairs, NumberColumn title) <- zip (toPairs (chtValues cht)) (tailOrSingle $ chtColumns cht)
]
convertChart (Bar {..}) cht =
BarChart $
plot_bars_titles .~ [T.unpack title | NumberColumn title <- chtColumns cht]
$ plot_bars_item_styles .~ [(mkFillStyle title, Just (mkLineStyle title)) | NumberColumn title <- chtColumns cht]
$ plot_bars_style .~ barStyle
$ plot_bars_values .~ toSeries (chtValues cht)
$ def
convertChart Area cht =
AreaChart [
plot_fillbetween_title .~ T.unpack title
$ plot_fillbetween_style .~ mkFillStyle title
$ plot_fillbetween_values .~ area
$ def
| (area, NumberColumn title) <- zip (toAreas (chtValues cht)) (tailOrSingle $ chtColumns cht)
]
convertChart Points cht =
PointsChart [
plot_points_title .~ T.unpack title
$ plot_points_style .~ mkPointStyle title
$ plot_points_values .~ pairs
$ def
| (pairs, NumberColumn title) <- zip (toPairs (chtValues cht)) (tail $ chtColumns cht)
]
chartToPlots :: AnyChart -> [Plot Value Double]
chartToPlots (LineChart plots) = map toPlot plots
chartToPlots (BarChart plot) = [plotBars plot]
chartToPlots (AreaChart plots) = map toPlot plots
chartToPlots (PointsChart plots) = map toPlot plots
makeChart :: ChartConfig -> ChartData -> Layout Value Double
makeChart chtype cht =
let title = chtTitle cht
foreground = chtForeground cht
background = chtBackground cht
setFontStyle = font_color .~ opaque foreground
setLineColor = line_color .~ opaque foreground
gridStyle = dashedLine 1 [5, 5] $ dissolve 0.5 $ opaque foreground
setAxisColor =
laxis_style %~ (axis_label_style %~ setFontStyle) .
(axis_line_style %~ setLineColor) .
(axis_grid_style .~ gridStyle)
setLegendFont = legend_label_style %~ setFontStyle
setBackground = fill_color .~ opaque background
yAxis = setAxisColor def
xAxis = setAxisColor def
legend = if chtLegend cht
then Just $ setLegendFont $ legend_orientation .~ LORows 4 $ def
else Nothing
in layout_title .~ T.unpack title
$ layout_background %~ setBackground
$ layout_title_style %~ setFontStyle
$ layout_x_axis .~ xAxis
$ layout_y_axis .~ yAxis
$ layout_legend .~ legend
$ layout_plots .~ chartToPlots (convertChart chtype cht)
$ def