diff --git a/web/src/beanflows/dashboard/templates/positioning.html b/web/src/beanflows/dashboard/templates/positioning.html index 2618739..7833847 100644 --- a/web/src/beanflows/dashboard/templates/positioning.html +++ b/web/src/beanflows/dashboard/templates/positioning.html @@ -72,24 +72,29 @@ function _positioningUrl() { return POSITIONING_URL + '?range=' + currentRange + '&type=' + currentType; } +function _swapCanvas(url) { + var el = document.getElementById('positioning-canvas'); + el.style.minHeight = el.offsetHeight + 'px'; + el.classList.add('canvas-loading'); + htmx.ajax('GET', url, { target: '#positioning-canvas', swap: 'innerHTML' }); +} + function setRange(val) { currentRange = val; var url = _positioningUrl(); window.history.pushState({}, '', url); - document.getElementById('positioning-canvas').classList.add('canvas-loading'); - htmx.ajax('GET', url, { target: '#positioning-canvas', swap: 'innerHTML' }); + _swapCanvas(url); } function setType(val) { currentType = val; var url = _positioningUrl(); window.history.pushState({}, '', url); - document.getElementById('positioning-canvas').classList.add('canvas-loading'); - htmx.ajax('GET', url, { target: '#positioning-canvas', swap: 'innerHTML' }); // Sync type pill active state immediately (canvas swap will also re-sync) document.querySelectorAll('#type-pills .filter-pill').forEach(function (btn) { btn.classList.toggle('active', btn.textContent.trim() === (val === 'combined' ? 'F+O Combined' : 'Futures')); }); + _swapCanvas(url); } // MA toggles: client-side only — update Chart.js dataset visibility @@ -103,7 +108,9 @@ function toggleMA(key) { document.addEventListener('htmx:afterSwap', function (e) { if (e.detail.target.id === 'positioning-canvas') { - document.getElementById('positioning-canvas').classList.remove('canvas-loading'); + var el = document.getElementById('positioning-canvas'); + el.classList.remove('canvas-loading'); + el.style.minHeight = ''; // release height lock requestAnimationFrame(function() { ['priceChart', 'cotChart'].forEach(function(id) { var c = Chart.getChart(id); diff --git a/web/src/beanflows/static/css/input.css b/web/src/beanflows/static/css/input.css index 1b6ab4e..bc94cbd 100644 --- a/web/src/beanflows/static/css/input.css +++ b/web/src/beanflows/static/css/input.css @@ -736,6 +736,13 @@ transition: opacity 0.15s; } + /* Prevent canvas from collapsing to 0 height during HTMX swaps. + Chart.js needs the container to have non-zero height before it can + measure the aspect ratio. min-height is locked in JS before each swap. */ + #positioning-canvas, #supply-canvas, #warehouse-canvas, #weather-canvas { + min-height: 200px; + } + /* Freshness badges */ .freshness-bar { display: flex;