Updated the web #31
@ -37,6 +37,9 @@
|
|||||||
--color-table-alt: #f8faf9;
|
--color-table-alt: #f8faf9;
|
||||||
--color-table-hover: #e9f1ed;
|
--color-table-hover: #e9f1ed;
|
||||||
--color-progress-track: #e0e0e0;
|
--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"] {
|
[data-theme="dark"] {
|
||||||
@ -68,6 +71,9 @@
|
|||||||
--color-table-alt: #182420;
|
--color-table-alt: #182420;
|
||||||
--color-table-hover: #20302a;
|
--color-table-hover: #20302a;
|
||||||
--color-progress-track: #2a3a33;
|
--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>
|
</script>
|
||||||
<link rel="stylesheet" href="{% static 'expenses/css/base.css' %}">
|
<link rel="stylesheet" href="{% static 'expenses/css/base.css' %}">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script src="{% static 'expenses/js/charts.js' %}"></script>
|
||||||
{% block extra_css %}{% endblock %}
|
{% block extra_css %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@ -259,25 +259,32 @@
|
|||||||
const catLabels = [{% for row in by_category_chart %}"{{ row.category__name }}",{% endfor %}];
|
const catLabels = [{% for row in by_category_chart %}"{{ row.category__name }}",{% endfor %}];
|
||||||
const catData = [{% for row in by_category_chart %}{{ row.total|unlocalize }},{% 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',
|
type: 'pie',
|
||||||
data: {
|
data: {
|
||||||
labels: catLabels,
|
labels: catLabels,
|
||||||
datasets: [{
|
datasets: [{
|
||||||
data: catData,
|
data: catData,
|
||||||
backgroundColor: [
|
backgroundColor: categoryChartColors.palette
|
||||||
'#ff6384', '#36a2eb', '#cc65fe', '#ffce56', '#4bc0c0', '#ffa1b5'
|
|
||||||
]
|
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
plugins: {
|
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>
|
</script>
|
||||||
|
|
||||||
<h3>Evolución anual por cuenta ({{ selected_year }})</h3>
|
<h3>Evolución anual por cuenta ({{ selected_year }})</h3>
|
||||||
@ -297,34 +304,50 @@
|
|||||||
const labels = {{ chart_labels|safe }};
|
const labels = {{ chart_labels|safe }};
|
||||||
const data = {{ chart_data|safe }};
|
const data = {{ chart_data|safe }};
|
||||||
const ctx = document.getElementById('mainChart');
|
const ctx = document.getElementById('mainChart');
|
||||||
|
const mainChartColors = getChartColors();
|
||||||
|
|
||||||
new Chart(ctx, {
|
const mainChart = new Chart(ctx, {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: {
|
data: {
|
||||||
labels: labels,
|
labels: labels,
|
||||||
datasets:[{
|
datasets:[{
|
||||||
label: 'Gastos (€)',
|
label: 'Gastos (€)',
|
||||||
data: data,
|
data: data,
|
||||||
backgroundColor: 'rgba(54, 162, 235, 0.6)',
|
backgroundColor: mainChartColors.primaryColor,
|
||||||
backgroundColor: 'rgba(54, 162, 235, 1)',
|
|
||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
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"];
|
const allMonths = ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"];
|
||||||
|
|
||||||
{% for acc in accounts_charts %}
|
{% for acc in accounts_charts %}
|
||||||
(function() {
|
(function() {
|
||||||
const dataAccount = {{ acc.data|safe }};
|
const dataAccount = {{ acc.data|safe }};
|
||||||
const filteredLabels = allMonths.slice(0, dataAccount.length);
|
const filteredLabels = allMonths.slice(0, dataAccount.length);
|
||||||
|
const accountChartColors = getChartColors();
|
||||||
|
|
||||||
new Chart(
|
const accountChart = new Chart(
|
||||||
document.getElementById('accountChart{{ acc.id }}'),
|
document.getElementById('accountChart{{ acc.id }}'),
|
||||||
{
|
{
|
||||||
type: 'line',
|
type: 'line',
|
||||||
@ -334,8 +357,8 @@
|
|||||||
label: 'Saldo Real (€)',
|
label: 'Saldo Real (€)',
|
||||||
data: dataAccount,
|
data: dataAccount,
|
||||||
fill: false,
|
fill: false,
|
||||||
borderColor: 'rgba(54, 162, 235, 1)',
|
borderColor: accountChartColors.primaryColor,
|
||||||
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
backgroundColor: accountChartColors.primaryFill,
|
||||||
tension: 0.1
|
tension: 0.1
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
@ -343,14 +366,26 @@
|
|||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
scales: {
|
scales: {
|
||||||
|
x: {
|
||||||
|
grid: { color: accountChartColors.gridColor },
|
||||||
|
ticks: { color: accountChartColors.tickColor }
|
||||||
|
},
|
||||||
y: {
|
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 %}
|
{% endfor %}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -45,22 +45,40 @@
|
|||||||
const data = {{ mini_chart_data|safe }};
|
const data = {{ mini_chart_data|safe }};
|
||||||
|
|
||||||
const ctx = document.getElementById('miniChart');
|
const ctx = document.getElementById('miniChart');
|
||||||
|
const miniChartColors = getChartColors();
|
||||||
|
|
||||||
new Chart(ctx, {
|
const miniChart = new Chart(ctx, {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: {
|
data: {
|
||||||
labels: labels,
|
labels: labels,
|
||||||
datasets:[{
|
datasets:[{
|
||||||
label: 'Gastos',
|
label: 'Gastos',
|
||||||
data: data,
|
data: data,
|
||||||
|
backgroundColor: miniChartColors.primaryColor,
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
responsive: true,
|
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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
|
||||||
|
registerThemedChart(miniChart, function (chart, colors) {
|
||||||
|
chart.data.datasets[0].backgroundColor = colors.primaryColor;
|
||||||
|
applyCartesianColors(chart, colors);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@ -59,31 +59,73 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
const monthlyData = {{ monthly_data|safe }};
|
const monthlyData = {{ monthly_data|safe }};
|
||||||
|
const monthlyChartColors = getChartColors();
|
||||||
|
|
||||||
new Chart(document.getElementById('monthlyChart'), {
|
const monthlyChart = new Chart(document.getElementById('monthlyChart'), {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: {
|
data: {
|
||||||
labels: ['Ene','Feb','Mar','Abr','May','Jun','Jul','Ago','Sep','Oct','Nov','Dic'],
|
labels: ['Ene','Feb','Mar','Abr','May','Jun','Jul','Ago','Sep','Oct','Nov','Dic'],
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Gasto mensual',
|
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 kmData = {{ km_data|safe }};
|
||||||
const kmDates = {{ km_dates|safe }};
|
const kmDates = {{ km_dates|safe }};
|
||||||
|
const kmChartColors = getChartColors();
|
||||||
|
|
||||||
new Chart(document.getElementById('kmChart'), {
|
const kmChart = new Chart(document.getElementById('kmChart'), {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
labels: kmDates,
|
labels: kmDates,
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Km entre repostajes',
|
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>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Loading…
Reference in New Issue
Block a user