Updated the web #31
@ -37,6 +37,9 @@
|
||||
--color-table-alt: #f8faf9;
|
||||
--color-table-hover: #e9f1ed;
|
||||
--color-progress-track: #e0e0e0;
|
||||
|
||||
--chart-grid-color: rgba(0, 0, 0, 0.1);
|
||||
--chart-fill-color: rgba(15, 118, 110, 0.2);
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
@ -68,6 +71,9 @@
|
||||
--color-table-alt: #182420;
|
||||
--color-table-hover: #20302a;
|
||||
--color-progress-track: #2a3a33;
|
||||
|
||||
--chart-grid-color: rgba(255, 255, 255, 0.12);
|
||||
--chart-fill-color: rgba(15, 118, 110, 0.35);
|
||||
}
|
||||
|
||||
* {
|
||||
|
||||
88
expenses_manager/expenses/static/expenses/js/charts.js
Normal file
88
expenses_manager/expenses/static/expenses/js/charts.js
Normal file
@ -0,0 +1,88 @@
|
||||
(function () {
|
||||
function getActiveTheme() {
|
||||
return document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
function getChartColors() {
|
||||
var styles = getComputedStyle(document.documentElement);
|
||||
var theme = getActiveTheme();
|
||||
|
||||
return {
|
||||
theme: theme,
|
||||
gridColor: styles.getPropertyValue('--chart-grid-color').trim() || 'rgba(0, 0, 0, 0.1)',
|
||||
tickColor: styles.getPropertyValue('--color-text-muted').trim() || '#6b6560',
|
||||
primaryColor: styles.getPropertyValue('--color-primary').trim() || '#0f766e',
|
||||
primaryFill: styles.getPropertyValue('--chart-fill-color').trim() || 'rgba(15, 118, 110, 0.2)',
|
||||
palette: ['#ff6384', '#36a2eb', '#cc65fe', '#ffce56', '#4bc0c0', '#ffa1b5']
|
||||
};
|
||||
}
|
||||
|
||||
// Aplica los colores de grid/ticks a las escalas cartesianas (x/y) de un gráfico.
|
||||
// Se usa tanto al crear el gráfico (colores embebidos en las opciones iniciales)
|
||||
// como al refrescar el tema en caliente sobre un gráfico ya existente.
|
||||
function applyCartesianColors(chart, colors) {
|
||||
var scales = chart.options.scales;
|
||||
|
||||
if (!scales) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(scales).forEach(function (axis) {
|
||||
var scale = scales[axis];
|
||||
|
||||
if (!scale) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Object.assign crea un objeto grid/ticks nuevo en vez de mutar en el
|
||||
// sitio (scale.grid.color = ...). Chart.js reconstruye internamente los
|
||||
// objetos de opciones de un gráfico 'line' ya creado, y mutar una
|
||||
// propiedad suelta sobre esa estructura reactiva provoca
|
||||
// "TypeError: t.startsWith is not a function" al redibujar. Reemplazar
|
||||
// el objeto entero evita tocar esas referencias internas.
|
||||
if (typeof colors.gridColor === 'string' && colors.gridColor) {
|
||||
scale.grid = Object.assign({}, scale.grid, { color: colors.gridColor });
|
||||
}
|
||||
|
||||
if (typeof colors.tickColor === 'string' && colors.tickColor) {
|
||||
scale.ticks = Object.assign({}, scale.ticks, { color: colors.tickColor });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Los colores de tema se fijan en las opciones al CREAR cada gráfico (evita el
|
||||
// bug de mutar un gráfico 'line' recién construido). Para que el gráfico
|
||||
// reaccione a un cambio de tema posterior sin recrearlo, cada plantilla registra
|
||||
// aquí su instancia junto con un callback que sabe cómo reaplicar sus colores.
|
||||
var registeredCharts = [];
|
||||
|
||||
function registerThemedChart(chart, applyColors) {
|
||||
registeredCharts.push({ chart: chart, applyColors: applyColors });
|
||||
}
|
||||
|
||||
function refreshThemedCharts() {
|
||||
var colors = getChartColors();
|
||||
|
||||
registeredCharts.forEach(function (entry) {
|
||||
entry.applyColors(entry.chart, colors);
|
||||
// 'none' desactiva la animación de la actualización: solo repinta con
|
||||
// los nuevos colores, sin la transición de entrada de datos.
|
||||
entry.chart.update('none');
|
||||
});
|
||||
}
|
||||
|
||||
// Observa el atributo data-theme del <html> (lo cambia el toggle de tema) para
|
||||
// refrescar todos los gráficos registrados en cuanto el usuario cambia de tema,
|
||||
// sin necesidad de recargar la página.
|
||||
new MutationObserver(function (mutations) {
|
||||
var themeChanged = mutations.some(function (m) { return m.attributeName === 'data-theme'; });
|
||||
|
||||
if (themeChanged) {
|
||||
refreshThemedCharts();
|
||||
}
|
||||
}).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
||||
|
||||
window.getChartColors = getChartColors;
|
||||
window.applyCartesianColors = applyCartesianColors;
|
||||
window.registerThemedChart = registerThemedChart;
|
||||
})();
|
||||
@ -16,6 +16,7 @@
|
||||
</script>
|
||||
<link rel="stylesheet" href="{% static 'expenses/css/base.css' %}">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="{% static 'expenses/js/charts.js' %}"></script>
|
||||
{% block extra_css %}{% endblock %}
|
||||
</head>
|
||||
|
||||
|
||||
@ -259,25 +259,32 @@
|
||||
const catLabels = [{% for row in by_category_chart %}"{{ row.category__name }}",{% endfor %}];
|
||||
const catData = [{% for row in by_category_chart %}{{ row.total|unlocalize }},{% endfor %}];
|
||||
|
||||
new Chart(document.getElementById('categoryChart'), {
|
||||
const categoryChartColors = getChartColors();
|
||||
|
||||
const categoryChart = new Chart(document.getElementById('categoryChart'), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: catLabels,
|
||||
datasets: [{
|
||||
data: catData,
|
||||
backgroundColor: [
|
||||
'#ff6384', '#36a2eb', '#cc65fe', '#ffce56', '#4bc0c0', '#ffa1b5'
|
||||
]
|
||||
backgroundColor: categoryChartColors.palette
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { position: 'right' }
|
||||
legend: {
|
||||
position: 'right',
|
||||
labels: { color: categoryChartColors.tickColor }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registerThemedChart(categoryChart, function (chart, colors) {
|
||||
chart.options.plugins.legend.labels.color = colors.tickColor;
|
||||
});
|
||||
</script>
|
||||
|
||||
<h3>Evolución anual por cuenta ({{ selected_year }})</h3>
|
||||
@ -297,34 +304,50 @@
|
||||
const labels = {{ chart_labels|safe }};
|
||||
const data = {{ chart_data|safe }};
|
||||
const ctx = document.getElementById('mainChart');
|
||||
const mainChartColors = getChartColors();
|
||||
|
||||
new Chart(ctx, {
|
||||
const mainChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets:[{
|
||||
label: 'Gastos (€)',
|
||||
data: data,
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.6)',
|
||||
backgroundColor: 'rgba(54, 162, 235, 1)',
|
||||
backgroundColor: mainChartColors.primaryColor,
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: { y: { beginAtZero: true } }
|
||||
scales: {
|
||||
x: {
|
||||
grid: { color: mainChartColors.gridColor },
|
||||
ticks: { color: mainChartColors.tickColor }
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: mainChartColors.gridColor },
|
||||
ticks: { color: mainChartColors.tickColor }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registerThemedChart(mainChart, function (chart, colors) {
|
||||
chart.data.datasets[0].backgroundColor = colors.primaryColor;
|
||||
applyCartesianColors(chart, colors);
|
||||
});
|
||||
|
||||
const allMonths = ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"];
|
||||
|
||||
{% for acc in accounts_charts %}
|
||||
(function() {
|
||||
const dataAccount = {{ acc.data|safe }};
|
||||
const filteredLabels = allMonths.slice(0, dataAccount.length);
|
||||
const accountChartColors = getChartColors();
|
||||
|
||||
new Chart(
|
||||
const accountChart = new Chart(
|
||||
document.getElementById('accountChart{{ acc.id }}'),
|
||||
{
|
||||
type: 'line',
|
||||
@ -334,8 +357,8 @@
|
||||
label: 'Saldo Real (€)',
|
||||
data: dataAccount,
|
||||
fill: false,
|
||||
borderColor: 'rgba(54, 162, 235, 1)',
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||||
borderColor: accountChartColors.primaryColor,
|
||||
backgroundColor: accountChartColors.primaryFill,
|
||||
tension: 0.1
|
||||
}]
|
||||
},
|
||||
@ -343,13 +366,25 @@
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: {
|
||||
grid: { color: accountChartColors.gridColor },
|
||||
ticks: { color: accountChartColors.tickColor }
|
||||
},
|
||||
y: {
|
||||
beginAtZero: false
|
||||
beginAtZero: false,
|
||||
grid: { color: accountChartColors.gridColor },
|
||||
ticks: { color: accountChartColors.tickColor }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
registerThemedChart(accountChart, function (chart, colors) {
|
||||
chart.data.datasets[0].borderColor = colors.primaryColor;
|
||||
chart.data.datasets[0].backgroundColor = colors.primaryFill;
|
||||
applyCartesianColors(chart, colors);
|
||||
});
|
||||
})();
|
||||
{% endfor %}
|
||||
</script>
|
||||
|
||||
@ -45,21 +45,39 @@
|
||||
const data = {{ mini_chart_data|safe }};
|
||||
|
||||
const ctx = document.getElementById('miniChart');
|
||||
const miniChartColors = getChartColors();
|
||||
|
||||
new Chart(ctx, {
|
||||
const miniChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets:[{
|
||||
label: 'Gastos',
|
||||
data: data,
|
||||
backgroundColor: miniChartColors.primaryColor,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: {
|
||||
grid: { color: miniChartColors.gridColor },
|
||||
ticks: { color: miniChartColors.tickColor }
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: miniChartColors.gridColor },
|
||||
ticks: { color: miniChartColors.tickColor }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registerThemedChart(miniChart, function (chart, colors) {
|
||||
chart.data.datasets[0].backgroundColor = colors.primaryColor;
|
||||
applyCartesianColors(chart, colors);
|
||||
});
|
||||
</script>
|
||||
|
||||
</section>
|
||||
|
||||
@ -59,31 +59,73 @@
|
||||
|
||||
<script>
|
||||
const monthlyData = {{ monthly_data|safe }};
|
||||
const monthlyChartColors = getChartColors();
|
||||
|
||||
new Chart(document.getElementById('monthlyChart'), {
|
||||
const monthlyChart = new Chart(document.getElementById('monthlyChart'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['Ene','Feb','Mar','Abr','May','Jun','Jul','Ago','Sep','Oct','Nov','Dic'],
|
||||
datasets: [{
|
||||
label: 'Gasto mensual',
|
||||
data: monthlyData
|
||||
data: monthlyData,
|
||||
backgroundColor: monthlyChartColors.primaryColor
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
x: {
|
||||
grid: { color: monthlyChartColors.gridColor },
|
||||
ticks: { color: monthlyChartColors.tickColor }
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: monthlyChartColors.gridColor },
|
||||
ticks: { color: monthlyChartColors.tickColor }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registerThemedChart(monthlyChart, function (chart, colors) {
|
||||
chart.data.datasets[0].backgroundColor = colors.primaryColor;
|
||||
applyCartesianColors(chart, colors);
|
||||
});
|
||||
|
||||
const kmData = {{ km_data|safe }};
|
||||
const kmDates = {{ km_dates|safe }};
|
||||
const kmChartColors = getChartColors();
|
||||
|
||||
new Chart(document.getElementById('kmChart'), {
|
||||
const kmChart = new Chart(document.getElementById('kmChart'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: kmDates,
|
||||
datasets: [{
|
||||
label: 'Km entre repostajes',
|
||||
data: kmData
|
||||
data: kmData,
|
||||
borderColor: kmChartColors.primaryColor,
|
||||
backgroundColor: kmChartColors.primaryFill
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
x: {
|
||||
grid: { color: kmChartColors.gridColor },
|
||||
ticks: { color: kmChartColors.tickColor }
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: kmChartColors.gridColor },
|
||||
ticks: { color: kmChartColors.tickColor }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registerThemedChart(kmChart, function (chart, colors) {
|
||||
chart.data.datasets[0].borderColor = colors.primaryColor;
|
||||
chart.data.datasets[0].backgroundColor = colors.primaryFill;
|
||||
applyCartesianColors(chart, colors);
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
Loading…
Reference in New Issue
Block a user