Add timeline car import and CO2 trip stats

This commit is contained in:
Edward Betts 2026-08-03 13:52:45 +01:00
parent 38dbb2b4e7
commit 3747e83ade
14 changed files with 1038 additions and 29 deletions

View file

@ -6,6 +6,14 @@
{% block title %}{{ heading }} - Edward Betts{% endblock %}
{% block style %}
<style>
.co2-chart {
height: 240px;
}
</style>
{% endblock %}
{% macro stat_list(id, label, counter, show_top=5) %}
<div class="mb-2">
<strong>{{ label }}:</strong> {{ counter | count }}
@ -24,6 +32,14 @@
</div>
{% endmacro %}
{% macro format_co2(co2_kg) %}
{% if co2_kg >= 1000 %}
{{ "{:,.2f}".format(co2_kg / 1000.0) }} tonnes
{% else %}
{{ "{:,.0f}".format(co2_kg) }} kg
{% endif %}
{% endmacro %}
{% block content %}
<div class="container-fluid">
<h1>Trip statistics</h1>
@ -39,10 +55,21 @@
{% for transport_type, distance in distances_by_transport_type %}
<div class="ms-3">{{ transport_type | title }}: {{ format_distance(distance) }}</div>
{% endfor %}
{% if total_co2_kg %}
<div class="mt-2">CO₂: {{ format_co2(total_co2_kg) }}</div>
{% endif %}
{% for transport_type, co2_kg in co2_by_transport_type %}
<div class="ms-3">{{ transport_type | title }} CO₂: {{ format_co2(co2_kg) }}</div>
{% endfor %}
</div>
<div class="col-md-6">
<div>Flight segments: {{ overall_stats.flight_count }}</div>
<div>Train segments: {{ overall_stats.train_count }}</div>
{% if co2_by_transport_type %}
<div class="co2-chart mt-3">
<canvas id="overall-co2-chart"></canvas>
</div>
{% endif %}
</div>
</div>
@ -79,12 +106,12 @@
<div>Flight segments: {{ year_stats.flight_count or 0 }}</div>
<div>Train segments: {{ year_stats.train_count or 0 }}</div>
{% if year_stats.co2_kg %}
<div>CO₂:
{% if year_stats.co2_kg >= 1000 %}
{{ "{:,.2f}".format(year_stats.co2_kg / 1000.0) }} tonnes
{% else %}
{{ "{:,.0f}".format(year_stats.co2_kg) }} kg
{% endif %}
<div>CO₂: {{ format_co2(year_stats.co2_kg) }}</div>
{% for transport_type, co2_kg in year_stats.co2_by_transport_type.items() %}
<div class="ms-3">{{ transport_type | title }} CO₂: {{ format_co2(co2_kg) }}</div>
{% endfor %}
<div class="co2-chart mt-3">
<canvas id="co2-chart-{{ year }}"></canvas>
</div>
{% endif %}
</div>
@ -119,3 +146,72 @@
{% endfor %}
</div>
{% endblock %}
{% block scripts %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const co2ChartColors = [
"#4e79a7",
"#f28e2b",
"#e15759",
"#76b7b2",
"#59a14f",
"#edc948",
"#b07aa1",
"#ff9da7",
"#9c755f",
"#bab0ac",
];
function titleCase(value) {
return value.replace(/\b\w/g, char => char.toUpperCase());
}
function co2TooltipLabel(context) {
const label = context.label || "";
const value = context.parsed || 0;
return `${label}: ${value.toLocaleString(undefined, { maximumFractionDigits: 1 })} kg`;
}
function renderCo2Chart(id, values) {
const canvas = document.getElementById(id);
if (!canvas || !values || values.length === 0) {
return;
}
new Chart(canvas.getContext("2d"), {
type: "pie",
data: {
labels: values.map(item => titleCase(item[0])),
datasets: [{
data: values.map(item => item[1]),
backgroundColor: co2ChartColors,
}],
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: "right",
},
tooltip: {
callbacks: {
label: co2TooltipLabel,
},
},
},
},
});
}
renderCo2Chart("overall-co2-chart", {{ co2_by_transport_type | tojson }});
{% for year, year_stats in yearly_stats | dictsort(reverse=True) %}
{% set year_co2_by_transport_type = year_stats.co2_by_transport_type | default({}) %}
renderCo2Chart(
"co2-chart-{{ year }}",
{{ year_co2_by_transport_type.items() | list | tojson }}
);
{% endfor %}
</script>
{% endblock %}

View file

@ -369,6 +369,8 @@
{% elif e.element_type in ("coach", "bus", "car") %}
{% set item = e.detail %}
{% set display_depart = item.display_depart if item.display_depart is defined else item.depart %}
{% set display_arrive = item.display_arrive if item.display_arrive is defined else item.arrive %}
<div class="trip-transport-card my-1">
<div class="card-body">
<h5 class="card-title">
@ -377,14 +379,14 @@
{% if item.operator %}<small class="text-muted fw-normal">{{ item.operator }}</small>{% endif %}
</h5>
<p class="card-text">
{% if item.depart.hour is defined and item.arrive.hour is defined %}
{{ item.depart.strftime("%H:%M") }} → {{ item.arrive.strftime("%H:%M") }}
{% if display_depart.hour is defined and display_arrive.hour is defined %}
{{ display_depart.strftime("%H:%M") }} → {{ display_arrive.strftime("%H:%M") }}
{% endif %}
{% if item.class %}
<span class="badge bg-info text-nowrap">{{ item.class }}</span>
{% endif %}
{% if item.depart.hour is defined and item.arrive.hour is defined %}
<span class="text-muted">🕒{{ trip_duration(item.depart, item.arrive) }}</span>
{% if display_depart.hour is defined and display_arrive.hour is defined %}
<span class="text-muted">🕒{{ trip_duration(display_depart, display_arrive) }}</span>
{% endif %}
{% if item.distance %}
<span class="text-muted">🛤️ {{ "{:,.0f} km".format(item.distance) }}</span>