Report Scripts for Ledger CLI with Gnuplot

I’ve been using Ledger CLI for a year now. It’s really a feature-rich command-line double-entry accounting tool, but the only one thing missed is data visualization. Although Ledger has a report script, it’s not very useful for me and the default output of Gnuplot is ugly. So I spent some time digging into Gnuplot and made 6 scripts based on Ledger’s original one.

Monthly Income and Expenses

monthlyio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/sh

if [-z "$LEDGER_TERM" ]; then
LEDGER_TERM="qt size 1280,720 persist"
fi

ledger -j reg ^Income -M --collapse --plot-amount-format="%(format_date(date, \"%Y-%m-%d\")) %(abs(quantity(scrub(display_amount))))\n" > ledgeroutput1.tmp
ledger -j reg ^Expenses -M --collapse > ledgeroutput2.tmp

(cat <<EOF) | gnuplot
set terminal $LEDGER_TERM
set style data histogram
set style histogram clustered gap 1
set style fill transparent solid 0.4 noborder
set xtics nomirror scale 0 center
set ytics add ('' 0) scale 0
set border 1
set grid ytics
set title "Monthly Income and Expenses"
set ylabel "Amount"
plot "ledgeroutput1.tmp" using 2:xticlabels(strftime('%b', strptime('%Y-%m-%d', strcol(1)))) title "Income" linecolor rgb "light-salmon", '' using 0:2:2 with labels left font "Courier,8" rotate by 15 offset -4,0.5 textcolor linestyle 0 notitle, "ledgeroutput2.tmp" using 2 title "Expenses" linecolor rgb "light-green", '' using 0:2:2 with labels left font "Courier,8" rotate by 15 offset 0,0.5 textcolor linestyle 0 notitle
EOF

rm ledgeroutput*.tmp

This is a typical graph for comparing total income and expenses of every month. I set the amount format of income to absolute value so it can appear on the Y axis as expenses side by side for easy comparison.

Monthly what?

monthlywhat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/sh

if [-z "$LEDGER_TERM" ]; then
LEDGER_TERM="qt size 1280,720 persist"
fi

ledger -j reg "$@" -M --collapse --plot-amount-format="%(format_date(date, \"%Y-%m-%d\")) %(abs(quantity(scrub(display_amount))))\n" > ledgeroutput1.tmp

(cat <<EOF) | gnuplot
set terminal $LEDGER_TERM
set style data histogram
set style histogram clustered gap 1
set style fill transparent solid 0.4 noborder
set xtics nomirror scale 0 center
set ytics add ('' 0) scale 0
set border 1
set grid ytics
set title "Monthly $@"
set ylabel "Amount"
plot "ledgeroutput1.tmp" using 2:xticlabels(strftime('%b', strptime('%Y-%m-%d', strcol(1)))) notitle linecolor rgb "light-turquoise", '' using 0:2:2 with labels font "Courier,8" offset 0,0.5 textcolor linestyle 0 notitle
EOF

rm ledgeroutput*.tmp

This script will plot a monthly histogram of any account we specify. When executing, an account name must be followed as parameter. For example, if Expenses:Eating is followed, the script will show us how much we spent on Eating in every month.

Histogram of what?

histogramof

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/sh

if [-z "$LEDGER_TERM" ]; then
LEDGER_TERM="qt size 1280,720 persist"
fi

ledger -J bal "$@" --sort="-abs(amount)" --flat --no-total --plot-total-format="%(partial_account(options.flat)) %(abs(quantity(scrub(total))))\n" > ledgeroutput1.tmp

(cat <<EOF) | gnuplot
set terminal $LEDGER_TERM
set style data histogram
set style histogram clustered gap 1
set style fill transparent solid 0.4 noborder
set xtics nomirror scale 0 rotate by -45
set ytics add ('' 0) scale 0
set border 1
set grid ytics
set title "Histogram of $@"
set ylabel "Amount"
plot "ledgeroutput1.tmp" using 2:xticlabels(1) notitle linecolor rgb "light-turquoise", '' using 0:2:2 with labels font "Courier,8" offset 0,0.5 textcolor linestyle 0 notitle
EOF

rm ledgeroutput*.tmp

This script will plot a histogram of any account we specify, not monthly, but with sub accounts. When executing, account name and/or period shall be followed as parameter. For example, if followed by Expenses -p oct, the script will show us what exactly took our money in October, from the most to the least.

Cashflow

cashflow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/sh

if [-z "$LEDGER_TERM" ]; then
LEDGER_TERM="qt size 1280,720 persist"
fi

ledger -J reg ^Income -M --collapse --plot-total-format="%(format_date(date, \"%Y-%m-%d\")) %(abs(quantity(scrub(display_total))))\n" > ledgeroutput1.tmp
ledger -J reg ^Expenses -M --collapse > ledgeroutput2.tmp

(cat <<EOF) | gnuplot
set terminal $LEDGER_TERM
set xdata time
set timefmt "%Y-%m-%d"
set xrange ["$(date --date='last year'+%Y)-12-20":"$(date +%Y)-12-10"]
set xtics nomirror "$(date +%Y)-01-01",2592000 format "%b"
unset mxtics
set mytics 2
set grid xtics ytics mytics
set title "Cashflow"
set ylabel "Accumulative Income and Expenses"
set style fill transparent solid 0.6 noborder
plot "ledgeroutput1.tmp" using 1:2 with filledcurves x1 title "Income" linecolor rgb "light-salmon", '' using 1:2:2 with labels font "Courier,8" offset 0,0.5 textcolor linestyle 0 notitle, "ledgeroutput2.tmp" using 1:2 with filledcurves y1=0 title "Expenses" linecolor rgb "seagreen", '' using 1:2:2 with labels font "Courier,8" offset 0,0.5 textcolor linestyle 0 notitle
EOF

rm ledgeroutput*.tmp

We can see how our Income and Expenses grow throughout the year with this script. If the gap between goes bigger, means we’re getting more wealthy. If the gap goes smaller, we should really pay good attention to it.

Wealthgrow

wealthgrow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/sh

if [-z "$LEDGER_TERM" ]; then
LEDGER_TERM="qt size 1280,720 persist"
fi

ledger -J reg ^Assets -M --collapse > ledgeroutput1.tmp
ledger -J reg ^Liabilities -M --collapse --plot-total-format="%(format_date(date, \"%Y-%m-%d\")) %(abs(quantity(scrub(display_total))))\n" > ledgeroutput2.tmp

(cat <<EOF) | gnuplot
set terminal $LEDGER_TERM
set xdata time
set timefmt "%Y-%m-%d"
set xrange ["$(date --date='last year'+%Y)-12-20":"$(date +%Y)-12-10"]
set xtics nomirror "$(date +%Y)-01-01",2592000 format "%b"
unset mxtics
set mytics 2
set grid xtics ytics mytics
set title "Wealthgrow"
set ylabel "Amount"
set style fill transparent solid 0.6 noborder
plot "ledgeroutput1.tmp" using 1:2 with filledcurves x1 title "Assets" linecolor rgb "goldenrod", '' using 1:2:2 with labels font "Courier,8" offset 0,0.5 textcolor linestyle 0 notitle, "ledgeroutput2.tmp" using 1:2 with filledcurves y1=0 title "Liabilities" linecolor rgb "violet", '' using 1:2:2 with labels font "Courier,8" offset 0,0.5 textcolor linestyle 0 notitle
EOF

rm ledgeroutput*.tmp

This script helps us to see how our Assets and Liabilities change throughout the year. The gap between represents net wealth. It’s a good thing to see the gap goes bigger.

Mega Expenses

megaexpenses

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/sh

if [-z "$LEDGER_TERM" ]; then
LEDGER_TERM="qt size 1280,720 persist"
fi

THIS_MONTH=`date +%m`

hledger bal -M ^Expenses --cumulative --no-total | sed 's/¥/ /g; 1,2d; 4d; $d; 3s/ /x/2; s/ //1; s/||//g' > ledgeroutput1.tmp

(cat <<EOF) | gnuplot
set terminal $LEDGER_TERM
set style data linespoints
set key outside horizontal Left samplen 2 autotitle columnhead
set xtics nomirror scale 0 rotate by -45
set ytics scale 0
set grid ytics
set border 1
set ylabel "Amount"
plot "ledgeroutput1.tmp" using 2:xticlabels(1) title "Jan", for [i=3:$THIS_MONTH+1] '' using i title strftime('%b', strptime('%m', sprintf("%i", i-1)))
EOF

rm ledgeroutput*.tmp

As you can see the graph is a little complicated, the X axis lists all the expenses, Y axis is amount accumulated month by month. Every line represents the total amount until that month. The script require a Haskell reimplementation of Leger - hledger, in order to work.

I don’t konw how to describe it appropriately, as it shows every month’s detailed expenses, so I name it Mega Expenses. It’s a clutter to see as is, but when using an interactive terminal, reveal month by month, we are able to figure out some unusual expenses that jump too high than previous steps.

Some Notes

First, all scripts assume that the default account names are Income, Expenses, Assets, Liabilities. If yours are not, change them accordingly.

Second, all scripts assume journal files keep in a yearly basis, i.e. every year’s journal should be a separate file. There’s no restrictions on reporting period, you may need to add some parameters to restrict the output to a specific period.

Third, multi-currency is not taking into consideration. If you have multi-currency, amend the scripts and add parameters just like you would do with Ledger.