web: cursed plotting intensifies
parent
0d674af6a4
commit
05301b4b68
|
@ -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;
|
|
@ -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}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue