90 lines
2.9 KiB
Haskell
90 lines
2.9 KiB
Haskell
{-|
|
|
|
|
Generate balances pie chart
|
|
|
|
-}
|
|
|
|
module Commands.Chart
|
|
where
|
|
import Ledger.Utils
|
|
import Ledger.Types
|
|
import Ledger.Amount
|
|
import Ledger.AccountName
|
|
import Ledger.Transaction
|
|
import Ledger.Ledger
|
|
import Ledger.Commodity
|
|
import Options
|
|
|
|
import Control.Monad (liftM3)
|
|
import Graphics.Rendering.Chart
|
|
import Data.Colour
|
|
import Data.Colour.Names
|
|
import Data.Colour.RGBSpace
|
|
import Data.Colour.RGBSpace.HSL (hsl)
|
|
import Data.Colour.SRGB.Linear (rgb)
|
|
import Data.List
|
|
|
|
-- | Generate an image with the pie chart and write it to a file
|
|
chart :: [Opt] -> [String] -> Ledger -> IO ()
|
|
chart opts args l = renderableToPNGFile (toRenderable chart) w h filename
|
|
where
|
|
chart = genPie opts args l
|
|
filename = getOption opts ChartOutput "hledger.png"
|
|
(w,h) = parseSize $ getOption opts ChartSize "1024x1024"
|
|
|
|
-- | Extract string option value from a list of options or use the default
|
|
getOption :: [Opt] -> (String->Opt) -> String -> String
|
|
getOption opts opt def =
|
|
case reverse $ optValuesForConstructor opt opts of
|
|
[] -> def
|
|
x:_ -> x
|
|
|
|
-- | Parse image size from a command-line option
|
|
parseSize :: String -> (Int,Int)
|
|
parseSize str = (read w, read h)
|
|
where
|
|
x = fromMaybe (error "Size should be in WIDTHxHEIGHT format") $ findIndex (=='x') str
|
|
(w,_:h) = splitAt x str
|
|
|
|
-- | Generate pie chart
|
|
genPie :: [Opt] -> [String] -> Ledger -> PieLayout
|
|
genPie opts _ l = defaultPieLayout
|
|
{ pie_background_ = solidFillStyle $ opaque $ white
|
|
, pie_plot_ = pie_chart }
|
|
where
|
|
pie_chart = defaultPieChart { pie_data_ = items, pie_colors_ = mkColours}
|
|
items = mapMaybe (uncurry accountPieItem) $
|
|
flatten $
|
|
balances $
|
|
ledgerAccountTree (fromMaybe 99999 $ depthFromOpts opts) $ cacheLedger' l
|
|
|
|
-- | Convert all quantities of MixedAccount to a single commodity
|
|
amountValue :: MixedAmount -> Double
|
|
amountValue = quantity . convertMixedAmountTo unknown
|
|
|
|
-- | Generate a tree of account names together with their balances.
|
|
-- The balance of account is decremented by the balance of its subaccounts
|
|
-- which are drawn on the chart.
|
|
balances :: Tree Account -> Tree (AccountName, Double)
|
|
balances (Node rootAcc subAccs) = Node newroot newsubs
|
|
where
|
|
newroot = (aname rootAcc,
|
|
amountValue $
|
|
abalance rootAcc - (sum . map (abalance . root)) subAccs)
|
|
newsubs = map balances subAccs
|
|
|
|
-- | Build a single pie chart item
|
|
accountPieItem :: AccountName -> Double -> Maybe PieItem
|
|
accountPieItem accname balance =
|
|
if balance == 0
|
|
then Nothing
|
|
else Just $ PieItem accname 0 balance
|
|
|
|
-- | Generate an infinite color list suitable for charts.
|
|
mkColours :: [AlphaColour Double]
|
|
mkColours = cycle $ [opaque $ rgbToColour $ hsl h s l | (h,s,l) <- liftM3 (,,)
|
|
[100] [0.7] [0.2,0.3..0.6] ]
|
|
|
|
rgbToColour :: (Fractional a) => RGB a -> Colour a
|
|
rgbToColour (RGB r g b) = rgb r g b
|