diff options
| author | Camil Staps | 2020-06-09 12:50:57 +0200 | 
|---|---|---|
| committer | Camil Staps | 2020-06-09 12:50:57 +0200 | 
| commit | 90f2c775e14673a4d84d9d89629ff2edc44b8144 (patch) | |
| tree | 8430a3977b6e211deebf7af7ba477cdd7e30598e | |
Initial commit
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | README.md | 15 | ||||
| -rwxr-xr-x | plotasn.py | 118 | 
3 files changed, 134 insertions, 0 deletions
| diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..afed073 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.csv diff --git a/README.md b/README.md new file mode 100644 index 0000000..f9a8690 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# plotasn.py + +This is a simple python application to plot the balance of your [ASN][] bank +accounts. It combines lines of the individual accounts and their total sum in a +single graph. + +Download CSV exports from the online environment and run: + +```bash +./plotasn.py account-1.csv [account-2.csv ...] +``` + +This is tool is hereby placed in the public domain. + +[ASN]: https://www.asnbank.nl/ diff --git a/plotasn.py b/plotasn.py new file mode 100755 index 0000000..0296ba5 --- /dev/null +++ b/plotasn.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 + +from argparse import ArgumentParser +import csv +import datetime as dt +import matplotlib.pyplot as plt +import matplotlib.dates as mdates +import numpy as np + +class Record: +    def __init__(self, date, balance): +        if type(date) == str: +            self.datetime = dt.datetime.strptime(date, '%d-%m-%Y') +        else: +            self.datetime = date +        self.balance = balance + +    def __repr__(self): +        return str(self.datetime) + ': ' + str(self.balance) + +def read_csv(f): +    """Read a CSV file into a list of records.""" +    records = [] +    with open(f) as csvfile: +        rdr = csv.reader(csvfile) +        for row in rdr: +            before = float(row[8]) +            after = before + float(row[10]) +            records.append(Record(row[11], before)) +            records.append(Record(row[11], after)) +    return records + +def remove_day_internal_records(records): +    """Keeps only the first and last record for each day.""" +    i = 0 +    while i < len(records): +        if i + 2 >= len(records): +            break +        elif records[i].datetime == records[i+2].datetime: +            del records[i+1] +        else: +            records[i+1].datetime = records[i+1].datetime.replace(hour=23, minute=59, second=59) +            i += 2 +    if i + 1 < len(records): +        records[i+1].datetime = records[i+1].datetime.replace(hour=23, minute=59, second=59) + +def add_limits(record_lists): +    """Add records for the start and end of all record lists. + +    This fixes files that do not cover the same interval. +    """ +    mindate = record_lists[0][0].datetime +    maxdate = record_lists[0][-1].datetime + +    for rs in record_lists: +        mindate = min(mindate, rs[0].datetime) +        maxdate = max(maxdate, rs[-1].datetime) + +    for rs in record_lists: +        # We add two because this still goes through remove_day_internal_records +        rs.insert(0, Record(mindate, rs[0].balance)) +        rs.insert(0, Record(mindate, rs[0].balance)) +        rs.append(Record(maxdate, rs[-1].balance)) +        rs.append(Record(maxdate, rs[-1].balance)) + +def sum_records(axes_lists): +    """Return a new axes pair for the sum of all given axes pairs.""" +    for i in range(len(axes_lists)): +        axes_lists[i][0] = [x.timestamp() for x in axes_lists[i][0]] +    xs = np.unique(np.concatenate([xy[0] for xy in axes_lists])) +    yss = [np.interp(xs, xy[0], xy[1], left=0, right=0) for xy in axes_lists] +    ys = sum(yss) +    return [dt.datetime.fromtimestamp(x) for x in xs], ys + +def records_to_axes(records): +    """Convert a list of records to an axes pair.""" +    xs = [] +    ys = [] +    for r in records: +        xs.append(r.datetime) +        ys.append(r.balance) +    return xs, ys + +def plot_files(files): +    record_lists = [read_csv(f) for f in files] +    add_limits(record_lists) + +    axes_lists = [] +    for rs in record_lists: +        remove_day_internal_records(rs) +        xs, ys = records_to_axes(rs) +        axes_lists.append([xs,ys]) + +    i = 0 +    for xs, ys in axes_lists: +        plt.plot(xs, ys, label=files[i]) +        i += 1 + +    if len(files) > 1: +        xs, ys = sum_records(axes_lists) +        plt.plot(xs, ys, label='Sum') + +    plt.legend() +    plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d')) +    plt.gca().set_ylim((0, None)) +    plt.gcf().autofmt_xdate() +    plt.show() + +def main(): +    p = ArgumentParser(description='Plot balances of ASN bank accounts') +    p.add_argument('files', metavar='FILE', type=str, nargs='+', +            help='a CSV export of an ASN bank account') +    args = p.parse_args() + +    plot_files(args.files) + +if __name__ == '__main__': +    main() | 
