Refactor History plot

This commit is contained in:
Edgar P. Burkhart 2024-01-02 10:58:30 +01:00
parent 6b50de5e35
commit 0940904cd8
Signed by: edpibu
GPG key ID: 9833D3C5A25BD227
4 changed files with 130 additions and 105 deletions

View file

@ -18,57 +18,23 @@
</tr>
</thead>
<tbody>
{% for y in history.years reversed %}
{% for date in y.d reversed %}
{% ifchanged %}
{% if date %}
<tr {% if not date.month.month|divisibleby:"2" %}class="even"{% endif %}>
<td class="icon">
<span class="ri-{% if date.sum > 0 %}arrow-up-s-line green{% elif date.sum < 0 %}arrow-down-s-line red{% endif %}"></span>
</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="bar m">
{% if date.sum_m %}
<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>
</tr>
{% else %}
<tr class="empty">
<td colspan="6" class="empty"></td>
</tr>
{% endif %}
{% endifchanged %}
{% endfor %}
{% for date in history.data reversed %}
{% ifchanged %}
{% if date.sum_m or date.sum_p %}
<tr {% if not date.month.month|divisibleby:"2" %}class="even"{% endif %}>
<td class="icon">{% up_down_icon date.sum %}</td>
<th class="date" scope="row">{% month_url date.month account=account category=category %}</th>
<td class="value">{{ date.sum_m|pmrvalue }}</td>
<td class="bar m">{% plot_bar date.sum date.sum_m history.max.pm %}</td>
<td class="bar p">{% plot_bar date.sum date.sum_p history.max.pm %}</td>
<td class="value">{{ date.sum_p|pmrvalue }}</td>
</tr>
{% else %}
<tr class="empty">
<td colspan="6" class="empty"></td>
</tr>
{% endif %}
{% endifchanged %}
{% endfor %}
</tbody>
</table>
@ -78,36 +44,26 @@
<thead>
<tr>
<th scope="col">{% translate "Year" %}</th>
<th scope="col">01</th>
<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>
{% calendar_head %}
</tr>
</thead>
<tbody>
{% for year in history.years reversed %}
{% regroup history.data by month.year as years_list %}
{% for y, year in years_list reversed %}
<tr>
<th>{{ year.y }}</th>
{% for m in year.d %}
<th>{{ y }}</th>
{% for m in year %}
{% if forloop.parentloop.last and forloop.first %}
{% for _ in history.offset.0 %}<td></td>{% endfor %}
{% empty_calendar_cells_start m.month.month %}
{% endif %}
{% if m %}
<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 %}
<td></td>
{% endif %}
{% if forloop.parentloop.first and forloop.last %}
{% for _ in history.offset.1 %}<td></td>{% endfor %}
{% empty_calendar_cells_end m.month.month %}
{% endif %}
{% endfor %}
</tr>

View file

@ -1,6 +1,6 @@
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
@ -23,34 +23,30 @@ def history(transaction_set):
sum_p=Sum("value", filter=Q(value__gt=0)),
sum_m=Sum("value", filter=Q(value__lt=0)),
sum=Sum("value"),
has_transactions=Value(1),
).order_by("-month")
_data = [
{
"y": y,
"d": [
_history.filter(month=datetime.date(y, m + 1, 1)).first()
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,
)
],
}
_history.filter(month=datetime.date(y, m + 1, 1)).first()
or {"month": datetime.date(y, m + 1, 1), "sum": 0}
for y in range(
_first_month.year,
_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 {
"years": _data,
"offset": [range(_first_month.month - 1), range(12 - _last_month.month)],
"max": max(
_history.aggregate(
max=Max("sum_p", default=0),
min=-Min("sum_m", default=0),
).values(),
),
"years_max": _history.aggregate(max=Max(Abs("sum")))["max"],
"data": _data,
"max": {
"pm": max(
_history.aggregate(
max=Max("sum_p", default=0),
min=-Min("sum_m", default=0),
).values(),
),
"sum": _history.aggregate(max=Max(Abs("sum")))["max"],
},
}

View file

@ -65,12 +65,17 @@ table.full-width col.bar {
}
}
}
tr.empty {
height: 0.5rem;
}
tr.even {
background: #eeeeff;
tbody tr {
background: initial;
&.empty {
height: 0.5rem;
}
&.even {
background: #eeeeff;
}
}
tfoot {
background: var(--bg-01);
}
@ -78,22 +83,23 @@ table.full-width col.bar {
.calendar {
margin-top: var(--gap);
font-feature-settings: var(--num);
tbody tr {
background: initial;
}
.p {
background: var(--green);
}
.m {
background: var(--red);
}
tbody tr {
&:not(:last-child) {
border-bottom: none;
}
&:not(:first-child) {
border-top: none;
table {
tbody tr {
background: initial;
&:not(:last-child) {
border-bottom: none;
}
&:not(:first-child) {
border-top: none;
}
}
}
}

View file

@ -1,7 +1,10 @@
import datetime
import math
from django import template
from django.template.defaultfilters import date
from django.templatetags.static import static
from django.urls import reverse
from django.utils import formats
from django.utils.safestring import mark_safe
@ -73,4 +76,68 @@ def css(href):
@register.simple_tag
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))