web: cursed plotting intensifies

master
Kasownik 2019-10-03 13:42:18 +02:00
parent 0d674af6a4
commit 05301b4b68
8 changed files with 128 additions and 132 deletions

View File

@ -0,0 +1,56 @@
with transfer2 as (
select
date_trunc('month', date) as dt,
-- case type when 'IN' then amount when 'OUT' then -amount when 'BANK_FEE' then -amount else NULL end as amount_rel,
case type when 'IN' then amount when 'OUT' then -amount when 'BANK_FEE' then -amount when 'IN_FROM_OWN' then amount when 'OUT_TO_OWN' then -amount else NULL end as amount_rel,
currency,
to_name
from raw_transfer
where
on_account in ('PL48195000012006000648890002', 'PL91195000012006000648890004', 'PL64195000012006000648890005', 'PL21195000012006000648890003')
-- on_account in ('PL48195000012006000648890002', 'PL91195000012006000648890004', 'PL64195000012006000648890005')
-- on_account in ('PL48195000012006000648890002') --, 'PL91195000012006000648890004') --, 'PL64195000012006000648890005')
and (type not in ('IN_FROM_OWN', 'OUT_FROM_OWN') or (from_account in ('PL02114010100000541244001001', 'PL72114010100000541244001002', 'PL45114010100000541244001003', 'PL18114010100000541244001004')))
and title not like 'Lokata nr DP%'
),
transfer3 as (
select dt, case currency when 'PLN' then amount_rel when 'EUR' then amount_rel * :EURPLN_RATE else NULL end as amount_c, to_name from transfer2
),
monthly_incomes as (
select
dt,
sum(amount_c)/100.0 as month_balance_i
from transfer3
where to_name not like '%PSP Zjednoczenie%' and amount_c > 0
group by dt order by dt
),
monthly_rest as (
select
dt,
sum(amount_c)/100.0 as month_balance_r
from transfer3
where to_name not like '%PSP Zjednoczenie%' and amount_c < 0
group by dt order by dt
),
monthly_psp as (
select
dt,
sum(amount_c)/100.0 as month_balance_p
from transfer3
where to_name like '%PSP Zjednoczenie%'
group by dt order by dt
),
joined as (
select
coalesce(monthly_rest.dt,monthly_psp.dt,monthly_incomes.dt) as dt,
coalesce(month_balance_i, 0) as mi,
coalesce(month_balance_r, 0) as mr,
coalesce(month_balance_p, 0) as mp
from monthly_rest full outer join monthly_psp on monthly_psp.dt = monthly_rest.dt full outer join monthly_incomes on monthly_incomes.dt = monthly_rest.dt
where
coalesce(monthly_rest.dt,monthly_psp.dt,monthly_incomes.dt) >= date(:START_DATE)
)
select
dt, mi, mr, mp,
sum(mr + mi + mp) over (order by dt asc rows between unbounded preceding and current row)
from joined;

1
web/webapp/static/css/c3.min.css vendored Normal file
View File

