Refactor History plot
This commit is contained in:
parent
6b50de5e35
commit
0940904cd8
4 changed files with 130 additions and 105 deletions
|
@ -18,48 +18,15 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for y in history.years reversed %}
|
{% for date in history.data reversed %}
|
||||||
{% for date in y.d reversed %}
|
|
||||||
{% ifchanged %}
|
{% ifchanged %}
|
||||||
{% if date %}
|
{% if date.sum_m or date.sum_p %}
|
||||||
<tr {% if not date.month.month|divisibleby:"2" %}class="even"{% endif %}>
|
<tr {% if not date.month.month|divisibleby:"2" %}class="even"{% endif %}>
|
||||||
<td class="icon">
|
<td class="icon">{% up_down_icon date.sum %}</td>
|
||||||
<span class="ri-{% if date.sum > 0 %}arrow-up-s-line green{% elif date.sum < 0 %}arrow-down-s-line red{% endif %}"></span>
|
<th class="date" scope="row">{% month_url date.month account=account category=category %}</th>
|
||||||
</td>
|
|
||||||
<th class="date" scope="row">
|
|
||||||
{% if date.has_transactions %}
|
|
||||||
{% if account %}
|
|
||||||
<a href="{% url "account_transaction_month" account=account.pk year=date.month.year month=date.month.month %}">{{ date.month|date:"Y-m" }}</a>
|
|
||||||
{% elif category %}
|
|
||||||
<a href="{% url "category_transaction_month" category=category.pk year=date.month.year month=date.month.month %}">{{ date.month|date:"Y-m" }}</a>
|
|
||||||
{% else %}
|
|
||||||
<a href="{% url "transaction_month" year=date.month.year month=date.month.month %}">{{ date.month|date:"Y-m" }}</a>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
{{ date.month|date:"Y-m" }}
|
|
||||||
{% endif %}
|
|
||||||
</th>
|
|
||||||
<td class="value">{{ date.sum_m|pmrvalue }}</td>
|
<td class="value">{{ date.sum_m|pmrvalue }}</td>
|
||||||
<td class="bar m">
|
<td class="bar m">{% plot_bar date.sum date.sum_m history.max.pm %}</td>
|
||||||
{% if date.sum_m %}
|
<td class="bar p">{% plot_bar date.sum date.sum_p history.max.pm %}</td>
|
||||||
<div style="width: {% widthratio date.sum_m history.max -100 %}%"></div>
|
|
||||||
{% endif %}
|
|
||||||
{% if date.sum < 0 %}
|
|
||||||
<div class="tot" style="width:{% widthratio date.sum history.max -100 %}%">
|
|
||||||
<span>{{ date.sum|pmrvalue }}</span>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="bar p">
|
|
||||||
{% if date.sum_p %}
|
|
||||||
<div style="width: {% widthratio date.sum_p history.max 100 %}%"></div>
|
|
||||||
{% endif %}
|
|
||||||
{% if date.sum > 0 %}
|
|
||||||
<div class="tot" style="width:{% widthratio date.sum history.max 100 %}%">
|
|
||||||
<span>{{ date.sum|pmrvalue }}</span>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="value">{{ date.sum_p|pmrvalue }}</td>
|
<td class="value">{{ date.sum_p|pmrvalue }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -69,7 +36,6 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endifchanged %}
|
{% endifchanged %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -78,36 +44,26 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">{% translate "Year" %}</th>
|
<th scope="col">{% translate "Year" %}</th>
|
||||||
<th scope="col">01</th>
|
{% calendar_head %}
|
||||||
<th scope="col">02</th>
|
|
||||||
<th scope="col">03</th>
|
|
||||||
<th scope="col">04</th>
|
|
||||||
<th scope="col">05</th>
|
|
||||||
<th scope="col">06</th>
|
|
||||||
<th scope="col">07</th>
|
|
||||||
<th scope="col">08</th>
|
|
||||||
<th scope="col">09</th>
|
|
||||||
<th scope="col">10</th>
|
|
||||||
<th scope="col">11</th>
|
|
||||||
<th scope="col">12</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for year in history.years reversed %}
|
{% regroup history.data by month.year as years_list %}
|
||||||
|
{% for y, year in years_list reversed %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ year.y }}</th>
|
<th>{{ y }}</th>
|
||||||
{% for m in year.d %}
|
{% for m in year %}
|
||||||
{% if forloop.parentloop.last and forloop.first %}
|
{% if forloop.parentloop.last and forloop.first %}
|
||||||
{% for _ in history.offset.0 %}<td></td>{% endfor %}
|
{% empty_calendar_cells_start m.month.month %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if m %}
|
{% if m %}
|
||||||
<td class="{% if m.sum > 0 %}p{% else %}m{% endif %}"
|
<td class="{% if m.sum > 0 %}p{% else %}m{% endif %}"
|
||||||
style="opacity: {% opacity m.sum history.years_max %}"></td>
|
style="opacity: {% opacity m.sum history.max.sum %}"></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td></td>
|
<td></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if forloop.parentloop.first and forloop.last %}
|
{% if forloop.parentloop.first and forloop.last %}
|
||||||
{% for _ in history.offset.1 %}<td></td>{% endfor %}
|
{% empty_calendar_cells_end m.month.month %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.db.models import Func, Max, Min, Q, Sum, Value
|
from django.db.models import Func, Max, Min, Q, Sum
|
||||||
from django.db.models.functions import Abs, TruncMonth
|
from django.db.models.functions import Abs, TruncMonth
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,34 +23,30 @@ def history(transaction_set):
|
||||||
sum_p=Sum("value", filter=Q(value__gt=0)),
|
sum_p=Sum("value", filter=Q(value__gt=0)),
|
||||||
sum_m=Sum("value", filter=Q(value__lt=0)),
|
sum_m=Sum("value", filter=Q(value__lt=0)),
|
||||||
sum=Sum("value"),
|
sum=Sum("value"),
|
||||||
has_transactions=Value(1),
|
|
||||||
).order_by("-month")
|
).order_by("-month")
|
||||||
|
|
||||||
_data = [
|
_data = [
|
||||||
{
|
|
||||||
"y": y,
|
|
||||||
"d": [
|
|
||||||
_history.filter(month=datetime.date(y, m + 1, 1)).first()
|
_history.filter(month=datetime.date(y, m + 1, 1)).first()
|
||||||
for m in range(
|
or {"month": datetime.date(y, m + 1, 1), "sum": 0}
|
||||||
_first_month.month - 1 if _first_month.year == y else 0,
|
|
||||||
_last_month.month if _last_month.year == y else 12,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
}
|
|
||||||
for y in range(
|
for y in range(
|
||||||
_first_month.year,
|
_first_month.year,
|
||||||
_last_month.year + 1,
|
_last_month.year + 1,
|
||||||
)
|
)
|
||||||
|
for m in range(
|
||||||
|
_first_month.month - 1 if _first_month.year == y else 0,
|
||||||
|
_last_month.month if _last_month.year == y else 12,
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"years": _data,
|
"data": _data,
|
||||||
"offset": [range(_first_month.month - 1), range(12 - _last_month.month)],
|
"max": {
|
||||||
"max": max(
|
"pm": max(
|
||||||
_history.aggregate(
|
_history.aggregate(
|
||||||
max=Max("sum_p", default=0),
|
max=Max("sum_p", default=0),
|
||||||
min=-Min("sum_m", default=0),
|
min=-Min("sum_m", default=0),
|
||||||
).values(),
|
).values(),
|
||||||
),
|
),
|
||||||
"years_max": _history.aggregate(max=Max(Abs("sum")))["max"],
|
"sum": _history.aggregate(max=Max(Abs("sum")))["max"],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,12 +65,17 @@ table.full-width col.bar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tr.empty {
|
|
||||||
|
tbody tr {
|
||||||
|
background: initial;
|
||||||
|
&.empty {
|
||||||
height: 0.5rem;
|
height: 0.5rem;
|
||||||
}
|
}
|
||||||
tr.even {
|
&.even {
|
||||||
background: #eeeeff;
|
background: #eeeeff;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tfoot {
|
tfoot {
|
||||||
background: var(--bg-01);
|
background: var(--bg-01);
|
||||||
}
|
}
|
||||||
|
@ -78,17 +83,17 @@ table.full-width col.bar {
|
||||||
|
|
||||||
.calendar {
|
.calendar {
|
||||||
margin-top: var(--gap);
|
margin-top: var(--gap);
|
||||||
|
font-feature-settings: var(--num);
|
||||||
|
|
||||||
tbody tr {
|
|
||||||
background: initial;
|
|
||||||
}
|
|
||||||
.p {
|
.p {
|
||||||
background: var(--green);
|
background: var(--green);
|
||||||
}
|
}
|
||||||
.m {
|
.m {
|
||||||
background: var(--red);
|
background: var(--red);
|
||||||
}
|
}
|
||||||
|
table {
|
||||||
tbody tr {
|
tbody tr {
|
||||||
|
background: initial;
|
||||||
&:not(:last-child) {
|
&:not(:last-child) {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
@ -96,4 +101,5 @@ table.full-width col.bar {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
import datetime
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
|
from django.template.defaultfilters import date
|
||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils import formats
|
from django.utils import formats
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
|
@ -73,4 +76,68 @@ def css(href):
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def opacity(v, vmax):
|
def opacity(v, vmax):
|
||||||
return f"{math.sin(math.fabs(v/vmax)*math.pi/2):.2f}"
|
return f"{math.sin(math.fabs(v/vmax)*math.pi/2):.3f}"
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def empty_calendar_cells(n):
|
||||||
|
return mark_safe(n * "<td></td>")
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def empty_calendar_cells_start(n):
|
||||||
|
return empty_calendar_cells(n - 1)
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def empty_calendar_cells_end(n):
|
||||||
|
return empty_calendar_cells(12 - n)
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def up_down_icon(val):
|
||||||
|
if val > 0:
|
||||||
|
return mark_safe("""<span class="ri-arrow-up-s-line green"></span>""")
|
||||||
|
elif val < 0:
|
||||||
|
return mark_safe("""<span class="ri-arrow-down-s-line red"></span>""")
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def month_url(month, account=None, category=None):
|
||||||
|
url_name = "transaction_month"
|
||||||
|
url_params = {"year": month.year, "month": month.month}
|
||||||
|
|
||||||
|
if account:
|
||||||
|
url_name = "account_" + url_name
|
||||||
|
url_params |= {"account": account.pk}
|
||||||
|
elif category:
|
||||||
|
url_name = "category_" + url_name
|
||||||
|
url_params |= {"category": category.pk}
|
||||||
|
|
||||||
|
url = reverse(url_name, kwargs=url_params)
|
||||||
|
return mark_safe(f"""<a href="{url}">{ date(month, "Y-m") }</a>""")
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def plot_bar(s, sum_pm, s_max):
|
||||||
|
_res = ""
|
||||||
|
|
||||||
|
if sum_pm:
|
||||||
|
_w = abs(sum_pm / s_max)
|
||||||
|
_res += f"""<div style="width: {_w:.1%}"></div>"""
|
||||||
|
if sum_pm is not None and s * sum_pm > 0:
|
||||||
|
_w = abs(s / s_max)
|
||||||
|
_res += (
|
||||||
|
f"""<div class="tot" style="width: {_w:.1%}">"""
|
||||||
|
f"""<span>{pmrvalue(s)}</span></div>"""
|
||||||
|
)
|
||||||
|
|
||||||
|
return mark_safe(_res)
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def calendar_head():
|
||||||
|
months = (datetime.date(1, m + 1, 1) for m in range(12))
|
||||||
|
th = (f"""<th>{date(month, "m")}</th>""" for month in months)
|
||||||
|
|
||||||
|
return mark_safe("".join(th))
|
||||||
|
|
Loading…
Reference in a new issue