From 6461c5895714940c8a7770fdfc46027e5a9d83be Mon Sep 17 00:00:00 2001 From: Deeman Date: Thu, 26 Feb 2026 15:16:13 +0100 Subject: [PATCH] fix(web): fix Chart.js sizing after HTMX swaps on all dashboard pages Two-part fix for charts going tiny on range changes (especially 3m) and staying broken after subsequent navigations: 1. dashboard_base.html: global htmx:beforeSwap handler destroys any Chart.js instances in the swap target before HTMX replaces the DOM. Without this, the old chart's ResizeObserver remains attached to the parent container and interferes with the newly created chart instance's dimension calculations. 2. All chart pages (positioning, supply, warehouse, weather): afterSwap handler now wraps chart resize in requestAnimationFrame, ensuring the browser has completed layout before Chart.js measures container dimensions. MA toggle state is also restored inside the rAF callback after resize. Root cause: chart init scripts run synchronously during innerHTML swap, before browser layout is complete. Fast server responses (e.g. 3m = small dataset) gave even less time for layout, making the timing issue reproducible. Co-Authored-By: Claude Sonnet 4.6 --- .../dashboard/templates/dashboard_base.html | 13 ++++++++++ .../dashboard/templates/positioning.html | 24 ++++++++++++------- .../beanflows/dashboard/templates/supply.html | 6 +++++ .../dashboard/templates/warehouse.html | 6 +++++ .../dashboard/templates/weather.html | 6 +++++ 5 files changed, 46 insertions(+), 9 deletions(-) diff --git a/web/src/beanflows/dashboard/templates/dashboard_base.html b/web/src/beanflows/dashboard/templates/dashboard_base.html index b2492ba..79f6137 100644 --- a/web/src/beanflows/dashboard/templates/dashboard_base.html +++ b/web/src/beanflows/dashboard/templates/dashboard_base.html @@ -167,6 +167,19 @@ + + + {% include "_feedback_widget.html" %} {% block scripts %}{% endblock %} diff --git a/web/src/beanflows/dashboard/templates/positioning.html b/web/src/beanflows/dashboard/templates/positioning.html index 18bfa0a..2618739 100644 --- a/web/src/beanflows/dashboard/templates/positioning.html +++ b/web/src/beanflows/dashboard/templates/positioning.html @@ -104,15 +104,21 @@ function toggleMA(key) { document.addEventListener('htmx:afterSwap', function (e) { if (e.detail.target.id === 'positioning-canvas') { document.getElementById('positioning-canvas').classList.remove('canvas-loading'); - // Re-apply MA checkbox state after swap - var ma20 = document.getElementById('ma20-toggle'); - var ma50 = document.getElementById('ma50-toggle'); - var chart = Chart.getChart('priceChart'); - if (chart && ma20 && ma50) { - chart.data.datasets[1].hidden = !ma20.checked; - chart.data.datasets[2].hidden = !ma50.checked; - chart.update(); - } + requestAnimationFrame(function() { + ['priceChart', 'cotChart'].forEach(function(id) { + var c = Chart.getChart(id); + if (c) c.resize(); + }); + // Re-apply MA checkbox state after resize + var ma20 = document.getElementById('ma20-toggle'); + var ma50 = document.getElementById('ma50-toggle'); + var chart = Chart.getChart('priceChart'); + if (chart && ma20 && ma50) { + chart.data.datasets[1].hidden = !ma20.checked; + chart.data.datasets[2].hidden = !ma50.checked; + chart.update(); + } + }); } }); diff --git a/web/src/beanflows/dashboard/templates/supply.html b/web/src/beanflows/dashboard/templates/supply.html index c7d4272..c4e0998 100644 --- a/web/src/beanflows/dashboard/templates/supply.html +++ b/web/src/beanflows/dashboard/templates/supply.html @@ -71,6 +71,12 @@ function setFilter(key, val) { document.addEventListener('htmx:afterSwap', function (e) { if (e.detail.target.id === 'supply-canvas') { document.getElementById('supply-canvas').classList.remove('canvas-loading'); + requestAnimationFrame(function() { + ['supplyDemandChart', 'stuChart', 'topCountriesChart'].forEach(function(id) { + var c = Chart.getChart(id); + if (c) c.resize(); + }); + }); } }); diff --git a/web/src/beanflows/dashboard/templates/warehouse.html b/web/src/beanflows/dashboard/templates/warehouse.html index 6bc8587..668a5c5 100644 --- a/web/src/beanflows/dashboard/templates/warehouse.html +++ b/web/src/beanflows/dashboard/templates/warehouse.html @@ -66,6 +66,12 @@ function setFilter(key, val) { document.addEventListener('htmx:afterSwap', function (e) { if (e.detail.target.id === 'warehouse-canvas') { document.getElementById('warehouse-canvas').classList.remove('canvas-loading'); + requestAnimationFrame(function() { + ['stocksChart', 'agingChart', 'byPortChart'].forEach(function(id) { + var c = Chart.getChart(id); + if (c) c.resize(); + }); + }); } }); diff --git a/web/src/beanflows/dashboard/templates/weather.html b/web/src/beanflows/dashboard/templates/weather.html index 5c80b1c..9a3a0fc 100644 --- a/web/src/beanflows/dashboard/templates/weather.html +++ b/web/src/beanflows/dashboard/templates/weather.html @@ -188,6 +188,12 @@ function setFilter(key, val) { document.addEventListener('htmx:afterSwap', function (e) { if (e.detail.target.id === 'weather-canvas') { document.getElementById('weather-canvas').classList.remove('canvas-loading'); + requestAnimationFrame(function() { + ['globalStressChart', 'locStressChart', 'locTempChart'].forEach(function(id) { + var c = Chart.getChart(id); + if (c) c.resize(); + }); + }); } });