barchart (empty) → 0.1
raw patch · 11 files changed
+1411/−0 lines, 11 filesdep +basedep +cmdargsdep +csvsetup-changed
Dependencies added: base, cmdargs, csv, diagrams, filepath
Files
- LICENSE +32/−0
- README.markdown +330/−0
- Setup.lhs +8/−0
- barchart.cabal +58/−0
- src/Graphics/BarChart.hs +99/−0
- src/Graphics/BarChart/Parser.hs +108/−0
- src/Graphics/BarChart/Parser/Criterion.hs +45/−0
- src/Graphics/BarChart/Parser/Progression.hs +32/−0
- src/Graphics/BarChart/Rendering.hs +127/−0
- src/Graphics/BarChart/Types.hs +225/−0
- src/barchart.hs +347/−0
+ LICENSE view
@@ -0,0 +1,32 @@+Copyright (c) 2010, Sebastian Fischer++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++ 1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ 2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in+ the documentation and/or other materials provided with the+ distribution.++ 3. Neither the name of the author nor the names of his contributors+ may be used to endorse or promote products derived from this+ software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+
+ README.markdown view
@@ -0,0 +1,330 @@+barchart is a command-line program for generating bar charts from CSV+files. It has special support for creating charts from data generated+by the Haskell benchmarking tools [criterion] and+[progression]. barchart can create PNG, SVG, PDF, and PS files using+the Haskell [Diagrams] library for rendering and, hence, depends on a+Haskell binding to Cairo which is part of [Gtk2Hs].++# installation++available soon. be patient.++# usage++Bar charts can be created from CSV files using the `barchart`+command-line utility. For example, if you want to track how many hours+you practice playing the guitar on each day of the week, you can+create a file `guitar.csv` with the following contents:++ Mon,1.2+ Tue,0.3+ Wed,2.1+ Thu,0.9+ Fri,1.1+ Sat,3.2+ Sun,3.1++The call++ # barchart --title="Practice hours" guitar.csv ++creates a file `guitar.png` which looks like this:++![Practice hours][guitar]++Each bar is labeled with a weekday and has an associated practice+hour. The chart is scaled automatically such that the largest bar+spans the (configurable, see below) height of the chart. The `--title`+flag passed to `barchart` in the above call is optional. If you do not+supply one, then barchart uses the basename of the CSV file as title+of the chart.++If you want to track practice hours over multiple weeks, you can+create a file with a _mean_, _minimal_, and _maximal_ values for each+day of the week:++ Mon,1.2,0.9,1.7+ Tue,0.3,0.1,0.5+ Wed,2.1,1.5,2.5+ Thu,0.9,0.4,1.0+ Fri,1.1,1.0,1.2+ Sat,3.2,1.5,5.2+ Sun,3.1,2.3,4.2++Calling `barchart` in the `interval` mode++ # barchart intervals --title="Mean practice hours" guitar-mean.csv++produces the following bar chart:++![Mean practice hours][guitar-mean]++In this chart, each bar represents the mean practice hour for a day+of the week and the minimum and maximum values are depicted with+intervals on the right edge of a bar.++If you want to compare your practice hours for each day of the week+and split it by months, you can create a CSV file like this:++ Mon,1.2,2.1,1.7+ Tue,0.6,0.7,0.8+ Wed,2.1,1.2,2.5+ Thu,0.9,1.5,1.7+ Fri,1.1,1.3,0.7+ Sat,3.2,1.7,4.3+ Sun,3.1,3.2,2.1++We can use `barchart` (in the default mode) ++ # barchart --title="Practice hours per month" --division="Jan Feb Mar" guitar-months.csv++to create the following diagram:++![Practice hours by month][guitar-months]++Each bar is divided into different blocks which all have an associated+amount of practice hours. Green blocks represent practice hours in+January, red blocks in February, and blue blocks represent practice+hours in March. The block labels are given to `barchart` via the+`--division` flag. You can also draw multiple blocks per bar in+`interval` mode but then three values (_mean_,_min_,_max_) are used+for each block. Hence, if you want to depict mean practice times with+deviations for January, February, and March, you must create a CSV+file where each day of the week is followed by nine practice times.++## flags++The `barchart` program can be configured using command-line flags. We+can use the `--help` flag to print a summary:++ # barchart --help+ Bar Chart 0.1+ + barchart [blocks] [FLAG] [FILE]+ + barchart intervals [FLAG] [FILE]+ + barchart criterion [FLAG] [FILE]+ + --summary Show benchmark summary (default)+ -s --summary-comparison Compare different benchmark summaries+ -b --benchmark-comparison Compare different benchmarks+ + barchart progression [FLAG] [FILE]+ + -s --summary-comparison Breakdown chart by benchmark summary (default)+ -b --benchmark-comparison Breakdown chart by benchmarks+ + Common flags:+ -? --help[=FORMAT] Show usage information (optional format)+ -V --version Show version information+ -v --verbose Higher verbosity+ -q --quiet Lower verbosity+ -o --out-file=FILE Name of generated file+ -t --title=STRING Title of bar chart+ -x --xlabel=STRING Label of x axis+ -y --ylabel=STRING Label of y axis+ -g --guess-file-type Guess output file type by name (default)+ --png Generate .png file+ --svg Generate .svg file+ --pdf Generate .pdf file+ --ps Generate .ps file+ -d --division=STRINGS Labels separated by whitespace+ -c --colors=STRINGS Color names separated by whitespace+ -w --width=NUM Width of generated bar chart (default=600)+ -h --height=NUM Height of generated bar chart (default=300)+ -l --label-size=NUM Font size used for labels (default=12)+ -W --bar-width=FLOAT Bar width between 0 and 1 (default=0.3)++`barchart` can be run in different modes. We have already seen the+default (`blocks`) mode and the `intervals` mode. The `criterion` and+`progression` modes are described below. Most command-line flags are+self explanatory. Apart from what we have seen in the example above,+the following options are particularly interesting:++ * `--xlabel` and `--ylabel` label the axis of the coordinate system.++ * `--colors` change the colors of the different blocks of a bar. You+ can use all color names listed in the [SVG 1.1+ Specification][Colors]. If you specify fewer colors than there are+ blocks, then colors are reused in a cyclic fashion. The default+ value for this argument is `--colors="seagreen firebrick+ midnightblue"`.++ * `--width` and `--height` specify the dimensions of the generated+ _chart_. The generated _picture_ is a little larger because of the+ title and bar labels. If you want to draw a chart with many bars,+ you should increase the width compared to the height or tweak the+ bar width.++ * `--bar-width` is a value between 0.0 and 1.0 hat specifies how+ thick the bars are compared to the bar distance. With a value of+ `1.0` the bars are drawn directly next to each other, a value of+ 0.0 draws bars that are actually lines.++## criterion++[criterion] is a Haskell tool for statistically robust benchmarking+that can generate graphs which, for example, depict the densities of+execution times. criterion can also generate a CSV file summarising+all benchmarks but does not provide means to visualise these+summaries.++We can use `barchart` in `criterion` mode to draw graphs based on the+summary files generated by criterion. To demonstrate the criterion+mode, we write a small Haskell program that benchmarks a simple+definition of the [factorial] function:++ import Criterion.Main+ + main = defaultMain [bgroup "fac" [bench (show n) (nf product [1..n]) | n <- ns]]+ where ns = [k*10^4 | k <- [1,2,3]] :: [Integer]++We can compile this program, generate a benchmark summary using+criterion, and visualise it using barchart as follows:++ # ghc -O2 --make factorial+ # ./factorial --summary=factorial.csv+ # barchart criterion factorial.csv++These three calls generate a bar chart with one bar for each benchmark+whose size is proportional to the corresponding run time.++![fac]++We can now modify the program to use an explicitly recursive+definition of the fibonacci function to see whether this affects the+run times.++ fac 0 = 1+ fac n = n * fac (n-1)++After generating another summary file `factorial2.csv` we could+generate another bar chart to visualise it and view both charts side+by side to compare the run times of the different implementations of+the fibonacci function. However, instead of generating two different+charts we can also generate a single chart that combines information+from multiple benchmark summaries.++ # barchart criterion --summary-comparison factorial.csv factorial2.csv++We can pass as many summary files as we like and barchart will draw a+bar for each summary file with blocks representing the different+benchmarks.++![fac-summaries]++We can see clearly that the original implementation using `product` is+faster than the explicitly recursive definition of the factorial+function. Instead of drawing different bars for different summaries,+barchart can also draw different bars for the different benchmarks+with blocks for the summaries.++![fac-benchmarks]++This is less useful for comparing different implementations but useful+to compare the different benchmarks using all implementations at once.++## progression++[progression] is an alternative tool to depict criterion's benchmark+results. It generates charts similar to the summary comparisons+discussed above but draws bars for different benchmarks next to each+other rather than as boxes on top of each other in a single+bar. Drawing the results of all benchmarks for a specific+implementation on top of each other lets you see immediately which+implementation is the fastest _in total_ because the total height of+the bar is the sum of all benchmarks for the corresponding+implementation. However, progression provides features that barchart+does not provide such as normalising run times and show them as+percentages. Hence, the barchart program has a `progression` mode to+generate bar charts from CSV files generated by progression.++We can alter the factorial program as follows to generate+progression's data:++ import Criterion+ import Progression.Main+ + main = defaultMain (bgroup "fac" [bench (show n) (nf fac n) | n <- ns])+ where ns = [k*10^4 | k <- [1,2,3]] :: [Integer]+ + fac 0 = 1+ fac n = n * fac (n-1)++We do not need to change the fibonacci function itself because we+won't execute benchmarks using progression. We only generate a+comparison of the previous benchmarks:++ # mv factorial.csv bench-factorial.csv+ # mv factorial2.csv bench-factorial2.csv+ # ./factorial --mode=graph --compare=factorial,factorial2++progression wants the summary files to have specific names, hence, we+rename them appropriately before generating a graph. The progression+tool generates two files `plot.csv` and `plot.png`. Here, we are not+interested in the generated PNG file but pass the CSV file to the+barchart program in `progression` mode.++ # barchart progression --division="product recursive" plot.csv++As the CSV file generated by progression does not include information+of the names of the summary files, we provide labels using the+`--division` flag. The generated bar chart loooks like this:++![progression-summaries]++The run times of the first implementation are normalised to 100 % and+the run times of the second implementation are depicted relative to+the first. Like in `criterion` mode we can also flip the axis in+`progression` mode.++ # barchart progression --benchmark-comparison --division="product recursive" plot.csv++Now the first benchmark is normalised to 100 % in each bar so this+diagram is of questionable use.++![progression-benchmarks]++# collaboration++The source code is on [GitHub] so you can create or vote on [issues]+to ask for extensions or fork this project to write extensions+yourself.++## limitations++Currently, barchart does not support negative quantities and draws+weird bars if you use them anyway. Also, the legend for blocks is+sometimes drawn suboptimally. I guess I made a mistake but I cannot+find it.++## thanks++I would not have written this program without the [Diagrams] library+by Brent Yorgey and the [CmdArgs] package by Neil Mitchell.++# contact++For questions or feedback email [Sebastian Fischer][email].++[email]: mailto:sebf@informatik.uni-kiel.de+[GitHub]: http://github.com/sebfisch/haskell-barchart +[issues]: http://github.com/sebfisch/haskell-barchart/issues++[guitar]: http://sebfisch.github.com/haskell-barchart/examples/guitar.png+[guitar-mean]: http://sebfisch.github.com/haskell-barchart/examples/guitar-mean.png+[guitar-months]: http://sebfisch.github.com/haskell-barchart/examples/guitar-months.png+[fac]: http://sebfisch.github.com/haskell-barchart/examples/factorial.png+[fac-summaries]: http://sebfisch.github.com/haskell-barchart/examples/fac-summaries.png+[fac-benchmarks]: http://sebfisch.github.com/haskell-barchart/examples/fac-benchmarks.png+[progression-summaries]: http://sebfisch.github.com/haskell-barchart/examples/progression-summaries.png+[progression-benchmarks]: http://sebfisch.github.com/haskell-barchart/examples/progression-benchmarks.png++[criterion]: http://hackage.haskell.org/package/criterion+[progression]: http://hackage.haskell.org/package/progression+[Diagrams]: http://code.haskell.org/diagrams/+[CmdArgs]: http://community.haskell.org/~ndm/cmdargs/+[Gtk2Hs]: http://www.haskell.org/gtk2hs/+[Colors]: http://www.w3.org/TR/SVG11/types.html#ColorKeywords+[factorial]: http://en.wikipedia.org/wiki/Factorial
+ Setup.lhs view
@@ -0,0 +1,8 @@+#! /usr/bin/env runhaskell++> module Main (main) where+>+> import Distribution.Simple (defaultMain)+>+> main :: IO ()+> main = defaultMain
+ barchart.cabal view
@@ -0,0 +1,58 @@+Name: barchart+Version: 0.1+Cabal-Version: >= 1.6+Synopsis: Creating Bar Charts in Haskell++Description:++ This package provides a program that generates bar charts from CSV+ files and has special support for visualising the benchmark results+ of the criterion and progression tools. It comes with an associated+ API that can be used to generate custom bar charts in case the+ charts generated by the command-line program are insufficient for+ your needs.++Category: Graphics+License: BSD3+License-File: LICENSE+Author: Sebastian Fischer+Maintainer: Sebastian Fischer+Bug-Reports: http://github.com/sebfisch/haskell-barchart/issues+Homepage: http://sebfish.github.com/haskell-barchart+Build-Type: Simple+Stability: experimental++Extra-Source-Files: README.markdown++Executable barchart+ Main-is: barchart.hs+ Hs-Source-Dirs: src+ Build-Depends: base >= 3 && < 5,+ cmdargs >= 0.1 && < 0.2, + csv >= 0.1 && < 1.2, + diagrams >= 0.2 && < 0.3,+ filepath >= 1.1 && < 1.2+ Extensions: DeriveDataTypeable,+ FlexibleContexts,+ GeneralizedNewtypeDeriving,+ NamedFieldPuns, + RecordWildCards,+ TypeFamilies++Library+ Exposed-Modules: Graphics.BarChart+ Other-Modules: Graphics.BarChart.Types,+ Graphics.BarChart.Rendering,+ Graphics.BarChart.Parser,+ Graphics.BarChart.Parser.Criterion,+ Graphics.BarChart.Parser.Progression+ Hs-Source-Dirs: src+ Build-Depends: base >= 3 && < 5,+ csv >= 0.1 && < 1.2, + diagrams >= 0.2 && < 0.3,+ filepath >= 1.1 && < 1.2+ Extensions: FlexibleContexts,+ GeneralizedNewtypeDeriving,+ NamedFieldPuns, + RecordWildCards,+ TypeFamilies
+ src/Graphics/BarChart.hs view
@@ -0,0 +1,99 @@+-- |+-- Module : Graphics.BarChart+-- Copyright : Sebastian Fischer+-- License : BSD3+-- Maintainer : Sebastian Fischer <mailto:sebf@informatik.uni-kiel.de>+-- Stability : experimental+-- +-- This library provides functions and data type for generating bar+-- charts, for example, from CSV files. If the functionality provided+-- by the command-line program is insufficient for your needs, you can+-- use the API provided by this library to draw custom bar charts.+-- +-- The library provides high-level functions to generate bar-chart+-- values from different types but also exports the underlying+-- datatype and all functions used for rendering such that you have+-- access to all internal details.+-- +module Graphics.BarChart (++ -- * Reading from CSV files++ -- | There are three different modes for parsing simple CSV files:+ -- treating different values in one row as blocks of one bar,+ -- treating them as deviations from a mean value, or both. ++ -- ** Writing images++ -- | The following three functions implement these modes parsing the+ -- input from a CSV file and producing output in an image file.++ writeMultiBarChart, writeIntervalChart, writeMultiBarIntervalChart,++ Config(..), Label,++ -- ** Creating bar charts++ -- | The previous functions use the following helper functions to+ -- create a 'BarChart' from a CSV file.++ multiBarChart, intervalChart, multiBarIntervalChart,++ Measurable(..),++ -- ** Bar chart representations++ -- | The three different parsing modes are reflected by three data+ -- types that can be parsed from a CSV file.++ MultiBars(..), parseMultiBars, drawMultiBars,+ Intervals(..), parseIntervals, drawIntervals,+ MultiBarIntervals(..), parseMultiBarIntervals, drawMultiBarIntervals,++ -- | When you have multiple CSV files representing an interval+ -- chart, you can merge them to a single multi-bar interval+ -- chart. Such charts can be flipped such that bars and blocks+ -- change their roles.++ mergeIntervals, flipMultiBarIntervals,++ -- | Bar charts are represented as values of type 'BarChart' @a@.++ BarChart(..), Bar(..), Block(..),++ -- * Rendering++ -- | Values of type 'BarChart' @a@ can be rendered to PNG, SVG, PDF,+ -- and PS files.++ render, renderWith, conf,++ -- ** Creating diagrams++ -- | Bar charts are rendered by conversion into 'Diagram's.++ diagram,++ -- * Benchmarking visualisation++ -- | There are special functions for parsing data generated by the+ -- benchmarking tools criterion and progression.++ -- ** Criterion++ writeCriterionChart, writeComparisonChart,+ criterionChart, comparisonChart,+ RunTime(..),++ -- ** Progression++ writeProgressionChart, progressionChart,+ Ratio(..)++ ) where++import Graphics.BarChart.Types+import Graphics.BarChart.Rendering+import Graphics.BarChart.Parser+import Graphics.BarChart.Parser.Criterion+import Graphics.BarChart.Parser.Progression
+ src/Graphics/BarChart/Parser.hs view
@@ -0,0 +1,108 @@+module Graphics.BarChart.Parser where++import Text.CSV++import Data.List ( nub )+import Data.Maybe ( fromJust, fromMaybe )++import System.FilePath++import Graphics.BarChart.Types+import Graphics.BarChart.Rendering++-- | Converts a CSV file to be drawn as a chart where each bar may+-- consist of multiple blocks.+-- +parseMultiBars :: Read a => [Label] -> CSV -> MultiBars a+parseMultiBars block_labels = MultiBars block_labels . map parseRecord+ where parseRecord (label:values) = (label,map read values)++-- | Converts a CSV file to be drawn as a chart where each bar has an+-- attached deviation depicted as an interval next to the bar.+-- +parseIntervals :: Read a => CSV -> Intervals a+parseIntervals = Intervals . map parseRecord+ where parseRecord [label,m,l,u] = (label,(read m, read l, read u))++-- | Converts a CSV file to be drawn as a chart where each bar may+-- consist of multiple blocks which have an attached deviation+-- depicted as an interval next to them.+-- +parseMultiBarIntervals :: Read a => [Label] -> CSV -> MultiBarIntervals a+parseMultiBarIntervals block_labels =+ MBIntervals block_labels . map parseRecord+ where parseRecord (label:fields) = (label, triples fields)+ triples (x:y:z:xs) = (read x,read y,read z) : triples xs+ triples _ = []++-- | Used by 'writeMultiBarChart' to create a 'BarChart' from a CSV file.+-- +multiBarChart :: (Measurable a, Read a)+ => [Label] -- ^ legend for blocks+ -> CSV -- ^ comma separated values to depict+ -> BarChart a+multiBarChart block_labels = drawMultiBars . parseMultiBars block_labels++-- | Used by 'writeIntervalChart' to create a 'BarChart' from a CSV file.+-- +intervalChart :: (Measurable a, Read a)+ => CSV -- ^ comma separated values to depict + -> BarChart a+intervalChart = drawIntervals . parseIntervals++-- | Used by 'writeMultiBarIntervalChart' to create a 'BarChart' from+-- | a CSV file.+-- +multiBarIntervalChart :: (Measurable a, Read a)+ => [Label] -- ^ legend for blocks+ -> CSV -- ^ comma separated values to depict+ -> BarChart a+multiBarIntervalChart block_labels =+ drawMultiBarIntervals . parseMultiBarIntervals block_labels++-- | The first column of the CSV file is parsed as names of the+-- bars. The height of each bar corresponds to the sum of all+-- subsequent entries. If there is more than one entry, the bars are+-- split into blocks.+-- +writeMultiBarChart :: Config -- ^ where and how to draw the bar chart+ -> FilePath -- ^ CSV file to read+ -> [Label] -- ^ if non-empty, used as legend for blocks+ -> IO ()+writeMultiBarChart config file block_labels =+ do csv <- readCSV file+ let chart = multiBarChart block_labels csv :: BarChart Double+ renderWith config chart++-- | The first column of the CSV file is parsed as names of the+-- bars. Three entries following each bar name are parsed as mean,+-- minimum, and maximum value and depicted using an interval next to+-- the bar.+-- +writeIntervalChart :: Config -- ^ where and how to draw the bar chart+ -> FilePath -- ^ CSV file to read+ -> IO ()+writeIntervalChart config file =+ do csv <- readCSV file+ let chart = intervalChart csv :: BarChart Double+ renderWith config chart++-- | The first column of the CSV file is parsed as names of the+-- bars. The entries following each bar name are parsed as triples+-- of mean, minimum, and maximum value and depicted using an+-- interval next to the bar. The number of subsequent entries must+-- be a multiple of three and each bar is divided into a+-- corresponding number of blocks.+-- +writeMultiBarIntervalChart :: Config -- ^ where and how to draw the bar chart+ -> FilePath -- ^ CSV file to read+ -> [Label] -- ^ legend for blocks+ -> IO ()+writeMultiBarIntervalChart config file block_labels =+ do csv <- readCSV file+ let chart = multiBarIntervalChart block_labels csv :: BarChart Double+ renderWith config chart++readCSV :: FilePath -> IO CSV+readCSV file = either (fail . show) (return . clean) =<< parseCSVFromFile file+ where clean = filter (not . all null)
+ src/Graphics/BarChart/Parser/Criterion.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE NamedFieldPuns, RecordWildCards #-}++module Graphics.BarChart.Parser.Criterion where++import Text.CSV++import System.FilePath++import Graphics.BarChart.Types+import Graphics.BarChart.Parser+import Graphics.BarChart.Rendering++-- | Used by 'writeCriterionChart' to generate a bar chart from+-- criterion's summary file.+-- +criterionChart :: CSV -> BarChart RunTime+criterionChart (_:csv) = intervalChart $ map (take 4) csv++-- | Used by 'writeComparisonChart' to generate a bar chart from+-- multiple summary files generated by criterion.+-- +comparisonChart :: Bool -> [(Label,CSV)] -> BarChart RunTime+comparisonChart flip+ = drawMultiBarIntervals+ . (if flip then flipMultiBarIntervals else id)+ . mergeIntervals+ . map (\ (label,_:csv) -> (label, parseIntervals $ map (take 4) csv))++-- | Reads a summary file generated by criterion and writes a+-- corresponding bar chart.+-- +writeCriterionChart :: Config -> FilePath -> IO ()+writeCriterionChart config file =+ renderWith config . criterionChart =<< readCSV file++-- | Reads multiple summary files generated by criterion and creates a+-- bar chart to compare them. If the first argument is 'True' the+-- chart is flipped such that the bars represent different+-- benchmarks rather than summaries.+-- +writeComparisonChart :: Bool -> Config -> [FilePath] -> IO ()+writeComparisonChart flip config@Config{..} files =+ do csvs <- mapM readCSV files+ renderWith config . comparisonChart flip $+ zip (map dropExtension files) csvs
+ src/Graphics/BarChart/Parser/Progression.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE NamedFieldPuns, RecordWildCards #-}++module Graphics.BarChart.Parser.Progression where++import Text.CSV++import System.FilePath++import Graphics.BarChart.Types+import Graphics.BarChart.Parser+import Graphics.BarChart.Rendering++-- | Used by 'writeProgressionChart' to generate a bar chart from+-- progression's @plot.csv@ file.+-- +progressionChart :: Bool -> [Label] -> CSV -> BarChart Ratio+progressionChart flip labels csv+ = drawMultiBarIntervals+ . (if flip then flipMultiBarIntervals else id)+ . parseMultiBarIntervals block_labels+ $ csv+ where block_labels | null labels = replicate (length csv) ""+ | otherwise = labels++-- | Reads the @plot.csv@ file generated by progression and creates a+-- corresponding bar chart.+-- +writeProgressionChart :: Bool -> Config -> FilePath -> [Label] -> IO ()+writeProgressionChart flip config@Config{..} file block_labels =+ do csv <- readCSV file+ let chart = progressionChart flip block_labels csv+ renderWith config chart
+ src/Graphics/BarChart/Rendering.hs view
@@ -0,0 +1,127 @@+{-# LANGUAGE NamedFieldPuns, RecordWildCards #-}++module Graphics.BarChart.Rendering where++import Data.List ( genericLength )++import Graphics.Rendering.Diagrams+import Graphics.Rendering.Diagrams.Types ( SomeColor )++import Graphics.BarChart.Types++-- | Renders a bar chart as @barchart.png@ according to the default+-- configuration 'conf'.+-- +render :: Measurable a => BarChart a -> IO ()+render = renderWith conf{ outFile = "barchart.png" }++-- | Renders a bar chart according to a custom configuration.+-- +renderWith :: Measurable a => Config -> BarChart a -> IO ()+renderWith config@Config{..} chart =+ do renderAs outputType outFile (Width (fromIntegral width))+ . diagram config+ $ chart+ putStrLn $ "Chart written in file " ++ outFile+ where (width,_) = dimensions++-- | This function can be used to embed bar charts into bigger+-- 'Diagram's.+-- +diagram :: Measurable a => Config -> BarChart a -> Diagram+diagram config@Config{..} chart@BarChart{..} =+ drawBarChart config{ ratio = hratio / wratio,+ fontSize = fontSize / wratio }+ chart+ where+ (width,height) = dimensions+ wratio = fromIntegral width / genericLength bars+ hratio = fromIntegral height / barChartHeight chart++barChartHeight :: Measurable a => BarChart a -> Double+barChartHeight BarChart{..} | null bars = 0+ | otherwise = maximum (map barSize bars)++barSize :: Measurable a => Bar a -> Double+barSize Bar{..} | null blocks = 0+ | otherwise = sum (upperSize b:map valueSize bs)+ where (b:bs) = reverse blocks++valueSize :: Measurable a => Block a -> Double+valueSize (Value x) = size x+valueSize Interval{..} = size mean++upperSize :: Measurable a => Block a -> Double+upperSize (Value x) = size x+upperSize Interval{..} = size upper++drawBarChart :: Measurable a => Config -> BarChart a -> Diagram+drawBarChart config@Config{..} chart@BarChart{..} =+ pad fontSize fontSize $ vsepA fontSize hcenter [header,body]+ where+ width = genericLength bars+ height = ratio * barChartHeight chart+ bar_sep = 1 - barRatio++ header = vsepA fontSize hcenter [title,legend]+ title = text (1.5*fontSize) caption+ legend = hcat (zipWith (drawDescr config) (roll barColors) block_labels)++ body = vcat [yBars, xaxis, xLabels]++ yBars = hdistribA (bar_sep/2) left bottom [yaxis, cols]+ yaxis = vsep (fontSize/2)+ [text fontSize yLabel,+ straight (pathFromVectors [(0,height)])]+ cols = sideBySide left (map (drawBar config) bars)++ xaxis = hsep (fontSize/2)+ [straight (pathFromVectors [(width,0)]), text fontSize xLabel]+ xLabels = vspace (fontSize/2)+ // sideBySide vcenter (map (drawBarLabel config) bars)++roll :: [SomeColor] -> [SomeColor]+roll colors | null colors = cycle [readColor "black"]+ | otherwise = cycle colors+ +drawDescr :: Config -> SomeColor -> Label -> Diagram+drawDescr Config{..} color string =+ block color fontSize fontSize <> text fontSize string++block :: SomeColor -> Double -> Double -> Diagram+block color width height = fillColor color $ roundRectF width height 0.1++drawBar :: Measurable a => Config -> Bar a -> Diagram+drawBar config@Config{..} Bar{..} =+ vcat . reverse $ zipWith (drawBlock config) (roll barColors) blocks++drawBlock :: Measurable a => Config -> SomeColor -> Block a -> Diagram++drawBlock Config{..} color (Value x) = hcatA bottom [bar, label]+ where+ bar = block color barRatio (ratio * size x)+ label = translateX (fontSize/2)+ . translateY (fontSize - ratio * size x)+ . ctext fontSize+ $ show x++drawBlock Config{..} color Interval{..} = hcatA bottom [bar, deviation, label]+ where+ bar = block color barRatio (ratio * size mean)+ deviation = translateY (-ratio * size lower) interval+ interval = vcat [bound,line,bound]+ bound = translateX (-fontSize/2) . straight $+ pathFromVectors [(fontSize,0)]+ line = straight $ pathFromVectors [(0,ratio * size (upper-lower))]+ label = translateX (-fontSize/2)+ . translateY (fontSize - ratio * size mean)+ . ctext fontSize+ $ show mean++ctext :: Double -> String -> Diagram+ctext size string = translateY (-size/2) $ text size string++drawBarLabel :: Config -> Bar a -> Diagram+drawBarLabel Config{..} Bar{..} = text fontSize label++sideBySide valign ds = hdistribA 1 valign bottom ds
+ src/Graphics/BarChart/Types.hs view
@@ -0,0 +1,225 @@+{-# 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{..}
+ src/barchart.hs view
@@ -0,0 +1,347 @@+{-# LANGUAGE NamedFieldPuns, RecordWildCards, DeriveDataTypeable #-}++import Graphics.BarChart hiding ( Intervals )+import Graphics.BarChart.Types ( readColor )++import Data.Char ( toLower )+import Data.Maybe ( fromMaybe )++import Control.Monad ( when, forM_ )++import System.FilePath++import System.Console.CmdArgs++import qualified Graphics.Rendering.Diagrams as D++data ExecMode+ = Blocks { out_file, title, xlabel, ylabel :: String,+ file_type :: FileType,+ division, colors :: String,+ width, height, label_size :: Int,+ bar_width :: Double,+ in_files :: [String] }+ | Intervals { out_file, title, xlabel, ylabel :: String,+ file_type :: FileType,+ division, colors :: String,+ width, height, label_size :: Int,+ bar_width :: Double,+ in_files :: [String] }+ | Criterion { breakdown :: Breakdown,++ out_file, title, xlabel, ylabel :: String,+ file_type :: FileType,+ division, colors :: String,+ width, height, label_size :: Int,+ bar_width :: Double,+ in_files :: [String] }+ | Progression { breakdown :: Breakdown,++ out_file, title, xlabel, ylabel :: String,+ file_type :: FileType,+ division, colors :: String,+ width, height, label_size :: Int,+ bar_width :: Double,+ in_files :: [String] }+ deriving (Show,Data,Typeable)++data FileType = Guess_File_Type | PNG | SVG | PDF | PS+ deriving (Eq,Show,Data,Typeable)++suffixOf :: FileType -> String+suffixOf PNG = "png"+suffixOf SVG = "svg"+suffixOf PDF = "pdf"+suffixOf PS = "ps"+suffixOf _ = ""++fromFileType :: FileType -> D.OutputType+fromFileType PNG = D.PNG+fromFileType SVG = D.SVG+fromFileType PDF = D.PDF+fromFileType PS = D.PS+fromFileType _ = error "fromFileType: cannot convert guessed file type"++replaceUnknownFileType :: FileType -> FileType -> FileType+replaceUnknownFileType Guess_File_Type t = t+replaceUnknownFileType t _ = t++data Breakdown = Summary | Summary_Comparison | Benchmark_Comparison+ deriving (Eq,Show,Data,Typeable)++blocksMode =+ Blocks {+ out_file = outFile conf+ &= text "Name of generated file"+ & typFile,+ file_type = Guess_File_Type `enum`+ [Guess_File_Type+ &= text "Guess output file type by name (default)",+ PNG &= text "Generate .png file",+ SVG &= text "Generate .svg file",+ PDF &= text "Generate .pdf file",+ PS &= text "Generate .ps file"],+ title = caption conf+ &= text "Title of bar chart"+ & typString,+ xlabel = xLabel conf+ &= text "Label of x axis"+ & typString,+ ylabel = xLabel conf+ &= text "Label of y axis"+ & typString,+ division = "" &= text "Labels separated by whitespace"+ & typStrings,+ colors = "" &= text "Color names separated by whitespace"+ & typStrings,+ width = width+ &= text "Width of generated bar chart"+ & typ "NUM",+ height = height+ &= text "Height of generated bar chart"+ & typ "NUM",+ label_size = 12 &= text "Font size used for labels"+ & typ "NUM",+ bar_width = barRatio conf+ &= text "Bar width between 0 and 1"+ & flag "W"+ & typ "FLOAT",+ in_files = [] &= text "CVS files with data to visualise"+ & args }+ where (width,height) = dimensions conf++-- cannot reuse flag attributes in cmdargs :(+intervalsMode =+ Intervals {+ out_file = outFile conf+ &= text "Name of generated file"+ & typFile,+ file_type = Guess_File_Type `enum`+ [Guess_File_Type+ &= text "Guess output file type by name (default)",+ PNG &= text "Generate .png file",+ SVG &= text "Generate .svg file",+ PDF &= text "Generate .pdf file",+ PS &= text "Generate .ps file"],+ title = caption conf+ &= text "Title of bar chart"+ & typString,+ xlabel = xLabel conf+ &= text "Label of x axis"+ & typString,+ ylabel = xLabel conf+ &= text "Label of y axis"+ & typString,+ division = "" &= text "Labels separated by whitespace"+ & typStrings,+ colors = "" &= text "Color names separated by whitespace"+ & typStrings,+ width = width+ &= text "Width of generated bar chart"+ & typ "NUM",+ height = height+ &= text "Height of generated bar chart"+ & typ "NUM",+ label_size = 12 &= text "Font size used for labels"+ & typ "NUM",+ bar_width = barRatio conf+ &= text "Bar width between 0 and 1"+ & flag "W"+ & typ "FLOAT",+ in_files = [] &= text "CVS files with data to visualise"+ & args }+ where (width,height) = dimensions conf++criterionMode =+ Criterion {+ out_file = outFile conf+ &= text "Name of generated file"+ & typFile,+ file_type = Guess_File_Type `enum`+ [Guess_File_Type+ &= text "Guess output file type by name (default)",+ PNG &= text "Generate .png file",+ SVG &= text "Generate .svg file",+ PDF &= text "Generate .pdf file",+ PS &= text "Generate .ps file"],+ title = caption conf+ &= text "Title of bar chart"+ & typString,+ xlabel = xLabel conf+ &= text "Label of x axis"+ & typString,+ ylabel = xLabel conf+ &= text "Label of y axis"+ & typString,+ division = "" &= text "Labels separated by whitespace"+ & typStrings,+ colors = "" &= text "Color names separated by whitespace"+ & typStrings,+ width = width+ &= text "Width of generated bar chart"+ & typ "NUM",+ height = height+ &= text "Height of generated bar chart"+ & typ "NUM",+ label_size = 12 &= text "Font size used for labels"+ & typ "NUM",+ bar_width = barRatio conf+ &= text "Bar width between 0 and 1"+ & flag "W"+ & typ "FLOAT",+ in_files = [] &= text "CVS files with data to visualise"+ & args,+ breakdown = Summary `enum`+ [Summary+ &= text "Show benchmark summary (default)",+ Summary_Comparison+ &= text "Compare different benchmark summaries"+ & flag "s",+ Benchmark_Comparison+ &= text "Compare different benchmarks"+ & flag "b"] }+ where (width,height) = dimensions conf++progressionMode =+ Progression {+ out_file = outFile conf+ &= text "Name of generated file"+ & typFile,+ file_type = Guess_File_Type `enum`+ [Guess_File_Type+ &= text "Guess output file type by name (default)",+ PNG &= text "Generate .png file",+ SVG &= text "Generate .svg file",+ PDF &= text "Generate .pdf file",+ PS &= text "Generate .ps file"],+ title = caption conf+ &= text "Title of bar chart"+ & typString,+ xlabel = xLabel conf+ &= text "Label of x axis"+ & typString,+ ylabel = xLabel conf+ &= text "Label of y axis"+ & typString,+ division = "" &= text "Labels separated by whitespace"+ & typStrings,+ colors = "" &= text "Color names separated by whitespace"+ & typStrings,+ width = width+ &= text "Width of generated bar chart"+ & typ "NUM",+ height = height+ &= text "Height of generated bar chart"+ & typ "NUM",+ label_size = 12 &= text "Font size used for labels"+ & typ "NUM",+ bar_width = barRatio conf+ &= text "Bar width between 0 and 1"+ & flag "W"+ & typ "FLOAT",+ in_files = [] &= text "CVS files with data to visualise"+ & args,+ breakdown = Summary_Comparison `enum`+ [Summary_Comparison+ &= text "Breakdown chart by benchmark summary (default)"+ & flag "s",+ Benchmark_Comparison+ &= text "Breakdown chart by benchmarks"+ & flag "b"] }+ where (width,height) = dimensions conf++typString = typ "STRING"+typStrings = typ "STRINGS"++execModes = [blocksMode &= defMode,+ intervalsMode, criterionMode, progressionMode]++exitIf msg cond = when cond (error msg)++main = do execMode <- cmdArgs "Bar Chart 0.0" (map mode execModes)+ exitIf "no input files given" $ null (in_files execMode)+ dispatch execMode++dispatch mode@Blocks{..} =+ forM_ in_files $ \in_file ->+ writeMultiBarChart+ (config (guessDefaults in_file mode))+ in_file+ (words division)++dispatch mode@Intervals{..} =+ forM_ in_files $ \in_file ->+ writeMultiBarIntervalChart+ (config (guessDefaults in_file mode))+ in_file+ (words division)++dispatch mode@Criterion{..} =+ case breakdown of+ Summary ->+ forM_ in_files $ \in_file ->+ writeCriterionChart+ (config (guessBenchmarkDefaults in_file mode))+ in_file+ Summary_Comparison ->+ writeComparisonChart False+ (config (guessBenchmarkDefaults "summaries.csv" mode))+ in_files+ Benchmark_Comparison ->+ writeComparisonChart True+ (config (guessBenchmarkDefaults "benchmarks.csv" mode))+ in_files++dispatch mode@Progression{..} =+ case breakdown of+ Summary_Comparison ->+ forM_ in_files $ \in_file ->+ writeProgressionChart True+ (config (guessBenchmarkDefaults in_file mode))+ in_file+ (words division)+ Benchmark_Comparison ->+ forM_ in_files $ \in_file ->+ writeProgressionChart False+ (config (guessBenchmarkDefaults in_file mode))+ in_file+ (words division)++guessDefaults :: FilePath -> ExecMode -> ExecMode+guessDefaults in_file = guessColors . guessTitle . guessFileType . guessOutFile+ where+ guessOutFile mode =+ mode { out_file = out_file mode ? replaceExtension in_file suffix }+ where suffix = suffixOf (file_type mode) ? ".png"++ guessFileType mode =+ mode { file_type = replaceUnknownFileType (file_type mode) $+ fromMaybe (error $ "unsupported type: " ++ suffix) $+ lookup suffix fileTypes }+ where suffix = map toLower $ takeExtension (out_file mode)+ fileTypes = [(".png",PNG),(".svg",SVG),(".pdf",PDF),(".ps",PS)]++ guessTitle mode =+ mode { title = title mode ? takeBaseName (out_file mode) }++ guessColors mode =+ mode { colors = colors mode ? "forestgreen firebrick midnightblue" }++guessBenchmarkDefaults in_file = guessAxis . guessDefaults in_file+ where+ guessAxis mode = mode { xlabel = xlabel mode ? "benchmark",+ ylabel = ylabel mode ? "run time" }++config mode = Config {+ outFile = out_file mode,+ outputType = fromFileType $ file_type mode,+ caption = title mode, xLabel = xlabel mode, yLabel = ylabel mode,+ barColors = map readColor . words $ colors mode,+ dimensions = (width mode,height mode),+ ratio = 1.0,+ fontSize = fromIntegral $ label_size mode,+ barRatio = bar_width mode }++(?) :: String -> String -> String+"" ? s = s+s ? _ = s