Files
WYGIWYH/app/templates/insights/fragments/sankey.html
Herculino Trotta b4e9446cf6 chore: update tailwind to v4
As is customary in the JS world EVERYTHING must break with each major version
2025-06-21 16:12:44 -03:00

118 lines
4.4 KiB
HTML

{% load i18n %}
{% if type == 'account' %}
<div class="show-loading" hx-get="{% url 'insights_sankey_by_account' %}" hx-trigger="updated from:window"
hx-swap="outerHTML" hx-include="#picker-form, #picker-type">
{% else %}
<div class="show-loading" hx-get="{% url 'insights_sankey_by_currency' %}" hx-trigger="updated from:window"
hx-swap="outerHTML" hx-include="#picker-form, #picker-type">
{% endif %}
<div class="chart-container position-relative tw:min-h-[85vh] tw:max-h-[85vh] tw:h-full tw:w-full"
id="sankeyContainer"
_="init call setupSankeyChart() end">
<canvas id="sankeyChart"></canvas>
</div>
</div>
<script>
var data = {{ sankey_data|safe }};
function setupSankeyChart(chartId = 'sankeyChart') {
function formatCurrency(value, currency) {
return new Intl.NumberFormat(undefined, {
minimumFractionDigits: currency.decimal_places,
maximumFractionDigits: currency.decimal_places
}).format(value);
}
// Create labels object mapping node IDs to display names
const labels = data.nodes.reduce((acc, node) => {
acc[node.id] = node.name;
return acc;
}, {});
// Define colors for each node based on its type
const colors = {};
data.nodes.forEach(node => {
if (node.id.startsWith('income_')) {
colors[node.id] = '#4dde80'; // Green for income
} else if (node.id.startsWith('expense_')) {
colors[node.id] = '#f87171'; // Red for expenses
} else {
colors[node.id] = '#fbb700'; // Primary for others
}
});
// Color getter functions
const getColor = (nodeId) => colors[nodeId];
const getHover = (nodeId) => colors[nodeId];
// Format data for Chart.js
const chartData = {
datasets: [{
data: data.flows.map(flow => ({
from: flow.from_node,
to: flow.to_node,
flow: flow.flow
})),
labels: labels,
colorFrom: (c) => getColor(c.dataset.data[c.dataIndex].from),
colorTo: (c) => getColor(c.dataset.data[c.dataIndex].to),
hoverColorFrom: (c) => getHover(c.dataset.data[c.dataIndex].from),
hoverColorTo: (c) => getHover(c.dataset.data[c.dataIndex].to),
colorMode: 'gradient',
alpha: 0.5,
size: 'max',
color: "white",
nodePadding: 30,
priority: data.nodes.reduce((acc, node) => {
acc[node.id] = node.priority;
return acc;
}, {}),
}]
};
const config = {
type: 'sankey',
data: chartData,
options: {
responsive: true,
maintainAspectRatio: false,
layout: {
padding: 20
},
plugins: {
tooltip: {
callbacks: {
label: (context) => {
const flow = data.flows[context.dataIndex];
const fromNode = data.nodes.find(n => n.id === flow.from_node);
const toNode = data.nodes.find(n => n.id === flow.to_node);
const formattedValue = formatCurrency(flow.original_amount, flow.currency);
return [
`{% trans 'From' %}: ${fromNode.name}`,
`{% trans 'To' %}: ${toNode.name}`,
`{% trans 'Amount' %}: ${flow.currency.prefix}${formattedValue}${flow.currency.suffix}`,
`{% trans 'Percentage' %}: ${flow.percentage.toFixed(2)}%`
];
}
}
},
legend: {
display: false
},
title: {
display: false,
}
}
}
};
// Create new chart
new Chart(
document.getElementById(chartId),
config
);
}
</script>