change to col-auto everywhere, with some more forcing of width maximums to make the page more consistent, redid defaults to incorporate updated per fortnight lease cost, and updated inflation figure/notes/references. Updated default Living expenses to 84000 to match latest view of data, fixed up compare_to to now work, shows a graph of data to compare with, allows drop-down to be compare_to nothing or a saved set, only 1 hand saved for now. Annotations on graph for large changes in savings now work and are legible - had to allow overlap and do some overly complex left/right up/down offsetting to make them all sensible

This commit is contained in:
2025-02-13 17:12:02 +11:00
parent 3cf1f1d4de
commit 14de3f1790
5 changed files with 148 additions and 128 deletions

26
calc.py
View File

@@ -1,9 +1,16 @@
# calc.py # calc.py
from datetime import datetime, timedelta from datetime import datetime, timedelta
def add_annotation(finance, dt, amt, text): def add_annotation(finance, dt, total, delta, text):
# dont add an annotation for small changes (jic)
if abs(delta) < 5000:
return
tm = dt.timestamp() * 1000 tm = dt.timestamp() * 1000
finance['annotations'].append( { 'label': text, 'x': tm, 'y': amt } ) if delta > 0:
text += f": ${int(abs(delta))}"
else:
text += f": -${int(abs(delta))}"
finance['annotations'].append( { 'label': text, 'x': tm, 'y': total } )
return return
def calculate_savings_depletion(finance): def calculate_savings_depletion(finance):
@@ -98,6 +105,7 @@ def calculate_savings_depletion(finance):
if D_Num_fortnights_pay == 0 and D_leave_after_tax > 0: if D_Num_fortnights_pay == 0 and D_leave_after_tax > 0:
print(f"D has resigned {current_date}: get paid out my 9 weeks leave and lose 45% to tax - ${D_leave_after_tax}" ) print(f"D has resigned {current_date}: get paid out my 9 weeks leave and lose 45% to tax - ${D_leave_after_tax}" )
current_savings += D_leave_after_tax current_savings += D_leave_after_tax
add_annotation(finance, current_date, current_savings, D_leave_after_tax, "D quit" )
D_leave_after_tax = 0 D_leave_after_tax = 0
if fortnight_income: if fortnight_income:
@@ -125,28 +133,28 @@ def calculate_savings_depletion(finance):
if current_date.date() == school_fees_date.date(): if current_date.date() == school_fees_date.date():
current_savings -= School_Fees current_savings -= School_Fees
add_annotation(finance, current_date, current_savings, f"Pay School Fees: ${School_Fees}") add_annotation(finance, current_date, current_savings, -School_Fees, "School Fees")
if current_date.date() == car_balloon_date.date(): if current_date.date() == car_balloon_date.date():
current_savings -= Car_balloon current_savings -= Car_balloon
add_annotation(finance, current_date, current_savings, f"car balloon paid: ${Car_balloon}" ) add_annotation(finance, current_date, current_savings, -Car_balloon, "car balloon")
# Anniversary of Car balloon so pay insurance/rego # Anniversary of Car balloon so pay insurance/rego
if current_date.year >= car_balloon_date.year and current_date.month == car_balloon_date.month and current_date.day == car_balloon_date.day: if current_date.year >= car_balloon_date.year and current_date.month == car_balloon_date.month and current_date.day == car_balloon_date.day:
current_savings -= post_lease_car_costs current_savings -= post_lease_car_costs
add_annotation(finance, current_date, current_savings, f"IONIQ 6 ins/rego: ${post_lease_car_costs}" ) add_annotation(finance, current_date, current_savings, -post_lease_car_costs, "IONIQ 6 ins/rego" )
if current_date.date() == overseas_trip_date.date(): if current_date.date() == overseas_trip_date.date():
current_savings -= Overseas_trip current_savings -= Overseas_trip
add_annotation(finance, current_date, current_savings, f"Overseas trip: ${Overseas_trip}") add_annotation(finance, current_date, current_savings, -Overseas_trip, "O/S trip")
if current_date.date() == mich_present_date.date(): if current_date.date() == mich_present_date.date():
current_savings -= Mich_present current_savings -= Mich_present
add_annotation(finance, current_date, current_savings, f"Michelle's present: ${Mich_present}") add_annotation(finance, current_date, current_savings, -Mich_present, "Mich's present" )
if current_date.date() == mark_reno_date.date(): if current_date.date() == mark_reno_date.date():
current_savings -= Mark_reno current_savings -= Mark_reno
add_annotation(finance, current_date, current_savings, f"Mark/reno costs: ${Mark_reno}") add_annotation(finance, current_date, current_savings, -Mark_reno, "Mark/reno" )
if current_savings < 0: if current_savings < 0:
depletion_date = current_date depletion_date = current_date
@@ -177,7 +185,7 @@ def calculate_savings_depletion(finance):
Sell_shares -= 1 Sell_shares -= 1
current_savings += actual_sell current_savings += actual_sell
add_annotation(finance, current_date, current_savings, f"Selling shares: ${int(actual_sell)}" ) add_annotation(finance, current_date, current_savings, actual_sell, "Sell shares" )
current_date += timedelta(days=1) current_date += timedelta(days=1)

