barchart-0.1: src/Graphics/BarChart/Types.hs
{-# LANGUAGE TypeFamilies, FlexibleContexts, NamedFieldPuns, RecordWildCards,
GeneralizedNewtypeDeriving #-}
module Graphics.BarChart.Types where
import System.IO ( FilePath )
import Graphics.Rendering.Diagrams
import Graphics.Rendering.Diagrams.Types ( SomeColor(..) )
import Data.Data ( Data, Typeable )
import Data.List ( nub, transpose )
import Data.Maybe ( fromJust, fromMaybe )
import Control.Arrow ( first )
type Label = String
-- | Bar charts consist of a (possibly empty) list of labels for the
-- diferent blcks of bars and the bars themselves.
--
data BarChart a = BarChart {
-- | Labels of blocks in bars. Drawn as a legend if non-empty.
block_labels :: [Label],
-- | The different bars of the chart.
bars :: [Bar a] }
deriving Show
-- | Represents one bar of a bar chart.
data Bar a = Bar {
-- | Label written underneath
label :: Label,
-- | Different blocks of the bar. Simple charts contain only one
-- block per bar.
blocks :: [Block a] }
deriving Show
-- | Bocks either have a single associated value or a mean value along
-- with minimum and maximum deviation.
--
data Block a = Value a | Interval { mean :: a, lower :: a, upper :: a }
deriving Show
-- | Instances of this class can be depicted in bar charts.
--
class Num a => Measurable a where
-- | Measures the given value to figure out the correponding height
-- of the bar.
--
size :: a -> Double
instance Measurable Double where size = id
instance Measurable Float where size = realToFrac
instance Measurable Integer where size = fromIntegral
instance Measurable Int where size = fromIntegral
type ColorName = String
readColor :: ColorName -> SomeColor
readColor color = SomeColor . fromMaybe (error $ "ivalid color: " ++ color) $
readColourName color
-- | Specifies how bar charts are rendered
data Config = Config {
-- | file to which the bar chart is written
outFile :: FilePath,
-- | Type of generated file
outputType :: OutputType,
-- | Title of the generated chart
caption :: Label,
-- | Label of the x-axis
xLabel :: Label,
-- | label of the y-axis
yLabel :: Label,
-- | Colors for the different blocks of a bar. If there are fewer
-- colors than blocks, then colors are reused in a cyclic fashion.
barColors :: [SomeColor],
-- | Dimensions of the generated chart. The image will be a bit
-- larger because of additiona space used for labels.
dimensions :: (Int,Int),
-- | Scales the height of the chart. The given ratio is multiplied
-- with the 'size' of bars as given by the corresponding
-- 'Measurable' instance.
ratio :: Double,
-- | Specifies the size of fonts used for labels.
fontSize :: Double,
-- | Value between 0.0 and 1.0 which pecifies the width of
-- bars. Zero means that the bars are lines and 1.0 means that the
-- is no space between bars.
barRatio :: Double }
-- | The default configuration generates a PNG file with a chart of
-- size 600x300 pixels. The output file is left unspecified and you
-- should provide one if you use a cstom configuration.
--
conf :: Config
conf = Config { outFile = "", outputType = PNG,
caption = "", xLabel = "", yLabel = "",
barColors = map SomeColor [forestgreen,firebrick,midnightblue],
dimensions = (600,300),
ratio = 1, fontSize = 12, barRatio = 0.3 }
-- | Wrapper around the 'Double' type used in bar charts for criterion
-- summary files. It has a custom 'Show' instance to produce labels
-- like @10ms@ or @2h@ rather than showing the plain 'Double'
-- values.
--
newtype RunTime = RunTime Double
deriving (Eq,Num,Measurable)
instance Read RunTime where
readsPrec n = map (first RunTime) . readsPrec n
instance Show RunTime where
show (RunTime t) | t >= hours = display (t/hours) ++ " h"
| t >= minutes = display (t/minutes) ++ " m"
| t >= seconds = display (t/seconds) ++ " s"
| t >= millis = display (t/millis) ++ " ms"
| t >= micros = display (t/micros) ++ " us"
| otherwise = display (t/nanos) ++ " ns"
where hours = 60 * minutes
minutes = 60 * seconds
seconds = 1
millis = seconds / 1000
micros = millis / 1000
nanos = micros / 1000
display :: Double -> String
display x | x >= 100 = show (fromIntegral (round x))
| x >= 10 = show (fromIntegral (round (10*x)) / 10)
| otherwise = show (fromIntegral (round (100*x)) / 100)
-- | Wrapper around the double type used in bar charts for progression
-- summaries. It has a custom 'Show' instance that shows the 'Double'
-- values as percentages.
--
newtype Ratio = Ratio Double
deriving (Eq,Num,Measurable)
instance Read Ratio where
readsPrec n = map (first Ratio) . readsPrec n
instance Show Ratio where
show (Ratio r) = display (100*r) ++ " %"
-- | Values of this type are drawn as charts where each bar may
-- consist of multiple blocks.
--
data MultiBars a = MultiBars [Label] [(Label,[a])]
deriving Show
-- | Converts bars with multiple blocks into their 'BarChart'
-- representation.
drawMultiBars :: Measurable a => MultiBars a -> BarChart a
drawMultiBars (MultiBars block_labels pairs) = BarChart{..}
where bars = map (uncurry mkBar) pairs
mkBar label values = Bar{..} where blocks = map Value values
-- | Values of this type are drawn as charts where each bar has an
-- associated deviation depicted as an interval next to the bar.
--
newtype Intervals a = Intervals [(Label,(a,a,a))]
deriving Show
-- | Converts bars with associated deviation into their 'BarChart'
-- representation.
--
drawIntervals :: Measurable a => Intervals a -> BarChart a
drawIntervals (Intervals pairs) = BarChart{..}
where block_labels = []
bars = map (uncurry mkBar) pairs
mkBar label (mean,lower,upper) = Bar{..} where blocks = [Interval{..}]
-- | Values of this type are drawn as charts where each bar may be
-- divided into multiple blocks with an associated deviation
-- depicted as intervals next to them.
--
data MultiBarIntervals a = MBIntervals [Label] [(Label,[(a,a,a)])]
deriving Show
-- | Merges several interval charts into a chart where each bar has
-- multiple blocks that represent the different interval charts.
--
mergeIntervals :: Num a => [(Label,Intervals a)] -> MultiBarIntervals a
mergeIntervals xs =
MBIntervals block_labels [ (label,intervals label) | label <- bar_labels ]
where
bar_labels = map fst xs
block_labels = nub (concatMap ((\ (Intervals ys) -> map fst ys) . snd) xs)
intervals l = map (fromMaybe (0,0,0) . flip lookup ys) block_labels
where Intervals ys = fromJust (lookup l xs)
-- | Swaps bars and blocks of a chart that contains both and
-- associated deviations.
--
flipMultiBarIntervals :: MultiBarIntervals a -> MultiBarIntervals a
flipMultiBarIntervals (MBIntervals old_block_labels old_bars) =
MBIntervals new_block_labels new_bars
where
new_block_labels = map fst old_bars
new_bars = zip old_block_labels . transpose . map snd $ old_bars
-- | Converts bars with multiple blocks and associated deviations into
-- their 'BarChart' representation.
--
drawMultiBarIntervals :: Measurable a => MultiBarIntervals a -> BarChart a
drawMultiBarIntervals (MBIntervals block_labels pairs) = BarChart{..}
where bars = map (uncurry mkBar) pairs
mkBar label ints = Bar{..}
where blocks = map mkInterval ints
mkInterval (mean,lower,upper) = Interval{..}