@ -0,0 +1 @@
.c3 svg{font:10px sans-serif;-webkit-tap-highlight-color:transparent}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc rect{stroke:#fff;stroke-width:1}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:grey;font-size:2em}.c3-line{stroke-width:1px}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:1;fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-title{font:14px sans-serif}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #ccc}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#fff}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip .value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:#fff}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max{fill:#777}.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000}.c3-chart-arc.c3-target g path{opacity:1}.c3-chart-arc.c3-target.c3-focused g path{opacity:1}.c3-drag-zoom.enabled{pointer-events:all!important;visibility:visible}.c3-drag-zoom.disabled{pointer-events:none!important;visibility:hidden}.c3-drag-zoom .extent{fill-opacity:.1}

2
web/webapp/static/js/c3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
web/webapp/static/js/d3.v5.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,130 +1,52 @@
var urlBase = '/api/';
var months_back = 28;
var populate_month_callback = function(i, d, url, year, month, required, paid) {
var populate_month = function(data) {
var res = data.content,
date = new Date(year, month, 1);
console.log("month " + year + " " + month);
required.unshift({ x: date.getTime() / 1000, y: res.required });
paid.unshift({ x: date.getTime() / 1000, y: res.paid });
i = i - 1;
var width = 100 - (i * (100.0 / months_back));
$("#loadprogress .progress-bar").attr("style", "width: " + width + "%");
if (i == 0)
{
d.resolve(data.modified);
}
else
{
month -= 1;
if(month == 0) {
month = 12;
year -= 1;
}
url = urlBase + 'month/' + year + '/' + month + '.json';
$.getJSON(url, populate_month_callback(i, d, url,
year, month,
required, paid));
}
};
return populate_month;
};
var populate_influx_callback = function(i, d, url, year, month, influx) {
var populate_influx = function(data) {
var res = data.content,
date = new Date(year, month, 1);
influx.unshift({ x: date.getTime() / 1000, y: res.in });
i = i - 1;
if (i == 0)
{
d.resolve();
}
else
{
month -= 1;
if(month == 0) {
month = 12;
year -= 1;
}
url = urlBase + 'cashflow/' + year + '/' + month + '.json';
$.getJSON(url, populate_influx_callback(i, d, url,
year, month,
influx));
}
};
return populate_influx;
};
$(window).load(function() {
var required = [], paid = [], influx = [];
var today = new Date(),
year = 1900 + today.getYear(),
month = today.getMonth() + 1;
var d1 = $.Deferred();
var url1 = urlBase + 'month/' + year + '/' + month + '.json';
$.getJSON(url1, populate_month_callback(months_back, d1, url1,
year, month,
required, paid));
var d2 = $.Deferred();
var url2 = urlBase + 'cashflow/' + year + '/' + month + '.json';
$.getJSON(url2, populate_influx_callback(months_back, d2, url2,
year, month, influx));
$.when(d1, d2).then(function(modified) {
$("#lastmod").text("Last modified: " + modified);
$.getJSON('/cursed-plot.json', function (data) {
$("#loadprogress").hide();
var palette = new Rickshaw.Color.Palette( { scheme: 'munin' } );
var graph = new Rickshaw.Graph({
element: document.getElementById("plot"),
width: $("#plot").width(),
height: $("#plot").width()*0.4,
renderer: 'line',
series: [
{
color: palette.color(),
data: required,
name: 'Required',
},
{
color: palette.color(),
data: paid,
name: 'Paid',
},
{
color: palette.color(),
data: influx,
name: 'Cash Influx',
},
]
});
graph.render();
/*var rent = data.map(item => { return { y: item.mp, x: new Date(item.dt) / 1000.0 } });
var others = data.map(item => { return { y: item.mr, x: new Date(item.dt) / 1000.0 } });
var sum = data.map(item => { return { y: item.mr + item.mp, x: new Date(item.dt) / 1000.0 } });*/
const chart = c3.generate({
bindto: '#chart',
data: {
x: 'x',
columns: [
['x', ...data.map(item => new Date(item.dt))],
['rent', ...data.map(item => item.mp)],
['incomes', ...data.map(item => item.mi)],
['others', ...data.map(item => item.mr)],
['sum', ...data.map(item => item.mp + item.mr + item.mi)],
// ['rolling', ...data.map(item=>item.sum)],
],
names: {
'rent': 'Rent, heating, power',
'incomes': 'Incomes (memberships & bgp.wtf)',
'others': 'Other expenses',
'sum': 'Monthly balance',
},
groups: [
['rent', 'incomes', 'others'],
],
type: 'bar',
types: {
sum: 'line',
},
},
grid: {
y: {
lines: [
{value: 0},
],
},
},
axis: {
x: {
type: 'timeseries',
tick: {
format: '%Y-%m'
}
}
}
});
var yAxis = new Rickshaw.Graph.Axis.Y({
graph: graph,
});
yAxis.render();
var xAxis = new Rickshaw.Graph.Axis.Time({
graph: graph,
});
xAxis.render();
var legend = new Rickshaw.Graph.Legend({
element: document.getElementById("legend"),
graph: graph,
});
var hoverDetail = new Rickshaw.Graph.HoverDetail( {
graph: graph,
xFormatter: function(x) {
var date = new Date(x * 1000);
return (1900 + date.getYear()) + '/' + date.getMonth();
}
});
});
});

View File

@ -2,10 +2,9 @@
{% set active_page = "stats" %}
{% block title %}Stats{% endblock %}
{% block extraheader %}
<link rel="stylesheet" href="/static/css/rickshaw.css">
<script src="/static/js/d3.min.js"></script>
<script src="/static/js/d3.layout.min.js"></script>
<script src="/static/js/rickshaw.min.js"></script>
<link rel="stylesheet" href="/static/css/c3.min.css">
<script src="/static/js/d3.v5.min.js"></script>
<script src="/static/js/c3.min.js"></script>
<script src="/static/js/plot.js"></script>
{% endblock %}
{% block content %}
@ -18,6 +17,6 @@
</div>
<h4 id="lastmod"></h4>
<div id="legend"></div>
<div id="plot"></div>
<div id="chart"></div>
</div>
{% endblock %}

View File

@ -25,17 +25,30 @@
# POSSIBILITY OF SUCH DAMAGE.
import requests
import os.path
from flask import Response, request, redirect, flash, render_template, url_for, abort, g
from flask import Response, request, redirect, flash, render_template, url_for, abort, g, jsonify
from flask_login import login_user, login_required, logout_user, current_user
from webapp import app, forms, User, models, cache
from webapp import app, forms, User, models, cache, db
import directory
@app.route('/')
def stats():
return render_template('stats.html')
@app.route('/cursed-plot.json')
def plot():
with open(os.path.join(os.path.dirname(__file__), 'cursed-query.sql')) as fd:
cursor = db.session.execute(fd.read(), {
'EURPLN_RATE': 4.3,
'START_DATE': '2018-01-01'
})
result = cursor.fetchall()
columns = cursor.keys()
print(columns)
return jsonify([dict(zip(columns, element)) for element in result])
@app.route('/memberlist')
@login_required
@cache.cached()

View File

@ -2,6 +2,7 @@
import warnings
from flask.exthook import ExtDeprecationWarning
warnings.simplefilter("ignore", ExtDeprecationWarning)
import logging
logging.basicConfig(level=logging.INFO)
import webapp
app = webapp.create_app()