views: Purchase graph
parent
ca2d83429f
commit
4e8e085587
|
@ -0,0 +1,67 @@
|
|||
from datetime import date, datetime, timedelta
|
||||
from sqlalchemy import func
|
||||
import itertools
|
||||
|
||||
from bitvend.models import db, Transaction
|
||||
|
||||
|
||||
def daterange(start_date, end_date):
|
||||
for n in range(int((end_date - start_date).days)+1):
|
||||
yield (start_date + timedelta(n)).date()
|
||||
|
||||
|
||||
def gen_graph_dataset(resultset, date_from=None, date_to=None, default=0):
|
||||
date_hash = {datetime.strptime(xdate, '%Y-%m-%d').date(): count for xdate, count in resultset}
|
||||
|
||||
if not date_from:
|
||||
date_from = min(date_hash.keys())
|
||||
|
||||
if not date_to:
|
||||
date_to = datetime.utcnow().date()
|
||||
|
||||
return [
|
||||
type(default)(date_hash[d]) if d in date_hash else default
|
||||
for d in daterange(date_from, date_to)
|
||||
]
|
||||
|
||||
|
||||
def gen_database_graph(title, query, date_from, date_to, default=0):
|
||||
date_column = query.column_descriptions[0]['expr']
|
||||
resultset = query \
|
||||
.group_by(
|
||||
date_column
|
||||
).filter(
|
||||
date_column >= date_from,
|
||||
date_column <= date_to
|
||||
).all()
|
||||
|
||||
return [title] + gen_graph_dataset(resultset, date_from, date_to, default)
|
||||
|
||||
|
||||
def zip_graph(dataset):
|
||||
keys = [n[0] for n in dataset]
|
||||
i = zip(*dataset)
|
||||
next(i)
|
||||
return [dict(zip(keys, values), values=None) for values in i]
|
||||
|
||||
|
||||
def gen_main_graph(date_from=None, date_to=None):
|
||||
if date_from is None:
|
||||
date_from = date.today() - timedelta(days=30)
|
||||
|
||||
if date_to is None:
|
||||
date_to = date.today()
|
||||
|
||||
date_from = datetime.combine(date_from, datetime.min.time())
|
||||
date_to = datetime.combine(date_to, datetime.max.time())
|
||||
|
||||
return zip_graph([
|
||||
['date'] + [n.strftime('%Y-%m-%d')
|
||||
for n in daterange(date_from, date_to)],
|
||||
|
||||
gen_database_graph('purchases', db.session.query(
|
||||
#func.date_trunc('day', Transaction.created),
|
||||
func.strftime('%Y-%m-%d', Transaction.created),
|
||||
func.count(Transaction.created),
|
||||
).filter(Transaction.type == 'purchase'), date_from, date_to),
|
||||
])
|
|
@ -0,0 +1,401 @@
|
|||
.mg-active-datapoint {
|
||||
fill: black;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 400;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.mg-area1-color {
|
||||
fill: #0000ff;
|
||||
}
|
||||
|
||||
.mg-area2-color {
|
||||
fill: #05b378;
|
||||
}
|
||||
|
||||
.mg-area3-color {
|
||||
fill: #db4437;
|
||||
}
|
||||
|
||||
.mg-area4-color {
|
||||
fill: #f8b128;
|
||||
}
|
||||
|
||||
.mg-area5-color {
|
||||
fill: #5c5c5c;
|
||||
}
|
||||
|
||||
text.mg-barplot-group-label {
|
||||
font-weight:900;
|
||||
}
|
||||
|
||||
.mg-barplot rect.mg-bar {
|
||||
shape-rendering: auto;
|
||||
}
|
||||
|
||||
.mg-barplot rect.mg-bar.default-bar {
|
||||
fill: #b6b6fc;
|
||||
}
|
||||
|
||||
.mg-barplot rect.mg-bar.default-active {
|
||||
fill: #9e9efc;
|
||||
}
|
||||
|
||||
.mg-barplot .mg-bar-prediction {
|
||||
fill: #5b5b5b;
|
||||
}
|
||||
|
||||
.mg-barplot .mg-bar-baseline {
|
||||
stroke: #5b5b5b;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.mg-bar-target-element {
|
||||
font-size:11px;
|
||||
padding-left:5px;
|
||||
padding-right:5px;
|
||||
font-weight:300;
|
||||
}
|
||||
|
||||
.mg-baselines line {
|
||||
opacity: 1;
|
||||
shape-rendering: auto;
|
||||
stroke: #b3b2b2;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.mg-baselines text {
|
||||
fill: black;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.6;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.mg-baselines-small text {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
.mg-category-guides line {
|
||||
stroke: #b3b2b2;
|
||||
}
|
||||
|
||||
.mg-header {
|
||||
cursor: default;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.mg-header .mg-chart-description {
|
||||
fill: #ccc;
|
||||
font-family: FontAwesome;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.mg-header .mg-warning {
|
||||
fill: #ccc;
|
||||
font-family: FontAwesome;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.mg-points circle {
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
.mg-popover {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.mg-popover-content {
|
||||
cursor: auto;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
.mg-data-table {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.mg-data-table thead tr th {
|
||||
border-bottom: 1px solid darkgray;
|
||||
cursor: default;
|
||||
font-size: 1.1rem;
|
||||
font-weight: normal;
|
||||
padding: 5px 5px 8px 5px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.mg-data-table thead tr th .fa {
|
||||
color: #ccc;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.mg-data-table thead tr th .popover {
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.mg-data-table .secondary-title {
|
||||
color: darkgray;
|
||||
}
|
||||
|
||||
.mg-data-table tbody tr td {
|
||||
margin: 2px;
|
||||
padding: 5px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.mg-data-table tbody tr td.table-text {
|
||||
opacity: 0.8;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.mg-y-axis line.mg-extended-yax-ticks {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.mg-x-axis line.mg-extended-xax-ticks {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.mg-histogram .axis path,
|
||||
.mg-histogram .axis line {
|
||||
fill: none;
|
||||
opacity: 0.7;
|
||||
shape-rendering: auto;
|
||||
stroke: #ccc;
|
||||
}
|
||||
|
||||
tspan.hist-symbol {
|
||||
fill: #9e9efc;
|
||||
}
|
||||
|
||||
.mg-histogram .mg-bar rect {
|
||||
fill: #b6b6fc;
|
||||
shape-rendering: auto;
|
||||
}
|
||||
|
||||
.mg-histogram .mg-bar rect.active {
|
||||
fill: #9e9efc;
|
||||
}
|
||||
|
||||
.mg-least-squares-line {
|
||||
stroke: red;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.mg-lowess-line {
|
||||
fill: none;
|
||||
stroke: red;
|
||||
}
|
||||
|
||||
.mg-line1-color {
|
||||
stroke: #4040e8;
|
||||
}
|
||||
|
||||
.mg-hover-line1-color {
|
||||
fill: #4040e8;
|
||||
}
|
||||
|
||||
.mg-line2-color {
|
||||
stroke: #05b378;
|
||||
}
|
||||
|
||||
.mg-hover-line2-color {
|
||||
fill: #05b378;
|
||||
}
|
||||
|
||||
.mg-line3-color {
|
||||
stroke: #db4437;
|
||||
}
|
||||
|
||||
.mg-hover-line3-color {
|
||||
fill: #db4437;
|
||||
}
|
||||
|
||||
.mg-line4-color {
|
||||
stroke: #f8b128;
|
||||
}
|
||||
|
||||
.mg-hover-line4-color {
|
||||
fill: #f8b128;
|
||||
}
|
||||
|
||||
.mg-line5-color {
|
||||
stroke: #5c5c5c;
|
||||
}
|
||||
|
||||
.mg-hover-line5-color {
|
||||
fill: #5c5c5c;
|
||||
}
|
||||
|
||||
.mg-line-legend text {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 300;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.mg-line1-legend-color {
|
||||
color: #4040e8;
|
||||
fill: #4040e8;
|
||||
}
|
||||
|
||||
.mg-line2-legend-color {
|
||||
color: #05b378;
|
||||
fill: #05b378;
|
||||
}
|
||||
|
||||
.mg-line3-legend-color {
|
||||
color: #db4437;
|
||||
fill: #db4437;
|
||||
}
|
||||
|
||||
.mg-line4-legend-color {
|
||||
color: #f8b128;
|
||||
fill: #f8b128;
|
||||
}
|
||||
|
||||
.mg-line5-legend-color {
|
||||
color: #5c5c5c;
|
||||
fill: #5c5c5c;
|
||||
}
|
||||
|
||||
.mg-main-area-solid svg .mg-main-area {
|
||||
fill: #ccccff;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.mg-markers line {
|
||||
opacity: 1;
|
||||
shape-rendering: auto;
|
||||
stroke: #b3b2b2;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.mg-markers text {
|
||||
fill: black;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.mg-missing-text {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.mg-missing-background {
|
||||
stroke: blue;
|
||||
fill: none;
|
||||
stroke-dasharray: 10,5;
|
||||
stroke-opacity: 0.05;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.mg-missing .mg-main-line {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.mg-missing .mg-main-area {
|
||||
opacity: 0.03;
|
||||
}
|
||||
|
||||
path.mg-main-area {
|
||||
opacity: 0.2;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
path.mg-confidence-band {
|
||||
fill: #ccc;
|
||||
opacity: 0.4;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
path.mg-main-line {
|
||||
fill: none;
|
||||
opacity: 0.8;
|
||||
stroke-width: 1.1px;
|
||||
}
|
||||
|
||||
.mg-points circle {
|
||||
fill-opacity: 0.4;
|
||||
stroke-opacity: 1;
|
||||
}
|
||||
|
||||
circle.mg-points-mono {
|
||||
fill: #0000ff;
|
||||
stroke: #0000ff;
|
||||
}
|
||||
|
||||
tspan.mg-points-mono {
|
||||
fill: #0000ff;
|
||||
stroke: #0000ff;
|
||||
}
|
||||
|
||||
/* a selected point in a scatterplot */
|
||||
.mg-points circle.selected {
|
||||
fill-opacity: 1;
|
||||
stroke-opacity: 1;
|
||||
}
|
||||
|
||||
.mg-voronoi path {
|
||||
fill: none;
|
||||
pointer-events: all;
|
||||
stroke: none;
|
||||
stroke-opacity: 0.1;
|
||||
}
|
||||
|
||||
.mg-x-rug-mono,
|
||||
.mg-y-rug-mono {
|
||||
stroke: black;
|
||||
}
|
||||
|
||||
.mg-x-axis line,
|
||||
.mg-y-axis line {
|
||||
opacity: 1;
|
||||
shape-rendering: auto;
|
||||
stroke: #b3b2b2;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.mg-x-axis text,
|
||||
.mg-y-axis text,
|
||||
.mg-histogram .axis text {
|
||||
fill: black;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.mg-x-axis .label,
|
||||
.mg-y-axis .label,
|
||||
.mg-axis .label {
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.mg-x-axis-small text,
|
||||
.mg-y-axis-small text,
|
||||
.mg-active-datapoint-small {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
.mg-x-axis-small .label,
|
||||
.mg-y-axis-small .label {
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
|
||||
.mg-european-hours {
|
||||
}
|
||||
|
||||
.mg-year-marker text {
|
||||
fill: black;
|
||||
font-size: 0.7rem;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.mg-year-marker line {
|
||||
opacity: 1;
|
||||
shape-rendering: auto;
|
||||
stroke: #b3b2b2;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.mg-year-marker-small text {
|
||||
font-size: 0.6rem;
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -6,6 +6,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<link rel="stylesheet" href="{{ static('css/bootstrap.css') }}" media="screen">
|
||||
<link rel="stylesheet" href="{{ static('css/metricsgraphics.css') }}" media="screen">
|
||||
<style>
|
||||
body {
|
||||
margin-top: 80px;
|
||||
|
@ -93,5 +94,9 @@
|
|||
|
||||
<script src="{{ static('js/jquery-1.10.2.min.js') }}"></script>
|
||||
<script src="{{ static('js/bootstrap.min.js') }}"></script>
|
||||
<script src="{{ static('js/d3.v4.min.js') }}"></script>
|
||||
<script src="{{ static('js/metricsgraphics.min.js') }}"></script>
|
||||
{% block tail_js %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -152,8 +152,27 @@
|
|||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div id="purchases"></div>
|
||||
|
||||
<blockquote class="blockquote-reverse">
|
||||
<p>Kowalski czuje zapach pieniędzy.</p>
|
||||
<small>Someone famous</small>
|
||||
</blockquote>
|
||||
{% endblock %}
|
||||
|
||||
{% block tail_js %}
|
||||
<script>
|
||||
d3.json('/api/1/history.json', function(data) {
|
||||
data = MG.convert.date(data, 'date');
|
||||
MG.data_graphic({
|
||||
title: "Purchases",
|
||||
data: data,
|
||||
height: 200,
|
||||
width: 600,
|
||||
target: document.getElementById('purchases'),
|
||||
x_accessor: 'date',
|
||||
y_accessor: 'purchases'
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from flask import Blueprint, render_template, redirect, request, flash, url_for
|
||||
from flask import Blueprint, render_template, redirect, request, flash, \
|
||||
url_for, jsonify
|
||||
from flask import current_app as app
|
||||
from flask_login import login_required, current_user, logout_user
|
||||
import six
|
||||
|
@ -10,6 +11,7 @@ from bitvend import dev, proc
|
|||
from bitvend.models import db, User, Transaction, NoFunds
|
||||
from bitvend.auth import try_login, cap_required
|
||||
from bitvend.forms import TransferForm
|
||||
from bitvend.graphs import gen_main_graph
|
||||
|
||||
|
||||
bp = Blueprint('bitvend', __name__, template_folder='templates')
|
||||
|
@ -132,3 +134,7 @@ def qrcode_gen(data):
|
|||
'Content-Type': 'image/svg+xml',
|
||||
'Cache-Control': 'public,max-age=3600',
|
||||
}
|
||||
|
||||
@bp.route('/api/1/history.json')
|
||||
def history():
|
||||
return jsonify(gen_main_graph())
|
||||
|
|
Loading…
Reference in New Issue