IFCNRUJED6H45DCCO63OOFB2TDAXH6QBB6YP6NFOCDED5QVVLO5QC assets;Total assets including retirementsuperannuation;Total assets including retirementassets;Total assetsliabilities;Total liabilitiesloan;Loan
{"$schema": "https://vega.github.io/schema/vega-lite/v5.json","name": "Wealth","title": {"text": "Total wealth","subtitle": "Total assets and liabilities (thousand $)"},"width": "container","height": "container","data": { "url": "../data/wealth.csv"},"transform": [{ "calculate": "datum.value / 1000", "as": "thousands" }],"layer": [{"encoding": {"x": {"field": "end_date","title": null,"type": "temporal","scale": { "domainMin": { "expr": "timeOffset('year', now(), -1)" } }},"y": {"field": "thousands","title": null,"type": "quantitative","aggregate": "sum","scale": { "zero": false }},"color": {"field": "account","type": "nominal","legend": { "title": null, "orient": "bottom" }}},"layer": [{"mark": "line","params": [{ "name": "grid", "select": "interval", "bind": "scales" }]},{"params": [{"name": "label","select": { "type": "point", "encodings": ["x"], "nearest": true, "on": "mouseover" }}],"mark": "point","encoding": { "opacity": { "condition": { "param": "label", "empty": false, "value": 1 }, "value": 0 } }}]},{"transform": [{"filter": {"param": "label", "empty": false}}],"layer": [{"mark": {"type": "rule", "color": "gray"},"encoding": {"x": {"type": "temporal", "field": "end_date", "aggregate": "min"}}},{"encoding": {"text": {"type": "quantitative", "field": "value", "aggregate": "sum", "format": "$0.3s"},"x": {"type": "temporal", "field": "end_date"},"y": {"type": "quantitative", "field": "thousands", "aggregate": "sum"},"color": {"type": "nominal", "field": "account"}},"layer": [{ "mark": {"type": "text", "align": "right", "dx": -5, "dy": -5, "strokeWidth": 2, "stroke": "white"} },{ "mark": {"type": "text", "align": "right", "dx": -5, "dy": -5} }]}]}]}
expenses;expensesrepayments;repaymentsrevenue;revenuerevenuesuper;revenuesuper
{"$schema": "https://vega.github.io/schema/vega-lite/v5.json","name": "SavingsGoals","title": {"text": "Savings goals","subtitle": "Percentage of revenues put into savings, averaged over the last half year"},"width": "container","height": "container","data": { "url": "../data/savings.csv"},"transform": [{"window": [{"op": "sum", "field": "value", "as": "rolling_sum"}],"groupby": ["account"],"frame": [-181, 0]},{"window": [{"op": "count", "as": "window_size"}],"groupby": ["account"],"frame": [-181, 0]},{ "filter": "datum.window_size >= 90" },{ "pivot": "account", "groupby": ["end_date"], "value": "rolling_sum" },{"calculate": "datum.revenuesuper != 0 ? (datum.revenuesuper - datum.expenses) / datum.revenuesuper : 0","as": "Including superannuation"},{"calculate": "datum.revenue != 0 ? (datum.revenue - datum.expenses) / datum.revenue : 0","as": "Savings"},{"calculate": "datum.revenue != 0 ? (datum.revenue - datum.repayments) / datum.revenue : 0","as": "Liquid savings"},{"fold": ["Savings", "Liquid savings", "Including superannuation"], "as": ["account", "value"]},{"calculate": "datum.value * 100", "as": "percentage"}],"layer": [{"encoding": {"x": {"field": "end_date","title": null,"type": "temporal","scale": { "domainMin": { "expr": "timeOffset('year', now(), -1)" } }},"y": {"field": "percentage","title": null,"type": "quantitative"},"color": {"field": "account","type": "nominal","legend": { "title": null, "orient": "bottom" }}},"layer": [{"mark": "line","params": [{ "name": "grid", "select": "interval", "bind": "scales" }]},{"params": [{"name": "label","select": { "type": "point", "encodings": ["x"], "nearest": true, "on": "mouseover" }}],"mark": "point","encoding": { "opacity": { "condition": { "param": "label", "empty": false, "value": 1 }, "value": 0 } }}]},{"transform": [{"filter": {"param": "label", "empty": false}}],"layer": [{"mark": {"type": "rule", "color": "gray"},"encoding": {"x": {"type": "temporal", "field": "end_date", "aggregate": "min"}}},{"encoding": {"text": {"type": "quantitative", "field": "value", "aggregate": "sum", "format": "0.1%"},"x": {"type": "temporal", "field": "end_date"},"y": {"type": "quantitative", "field": "percentage", "aggregate": "sum"},"color": {"type": "nominal", "field": "account"}},"layer": [{ "mark": {"type": "text", "align": "right", "dx": -5, "dy": -5, "strokeWidth": 2, "stroke": "white"} },{ "mark": {"type": "text", "align": "right", "dx": -5, "dy": -5} }]}]}]}
netposition;Net positionsuperannuation;Retirementnetposition;Totalsuperannuation;Total
{"$schema": "https://vega.github.io/schema/vega-lite/v5.json","name": "NetPosition","title": {"text": "Net position","subtitle": "Net position (thousand $)"},"width": "container","height": "container","data": { "url": "../data/netposition.csv"},"transform": [{ "calculate": "datum.value / 1000", "as": "thousands" }],"layer": [{"encoding": {"x": {"field": "end_date","title": null,"type": "temporal","scale": { "domainMin": { "expr": "timeOffset('year', now(), -1)" } }},"y": {"field": "thousands","title": null,"type": "quantitative","aggregate": "sum","scale": { "zero": false }},"color": {"field": "account","type": "nominal","legend": { "title": null, "orient": "bottom" }}},"layer": [{"mark": "line","params": [{ "name": "grid", "select": "interval", "bind": "scales" }]},{"params": [{"name": "label","select": { "type": "point", "encodings": ["x"], "nearest": true, "on": "mouseover" }}],"mark": "point","encoding": { "opacity": { "condition": { "param": "label", "empty": false, "value": 1 }, "value": 0 } }}]},{"transform": [{"filter": {"param": "label", "empty": false}}],"layer": [{"mark": {"type": "rule", "color": "gray"},"encoding": {"x": {"type": "temporal", "field": "end_date", "aggregate": "min"}}},{"encoding": {"text": {"type": "quantitative", "field": "value", "aggregate": "sum", "format": "$0.3s"},"x": {"type": "temporal", "field": "end_date"},"y": {"type": "quantitative", "field": "thousands", "aggregate": "sum"},"color": {"type": "nominal", "field": "account"}},"layer": [{ "mark": {"type": "text", "align": "right", "dx": -5, "dy": -5, "strokeWidth": 2, "stroke": "white"} },{ "mark": {"type": "text", "align": "right", "dx": -5, "dy": -5} }]}]}]}
liquidassets;Liquid assetsliquidliabilities;Short-term liabilitiesnetliquid;Net liquid assets
{"$schema": "https://vega.github.io/schema/vega-lite/v5.json","name": "NetLiquid","title": {"text": "Liquid assets","subtitle": "Liquid assets, net liquid assets, and short-term liabilities (thousand $)"},"width": "container","height": "container","data": { "url": "../data/netliquid.csv"},"transform": [{ "calculate": "datum.value / 1000", "as": "thousands" }],"layer": [{"encoding": {"x": {"field": "end_date","title": null,"type": "temporal","scale": { "domainMin": { "expr": "timeOffset('year', now(), -1)" } }},"y": {"field": "thousands","title": null,"type": "quantitative","aggregate": "sum"},"color": {"field": "account","type": "nominal","legend": { "title": null, "orient": "bottom" }}},"layer": [{"mark": "line","params": [{ "name": "grid", "select": "interval", "bind": "scales" }]},{"params": [{"name": "label","select": { "type": "point", "encodings": ["x"], "nearest": true, "on": "mouseover" }}],"mark": "point","encoding": { "opacity": { "condition": { "param": "label", "empty": false, "value": 1 }, "value": 0 } }}]},{"transform": [{"filter": {"param": "label", "empty": false}}],"layer": [{"mark": {"type": "rule", "color": "gray"},"encoding": {"x": {"type": "temporal", "field": "end_date", "aggregate": "min"}}},{"encoding": {"text": {"type": "quantitative", "field": "value", "aggregate": "sum", "format": "$0.3s"},"x": {"type": "temporal", "field": "end_date"},"y": {"type": "quantitative", "field": "thousands", "aggregate": "sum"},"color": {"type": "nominal", "field": "account"}},"layer": [{ "mark": {"type": "text", "align": "right", "dx": -5, "dy": -5, "strokeWidth": 2, "stroke": "white"} },{ "mark": {"type": "text", "align": "right", "dx": -5, "dy": -5} }]}]}]}
expenses;Total expensesrevenue;Total revenuerepayments;Expenses and repayments
{"$schema": "https://vega.github.io/schema/vega-lite/v5.json","name": "IncomeStatement","title": {"text": "Income statement","subtitle": "Weekly revenues and expenses, rolling average over a two-week window ($)"},"width": "container","height": "container","data": { "url": "../data/incomestatement.csv"},"transform": [{"window": [{"op": "mean", "field": "value", "as": "rolling_mean"}],"groupby": ["account"],"frame": [-13, 0]},{ "calculate": "datum.rolling_mean * 7", "as": "weekly" },{ "calculate": "datum.weekly / 1000", "as": "thousands" }],"layer": [{"encoding": {"x": {"field": "end_date","title": null,"type": "temporal","scale": { "domainMin": { "expr": "timeOffset('year', now(), -1)" } }},"y": {"field": "thousands","title": null,"type": "quantitative","aggregate": "sum"},"color": {"field": "account","type": "nominal","legend": { "title": null, "orient": "bottom" }}},"layer": [{"mark": "line","params": [{ "name": "grid", "select": "interval", "bind": "scales" }]},{"params": [{"name": "label","select": { "type": "point", "encodings": ["x"], "nearest": true, "on": "mouseover" }}],"mark": "point","encoding": { "opacity": { "condition": { "param": "label", "empty": false, "value": 1 }, "value": 0 } }}]},{"transform": [{"filter": {"param": "label", "empty": false}}],"layer": [{"mark": {"type": "rule", "color": "gray"},"encoding": {"x": {"type": "temporal", "field": "end_date", "aggregate": "min"}}},{"encoding": {"text": {"type": "quantitative", "field": "weekly", "aggregate": "sum", "format": "$0.3s"},"x": {"type": "temporal", "field": "end_date"},"y": {"type": "quantitative", "field": "thousands", "aggregate": "sum"},"color": {"type": "nominal", "field": "account"}},"layer": [{ "mark": {"type": "text", "align": "right", "dx": -5, "dy": -5, "strokeWidth": 2, "stroke": "white"} },{ "mark": {"type": "text", "align": "right", "dx": -5, "dy": -5} }]}]}]}
expensecategories;
{"$schema": "https://vega.github.io/schema/vega-lite/v5.json","name": "ExpenseCategories","title": {"text": "Expense breakdown","subtitle": "Amount per week ($)"},"data": { "url": "../data/expensecategories.csv"},"transform": [{ "calculate": "split(datum.account, ':', 2)", "as": "accountpair" },{"window": [{"op": "mean", "field": "value", "as": "rolling_mean"}],"groupby": ["accountpair"],"frame": [-13, 0]},{ "calculate": "datum.rolling_mean * 7", "as": "weekly" }],"facet": {"field": "accountpair.0","type": "ordinal","sort": { "op": "sum", "field": "weekly", "order": "descending" },"legend": { "title": null }},"columns": 2,"resolve": { "scale": { "y": "independent", "color": "independent" } },"params": [{"name": "grid","select": "interval","bind": "scales"}],"spec": {"width": 750,"height": 190,"mark": "line","encoding": {"x": {"field": "end_date","title": null,"type": "temporal","scale": { "domainMin": { "expr": "timeOffset('year', now(), -1)" } }},"y": {"field": "weekly","title": null,"type": "quantitative","stack": true},"color": {"field": "accountpair.1","type": "nominal","legend": { "title": null }}}}}
<html><head><title>Hledger charts</title><meta charset="utf-8"><script src="https://cdn.jsdelivr.net/npm/vega@5.21.0"></script><script src="https://cdn.jsdelivr.net/npm/vega-lite@5.2.0"></script><script src="https://cdn.jsdelivr.net/npm/vega-embed@6.20.2"></script><style>.halfchart { float: left; width: 50%; height: 50%; }.fullchart { float: left; width: 100%; height: 50%; }</style></head><body><div class="halfchart" id="vis0"></div><div class="halfchart" id="vis1"></div><div class="halfchart" id="vis2"></div><div class="halfchart" id="vis3"></div><div class="fullchart" id="vis4"></div><div id="vis5"></div><script type="text/javascript">vegaEmbed('#vis0', "reports/incomestatement.json");vegaEmbed('#vis1', "reports/savings.json");vegaEmbed('#vis2', "reports/netliquid.json");vegaEmbed('#vis3', "reports/netposition.json");vegaEmbed('#vis4', "reports/wealth.json");vegaEmbed('#vis5', "reports/expensecategories.json");</script></body></html>
#!/bin/shumask 077dir="$(mktemp -d "${TMPDIR:-/tmp}/hledger-vega.XXXXXXXXXX")"trap "rm -r $dir" EXITargsdir=./argsreportdir=./reportsoutdir=./data# initialize a semaphore with a given number of tokensopen_sem(){mkfifo $dir/pipe-$$exec 3<>$dir/pipe-$$rm $dir/pipe-$$local i=$1for((;i>0;i--)); doprintf %s 000 >&3done}# run the given command asynchronously and pop/push tokensrun_with_lock(){local x# this read waits until there is something to readread -u 3 -n 3 x && ((0==x)) || exit $x(( "$@"; )# push the return code of the command to the semaphoreprintf '%.3d' $? >&3)&}# Create the data directory if it doesn't existmkdir -p "$outdir"# Use 4 jobsN=4open_sem $Nhledger_with_name() {local namewithext="$(basename "$1")"local name="${namewithext%.*}"hledger balance --output-format=csv --layout=tidy --daily @"$1" \> "$dir/$name.csv"if [[ $(wc -l < "$dir/$name.csv") -le 1 ]]; then>&2 echo "Warning: hledger produced no output from \"$1\""fi}make_report() {local namewithext="$(basename "$1")"local name="${namewithext%.*}"local IFS=";"local first=1while read -a line; dolocal csv="$dir/${line[0]}.csv"local label="${line[1]}"{if [[ $first -eq 1 ]]; thencat "$csv"elsetail -n "+2" "$csv"fi} | sed -e "s/^\"...\"/\"$label\"/"first=0done < "$1" \> "$outdir/$name.csv"}for query in "$argsdir"/*.args; dorun_with_lock hledger_with_name "$query"donewaitfor report in "$reportdir"/*.report; dorun_with_lock make_report "$report"donewait
@import/cumulative.args^assets:.*retirement
@import/change.args--invert^(revenue|income)not::taxes
@import/change.args--invert^(revenue|income)not::retirement
@import/change.args--alias=/^liabilities:(.*:loan.*)/=expenses:\1^expenses
@import/cumulative.args^assetsnot:^assets:.*retirement^liabilities
@import/cumulative.args^assetsnot:^assets:.*prepaidsnot:^assets:.*bondsnot:^assets:.*retirement^liabilitiesnot:^liabilities:.*loan
@import/cumulative.args--invert^liabilities:.*loan
@import/cumulative.args--invert^liabilitiesnot:^liabilities:.*loan
@import/cumulative.args^assetsnot:^assets:.*prepaidsnot:^assets:.*bondsnot:^assets:.*retirement
@import/cumulative.args--invert^liabilities
--historical--value=end,$--depth=0
--value=then,$--cost--depth=0
@import/change.args^expenses
@import/change.args--alias=/^expenses:([^:]*)$/=expenses:\1:\1--alias=/^expenses$/=expenses:uncategorised:uncategorised--depth=3--drop=1^expenses
@import/cumulative.args^assets
all: vega vega-lite vega-embedvegaVersion = 5vegaLiteVersion = 5vegaEmbedVersion = 6vega: .FORCEcurl https://cdn.jsdelivr.net/npm/vega@$(vegaVersion) > vega@$(vegaVersion)vega-lite: .FORCEcurl https://cdn.jsdelivr.net/npm/vega-lite@$(vegaLiteVersion) > vega-lite@$(vegaLiteVersion)vega-embed: .FORCEcurl https://cdn.jsdelivr.net/npm/vega-embed@$(vegaEmbedVersion) > vega-embed@$(vegaEmbedVersion).PHONY: all .FORCE
.git.DS_Store