44
db.py
View File

@@ -34,7 +34,8 @@ def init_db():
CBA_price REAL, CBA_price REAL,
Overseas_trip_date STRING, Overseas_trip_date STRING,
Mark_reno_date STRING, Mark_reno_date STRING,
Sell_shares INTEGER Sell_shares INTEGER,
compare_to INTEGER
)''') )''')
cur.execute('''CREATE TABLE IF NOT EXISTS comparison_set ( cur.execute('''CREATE TABLE IF NOT EXISTS comparison_set (
@@ -82,14 +83,15 @@ def init_db():
# Interest rate # Interest rate
# D_leave_owed_in_days # D_leave_owed_in_days
# maybe quarterly update Inflation? (this is harder to appreciate, seems much lower officialy than Savings, but which inflation: # maybe quarterly update Inflation? (this is harder to appreciate, seems much lower officialy than Savings, but which inflation:
# I've decided to consider food only or overall, whichever is worse - but only found a rabobank reference - THIS IS A MATERIAL ISSUE WITH PROJECTING OUT) # I've decided to consider food only or overall, whichever is worse - using this: https://tradingeconomics.com/australia/food-inflation
# FOR NOW inf=3 THIS IS A MATERIAL ISSUE WITH PROJECTING OUT)
### ###
cur.execute('''INSERT INTO finance (D_Salary, D_Num_fortnights_pay, School_Fees, Car_loan_via_pay, Car_loan, Car_balloon, Living_Expenses, Savings, Interest_Rate, cur.execute('''INSERT INTO finance (D_Salary, D_Num_fortnights_pay, School_Fees, Car_loan_via_pay, Car_loan, Car_balloon, Living_Expenses, Savings, Interest_Rate,
Inflation, Mich_present, Overseas_trip, Mark_reno, D_leave_owed_in_days, D_TLS_shares, M_TLS_shares, D_CBA_shares, TLS_price, CBA_price, Overseas_trip_date, Mark_reno_date, Sell_shares) Inflation, Mich_present, Overseas_trip, Mark_reno, D_leave_owed_in_days, D_TLS_shares, M_TLS_shares, D_CBA_shares, TLS_price, CBA_price, Overseas_trip_date, Mark_reno_date, Sell_shares, compare_to)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''',
(4762.29, 6, 22000, 620, 1001.12, 45824.68, 83000, 416670.67, 5.0, 3.9, 10000, 32000, 10000, 93.64, 1000, 750, 1095, 3.94, 158.57, '2025-06-01', '2025-09-01', 6)) (4762.29, 6, 22000, 620, 1017.99, 45824.68, 84000, 416670.67, 5.0, 3.0, 10000, 32000, 10000, 93.64, 1000, 750, 1095, 3.92, 167.03, '2025-06-01', '2025-09-01', 6, 0))
# NOTE: 1001.12 car-pay -- is 1017.99 (actual rate) - 16.87 (car park) # NOTE: 1017.99 car-lease (NEED TO VERIFY)
# NOTE: o/s trip. ~ $4kpp flights x3, then ~$3k / week in barcelona accom, $100pp/pd for food ($2k), + spending money # NOTE: o/s trip. ~ $4kpp flights x3, then ~$3k / week in barcelona accom, $100pp/pd for food ($2k), + spending money (could be less)
conn.commit() conn.commit()
conn.close() conn.close()
@@ -112,8 +114,8 @@ def get_budget_data(finance_data):
return BUDGET return BUDGET
def update_finance(data): def update_finance(data):
print(data)
conn = connect_db() conn = connect_db(False)
cur = conn.cursor() cur = conn.cursor()
cur.execute('''UPDATE finance SET cur.execute('''UPDATE finance SET
D_Salary = ?, D_Salary = ?,
@@ -137,14 +139,15 @@ def update_finance(data):
CBA_price = ?, CBA_price = ?,
Overseas_trip_date = ?, Overseas_trip_date = ?,
Mark_reno_date = ?, Mark_reno_date = ?,
Sell_shares = ? Sell_shares = ?,
compare_to = ?
WHERE id = 1''', data) WHERE id = 1''', data)
conn.commit() conn.commit()
conn.close() conn.close()
def insert_cset( data ): def insert_cset( data ):
conn = connect_db() conn = connect_db(False)
cur = conn.cursor() cur = conn.cursor()
cur.execute('''INSERT INTO comparison_set ( cur.execute('''INSERT INTO comparison_set (
name, D_Salary, D_Num_fortnights_pay, School_Fees, Car_loan_via_pay, name, D_Salary, D_Num_fortnights_pay, School_Fees, Car_loan_via_pay,
@@ -181,12 +184,12 @@ def get_comp_set_data(cset_id):
conn = connect_db(True) conn = connect_db(True)
cur = conn.cursor() cur = conn.cursor()
# HARDCODED FOR NOW
cset_id = 1
# get saved finance data for this comparison set # get saved finance data for this comparison set
cur.execute( f"select * from comparison_set where id = {cset_id}" ) cur.execute( f"select * from comparison_set where id = {cset_id}" )
COMP['vars']= dict(cur.fetchone()) res=cur.fetchone()
if not res:
return None
COMP['vars']= dict(res)
conn.close() conn.close()
# open new connection so we get rows back as basic array # open new connection so we get rows back as basic array
@@ -201,3 +204,14 @@ def get_comp_set_data(cset_id):
COMP['date'], COMP['amount'] = COMP['savings_data'][-1] COMP['date'], COMP['amount'] = COMP['savings_data'][-1]
return COMP return COMP
def get_comp_set_options(finance):
finance['COMP_SETS']=[ ( 0, 'Nothing' ) ]
# get comp data from DB (as object so dict conversion works below)
conn = connect_db(False)
cur = conn.cursor()
# get saved finance data for this comparison set
cur.execute( f"select id, name from comparison_set order by id" )
finance['COMP_SETS'].extend( cur.fetchall() )
return

View File

@@ -22,7 +22,7 @@ class FP_VAR(FP):
dot-notation of fields dot-notation of fields
""" """
def __init__(self, label, varname, display='', cl='col-2', datevarname=''): def __init__(self, label, varname, display='', cl='col-auto', datevarname=''):
### Var Attributes -- note, simple class, no methods ### ### Var Attributes -- note, simple class, no methods ###
self.label=label self.label=label
self.varname=varname self.varname=varname

22
main.py
View File

@@ -1,7 +1,7 @@
# main.py # main.py
from flask import Flask, render_template, request, redirect, url_for, Response from flask import Flask, render_template, request, redirect, url_for, Response
from calc import calculate_savings_depletion from calc import calculate_savings_depletion
from db import init_db, get_finance_data, update_finance, get_budget_data, get_comp_set_data from db import init_db, get_finance_data, update_finance, get_budget_data, get_comp_set_data, get_comp_set_options
from collections import defaultdict from collections import defaultdict
from datetime import datetime from datetime import datetime
import csv import csv
@@ -16,18 +16,15 @@ init_db()
@app.route('/') @app.route('/')
def index(): def index():
finance_data = get_finance_data() finance_data = get_finance_data()
get_comp_set_options(finance_data)
depletion_date, savings_per_fortnight, final_savings = calculate_savings_depletion(finance_data) depletion_date, savings_per_fortnight, final_savings = calculate_savings_depletion(finance_data)
BUDGET=get_budget_data(finance_data) BUDGET=get_budget_data(finance_data)
if depletion_date: if depletion_date:
depletion_date=depletion_date.date(); # just show date depletion_date=depletion_date.date(); # just show date
#HARDCODE HACK: # if we are comparing...(compare_to will be 0 / None to start with, and then COMP will be None
finance_data['comp_set']=1 COMP=get_comp_set_data(finance_data['compare_to'])
# we are comparing...
if finance_data['comp_set'] >= 1:
COMP=get_comp_set_data(finance_data['comp_set'])
DISP=[] DISP=[]
# Row 1 # Row 1
@@ -36,7 +33,7 @@ def index():
r.append( FP_VAR( 'Savings', 'Savings' ) ) r.append( FP_VAR( 'Savings', 'Savings' ) )
r.append( FP_VAR( 'Car Loan via Pay', 'Car_loan_via_pay', 'readonly' ) ) r.append( FP_VAR( 'Car Loan via Pay', 'Car_loan_via_pay', 'readonly' ) )
r.append( FP_VAR( 'Living Expenses', 'Living_Expenses' ) ) r.append( FP_VAR( 'Living Expenses', 'Living_Expenses' ) )
r.append( FP_VAR( 'Overseas Trip', 'Overseas_trip', 'date', 'col-4', 'Overseas_trip_date' ) ) r.append( FP_VAR( 'Overseas Trip', 'Overseas_trip', 'date', 'col-auto', 'Overseas_trip_date' ) )
DISP.append(r) DISP.append(r)
# Row 2 # Row 2
@@ -45,12 +42,12 @@ def index():
r.append( FP_VAR( 'Interest Rate', 'Interest_Rate' ) ) r.append( FP_VAR( 'Interest Rate', 'Interest_Rate' ) )
r.append( FP_VAR( 'Car Loan', 'Car_loan', 'readonly' ) ) r.append( FP_VAR( 'Car Loan', 'Car_loan', 'readonly' ) )
r.append( FP_VAR( 'Inflation', 'Inflation' ) ) r.append( FP_VAR( 'Inflation', 'Inflation' ) )
r.append( FP_VAR( 'Reno Costs', 'Mark_reno', 'date', 'col-4', 'Mark_reno_date' ) ) r.append( FP_VAR( 'Reno Costs', 'Mark_reno', 'date', 'col-auto', 'Mark_reno_date' ) )
DISP.append(r) DISP.append(r)
# Row 2 # Row 2
r=[] r=[]
r.append( FP_VAR( 'D leave owed (in days)', 'D_leave_owed_in_days' ) ) r.append( FP_VAR( 'D # days leave', 'D_leave_owed_in_days' ) )
r.append( FP_VAR( 'M TLS amount', 'M_TLS_shares' ) ) r.append( FP_VAR( 'M TLS amount', 'M_TLS_shares' ) )
r.append( FP_VAR( 'Car Balloon', 'Car_balloon', 'readonly' ) ) r.append( FP_VAR( 'Car Balloon', 'Car_balloon', 'readonly' ) )
DISP.append(r) DISP.append(r)
@@ -60,7 +57,7 @@ def index():
r.append( FP_VAR( 'D CBA amount', 'D_CBA_shares' ) ) r.append( FP_VAR( 'D CBA amount', 'D_CBA_shares' ) )
r.append( FP_VAR( 'D TLS amount', 'D_TLS_shares' ) ) r.append( FP_VAR( 'D TLS amount', 'D_TLS_shares' ) )
r.append( FP_VAR( 'Mich Present', 'Mich_present', 'readonly' ) ) r.append( FP_VAR( 'Mich Present', 'Mich_present', 'readonly' ) )
r.append( FP_VAR( 'Sell Shares for:', 'Sell_shares', 'select', 'offset-2 col-2' ) ) r.append( FP_VAR( 'Sell Shares for:', 'Sell_shares', 'select', 'col-auto' ) )
DISP.append(r) DISP.append(r)
# Row 4 # Row 4
@@ -100,6 +97,7 @@ def update():
request.form['Overseas_trip_date'], request.form['Overseas_trip_date'],
request.form['Mark_reno_date'], request.form['Mark_reno_date'],
request.form['Sell_shares'], request.form['Sell_shares'],
request.form['compare_to']
) )
update_finance(finance_data) update_finance(finance_data)
@@ -110,7 +108,7 @@ def update():
def download_csv(): def download_csv():
finance_data = get_finance_data() finance_data = get_finance_data()
depletion_date, savings_per_fortnight, final_savings, TLS, CBA = calculate_savings_depletion(finance_data) depletion_date, savings_per_fortnight, final_savings = calculate_savings_depletion(finance_data)
BUDGET=get_budget_data(finance_data) BUDGET=get_budget_data(finance_data)
# Group data by year # Group data by year

View File

@@ -8,38 +8,34 @@
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
<script src="https://code.highcharts.com/highcharts.js"></script> <script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/modules/annotations.js"></script> <script src="https://code.highcharts.com/modules/annotations.js"></script>
<script src="https://code.highcharts.com/modules/accessibility.js"></script>
<style> <style>
.col-form-label { .col-form-label { width:140px; }
width:170px;
}
</style> </style>
</head> </head>
<body> <body>
<div class="container-fluid"> <div class="containerfluid">
<h3 align="center">Finance Tracker</h3> <h3 align="center">Finance Tracker</h3>
<form id="vals_form" class="mt-3" action="/update" method="POST"> <form id="vals_form" class="ms-3 mt-3" action="/update" method="POST">
{% for r in DISP %} {% for r in DISP %}
<div class="row align-items-center"> <div class="row align-items-center">
{% for el in r %} {% for el in r %}
<div class="{{el.cl}}"> <div class="{{el.cl}}">
<div class="input-group"> <div class="input-group">
{% if el.display=="select" %} {% if el.display=="select" %}
<label for="{{el.varname}}" class="input-group-text">{{el.label}}</label> <label for="{{el.varname}}" class="col-form-label me-2 text-end float-end">{{el.label}}</label>
<select class="form-select border border-primary text-primary" id="{{el.varname}}" name="{{el.varname}}" onchange="this.form.submit()"> <select class="form-select border border-primary text-primary" id="{{el.varname}}" name="{{el.varname}}" style="width: 120px;" onchange="this.form.submit()">
<option value="0">Never</option> <option value="0">Never</option>
<option value="1">1 years</option> {% for el in range( 1,7 ) %}
<option value="2">2 years</option> <option value="{{el}}">{{el}} years</option>
<option value="3">3 years</option> {% endfor %}
<option value="4">4 years</option>
<option value="5">5 years</option>
<option value="6">6 years</option>
</select> </select>
{% elif el.display=="date" %} {% elif el.display=="date" %}
<label for="{{el.varname}}" class="col-form-label me-2 text-end float-end">{{el.label}}</label> <label for="{{el.varname}}" class="col-form-label me-2 text-end float-end">{{el.label}}</label>
<input type="number" step="any" class="form-control text-end float-end border border-primary" onchange="this.form.submit()" <input type="number" step="any" class="form-control text-end float-end border border-primary" onchange="this.form.submit()" style="max-width: 120px;"
id="{{el.varname}}" name="{{el.varname}}" value="{{ finance[el.varname] }}" {{el.display}}> id="{{el.varname}}" name="{{el.varname}}" value="{{ finance[el.varname] }}" {{el.display}}>
<input type="date" class="form-control text-end float-end border border-primary" id="{{el.datevarname}}" <input type="date" class="form-control text-end float-end border border-primary" id="{{el.datevarname}}" style="max-width: 150px;"
name="{{el.datevarname}}" value="{{ finance[el.datevarname] }}" onchange="this.form.submit()"> name="{{el.datevarname}}" value="{{ finance[el.datevarname] }}" onchange="this.form.submit()">
{% else %} {% else %}
<label for="{{el.varname}}" class="col-form-label me-2 text-end float-end">{{el.label}}</label> <label for="{{el.varname}}" class="col-form-label me-2 text-end float-end">{{el.label}}</label>
@@ -50,7 +46,7 @@
{% set bg="" %} {% set bg="" %}
{% set bd="border-1 border-primary" %} {% set bd="border-1 border-primary" %}
{% endif %} {% endif %}
<input type="number" step="any" class="{{bg}} form-control text-end float-end {{bd}}" onchange="this.form.submit()" <input type="number" step="any" class="{{bg}} form-control text-end float-end {{bd}}" onchange="this.form.submit()" style="max-width: 120px;"
id="{{el.varname}}" name="{{el.varname}}" value="{{ finance[el.varname] }}" {{el.display}}> id="{{el.varname}}" name="{{el.varname}}" value="{{ finance[el.varname] }}" {{el.display}}>
{% endif %} {% endif %}
</div> </div>
@@ -58,24 +54,23 @@
{% endfor %} {% endfor %}
</div> </div>
{% endfor %} {% endfor %}
</form>
<h5 align="center" class="mt-4">Fortnighthly Savings data: <h5 align="center" class="mt-4">Fortnighthly Savings data:
{% if COMP %} {% if COMP %}
<font color="blue">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Note: value in blue below is value we should have been at when comparing to saved values</font> {# get comparison date so we can use it below in loop to know when to print it out #}
{% set comp_yr=COMP['date'][:4] %}
{% set comp_mon=COMP['date'][5:7] %}
{% set comp_day=COMP['date'][8:10 ] %}
{% set comp_done=namespace( val=0 ) %}
{% else %}
{# we dont need to do a comparison, so consider it done before we begin #}
{% set comp_done=namespace( val=1 ) %}
{% endif %} {% endif %}
</h5> </h5>
<div class="row"> <div class="row">
<div class="col-auto"> <div class="pt-1 pb-1 mb-0 alert text-center" style="background:lemonchiffon">2025</div> <div class="col-auto"> <div class="pt-1 pb-1 mb-0 alert text-center" style="background:lemonchiffon">2025</div>
{# get comparison date so we can use it below in loop to know when to print it out #}
{% set comp_yr=COMP['date'][:4] %}
{% set comp_mon=COMP['date'][5:7] %}
{% set comp_day=COMP['date'][8:10 ] %}
{% set comp_done=namespace( val=0 ) %}
{% set first_yr=2025 %} {% set first_yr=2025 %}
{% for date, dollars in savings %} {% for date, dollars in savings %}
{% set yr=date[:4] %} {% set yr=date[:4] %}
@@ -103,6 +98,9 @@
{% else %} {% else %}
<div class="alert alert-success">Super kicks in!!!</div> <div class="alert alert-success">Super kicks in!!!</div>
{% endif %} {% endif %}
{% if COMP %}
<div class="alert alert-info">Note: value in blue<br>above is value we<br>should have been<br>at when comparing<br> to saved values</div>
{% endif %}
</div> </div>
<div class="col-auto"> <div class="col-auto">
<div class="alert alert-warning"> <div class="alert alert-warning">
@@ -115,18 +113,7 @@
</div> </div>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<button type="submit" class="btn btn-primary" onClick="$('#vals_form').submit()">Update</button> <button type="button" class="btn btn-primary" onClick="alert('not yet'); return false">Save</button>
</div>
<div class="col-auto">
<div class="input-group">
<button type="submit" class="disabled btn btn-primary" onClick="$('#vals_form').submit() disabled">Compare with:</button>
<select class="form-select border border-primary text-primary" id="comp_set" name="comp_set" onchange="">
<option value="0">None</option>
<option value="1">something</option>
</select>
</div>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary" onClick=" <button type="button" class="btn btn-primary" onClick="
$.ajax( { type: 'GET', url: '/download_csv', xhrFields: { responseType: 'blob' }, $.ajax( { type: 'GET', url: '/download_csv', xhrFields: { responseType: 'blob' },
success: function(res){ success: function(res){
@@ -142,24 +129,42 @@
console.log('done') } }) console.log('done') } })
"> Export to CSV </button> "> Export to CSV </button>
</div> </div>
<div class="col-auto">
<div class="input-group">
<button type="submit" class="disabled btn btn-primary" onClick="$('#vals_form').submit() disabled">Compare to:</button>
<select class="form-select border border-primary text-primary" id="compare_to" name="compare_to" onchange="$('#vals_form').submit()">
{% for el in finance['COMP_SETS'] %}
<option value="{{el[0]}}">{{el[1]}}</option>
{% endfor %}
</select>
</div>
</div>
</div> </div>
</form>
<div class="row mt-4" id="container" style="width:100%; height:400px;"></div> <div class="row mt-4" id="container" style="width:100%; height:400px;"></div>
<script type="text/javascript"> <script type="text/javascript">
window.onload = function() { window.onload = function() {
document.getElementById("Sell_shares").value = {{finance['Sell_shares']}}; $('#Sell_shares').val( {{finance['Sell_shares']}} )
}; $('#compare_to').val( {{finance['compare_to']}} )
};
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
// Parse the savings_data from Flask // Parse the savings_data from Flask
const savingsData = JSON.parse('{{ COMP['savings_data'] | tojson }}'); const savingsData = JSON.parse('{{ savings | tojson }}');
const chartData = savingsData.map(entry => [new Date(entry[0]).getTime(), parseFloat(entry[1])]); const chartData = savingsData.map(entry => [new Date(entry[0]).getTime(), parseFloat(entry[1])]);
{% if COMP %}
const compSavingsData = JSON.parse('{{ COMP['savings_data'] | tojson }}');
const compChartData = compSavingsData.map(entry => [new Date(entry[0]).getTime(), parseFloat(entry[1])]);
{% endif %}
// Get the legend name and vars for the tooltip // Get the legend name and vars for the tooltip
const legendName = '{{ COMP["vars"]["name"] }}'; const legendName = 'Savings';
const vars = JSON.parse('{{ COMP["vars"] | tojson }}'); const vars = JSON.parse('{{ finance | tojson }}');
// Tooltip content from vars // Tooltip content from vars
const tooltipContent = Object.entries(vars).map(([key, value]) => `${key}: ${value}`).join('<br>'); const tooltipContent = Object.entries(vars).map(([key, value]) => `${key}: ${value}`).join('<br>');
console.log(tooltipContent)
// Calculate plot bands for each year with alternating background colors // Calculate plot bands for each year with alternating background colors
const plotBands = []; const plotBands = [];
@@ -188,63 +193,58 @@
color: year % 2 === 0 ? 'white' : 'lemonchiffon' color: year % 2 === 0 ? 'white' : 'lemonchiffon'
}); });
// Add annotations for changes greater than 5000
const annotations = []; const annotations = [];
{% for a in finance['annotations'] %} var offset=13
console.log( "{{a['x']}}" ) var al='left'
console.log( "{{a['y']}}" ) var x=-130
console.log( "{{a['label']}}" ) var done=0
annotations.push({ {% if not COMP %}
labels: [{ // Add annotations for changes greater than 5000
point: { {% for a in finance['annotations'] %}
x: {{a['x']}}, annotations.push({
y: {{a['y']}}, labels: [{
xAxis: 0, point: {
yAxis: 0 x: {{a['x']}},
}, y: {{a['y']}},
text: '{{a['label']}}' crop: true,
}] xAxis: 0,
}); yAxis: 0
{% endfor %} },
x: x,
y: offset,
align: al,
text: '{{a['label']}}'
}], labelOptions: { allowOverlap: true }
});
if( offset == 150 ) { offset=100 } else { offset=150 }
if( done == 2 ) {
if( al=='right' ) { console.log('change to left'); al='left'; x=-130 } else { console.log('change to right'); al='right'; x=130 }
done=0
}
done++
{% endfor %}
document.keep = annotations
{% endif %}
// Highcharts configuration // Highcharts configuration
Highcharts.chart('container', { Highcharts.chart('container', {
chart: { chart: { type: 'line' },
type: 'line' title: { text: 'Savings Over Time' },
},
title: {
text: 'Savings Over Time'
},
xAxis: { xAxis: {
type: 'datetime', type: 'datetime',
title: { title: { text: 'Date' },
text: 'Date'
},
plotBands: plotBands // Alternating background for years plotBands: plotBands // Alternating background for years
}, },
yAxis: { yAxis: { title: { text: 'Amount ($)' } },
title: { legend: { labelFormatter: function () { return `<span title="${tooltipContent}">${legendName}</span>`; } },
text: 'Amount ($)' tooltip: { pointFormat: '{point.x:%Y-%m-%d}: <b>{point.y:.2f}</b>' },
}
},
legend: {
labelFormatter: function () {
return `<span title="${tooltipContent}">${legendName}</span>`;
}
},
tooltip: {
pointFormat: '{point.x:%Y-%m-%d}: <b>{point.y:.2f}</b>'
},
annotations: annotations, // Add annotations annotations: annotations, // Add annotations
series: [{ series: [
name: legendName, { name: legendName, data: chartData, marker: { radius: 2 } }
data: chartData, {% if COMP %}
marker: { ,{ name: "TEST", data: compChartData, marker: { radius: 2 } }
radius: 2, // Smaller points (default is 4) {% endif %}
lineWidth: 0, // Optional: thinner border ]
symbol: 'circle' // Optional: shape of the points (default is 'circle')
}
}]
}); });
}); });
</script> </script>