happy-hour-0.0.0.1: library/Graphics/HappyHour.hs
{-# OPTIONS_GHC -Wall #-}
module Graphics.HappyHour (writeBarGraphSvgFile) where
-- base
import Data.List (genericLength)
-- Chart
import Graphics.Rendering.Chart.Easy hiding (bars)
-- Chart-diagrams
import Graphics.Rendering.Chart.Backend.Diagrams (renderableToFile, FileOptions, fo_size)
-- | Create an SVG file containing a bar graph.
writeBarGraphSvgFile
:: FilePath
-- ^ Where the file will be written. The containing directory must already exist. If there already exists a file at this path, it will be overwritten. If there exists a directory at this path, the action will fail.
-> [(String, Int)]
-- ^ The data to visualize. Each list entry represents a bar on the plot, ordered from left to right. The @String@ is the label on the X axis, and the @Int@ is the height of the bar.
-> IO ()
writeBarGraphSvgFile path bars =
do
let layout = applyBarPlot bars def :: Layout PlotIndex Int
let r = toRenderable layout :: Renderable ()
let fo = applyFileOptions bars def :: FileOptions
_ <- renderableToFile fo path r
return ()
type Bars = [(String, Int)]
type Endo a = a -> a
foldEndo :: [a -> a] -> (a -> a)
foldEndo = foldr (.) id
applyFileOptions :: Bars -> Endo FileOptions
applyFileOptions bars =
set fo_size (maximum ([l x | (x, _) <- bars]) * l bars * 50, 600)
where
l = genericLength
-- | Add a single bar plot to a layout, configure its X axis labels to match the data, and style the layout to look nice with a bar plot.
applyBarPlot :: Bars -> Endo (Layout PlotIndex Int)
applyBarPlot bars =
foldEndo
[ applyLayoutStyle
, applyLabels bars
, set layout_plots [plotBars (( applyBarStyle . applyValues bars ) def)]
]
-- | Set the data values on a bar plot.
applyValues :: Bars -> Endo (PlotBars PlotIndex Int)
applyValues bars =
set plot_bars_values (addIndexes [ [y] | (_, y) <- bars ])
-- | Set up the X axis of a layout in preparation for adding the bar plot.
applyLabels :: Integral x => Bars -> Endo (Layout x y)
applyLabels bars =
set (layout_x_axis . laxis_generate) (autoIndexAxis [ x | (x, _) <- bars ])
-- | Set some layout styles that look nice.
applyLayoutStyle :: Endo (Layout x y)
applyLayoutStyle =
foldEndo
[ set layout_margin 40
, setAxisLineWidth 5
, over yGridStyle (set line_width 5 . set line_dashes [40, 20])
, set (layout_y_axis . laxis_style . axis_label_gap) 25
, set (layout_title_style . font_size) 80
, setAxisFontSize 40
]
-- | Lens for the grid style on the Y axis
yGridStyle :: Lens' (Layout x y) LineStyle
yGridStyle = layout_y_axis . laxis_style . axis_grid_style
-- | Lens for the width of an axis
axisLineWidth :: Lens' (LayoutAxis x) Double
axisLineWidth = laxis_style . axis_line_style . line_width
-- | Lens for the label font size of an axis
axisLabelSize :: Lens' (LayoutAxis x) Double
axisLabelSize = laxis_style . axis_label_style . font_size
-- | Set the line width on both axes.
setAxisLineWidth :: Double -> Endo (Layout x y)
setAxisLineWidth v =
foldEndo [ set l v | l <- [ layout_x_axis . axisLineWidth
, layout_y_axis . axisLineWidth ] ]
-- | Set the label size on both axes.
setAxisFontSize :: Double -> Endo (Layout x y)
setAxisFontSize v =
foldEndo [ set l v | l <- [ layout_x_axis . axisLabelSize
, layout_y_axis . axisLabelSize ] ]
-- | Set some bar styles that look nice.
applyBarStyle :: Endo (PlotBars x y)
applyBarStyle =
foldEndo
[ set plot_bars_spacing (BarsFixGap 80 20)
, set plot_bars_item_styles [(FillStyleSolid (opaque steelblue), Nothing)]
]