From 38d585e071f06b899a7ac8cb89688a867870b5fd Mon Sep 17 00:00:00 2001 From: Matt Batchelder Date: Sat, 21 Feb 2026 01:55:20 -0500 Subject: [PATCH] Enhance dashboard chart animations with improved element selection, faster update speed, and robust error handling --- theme/assets/js/dashboard-animator.js | 165 ++++++++++++++++++++------ theme/blocks/index.php | 109 ++++++++--------- 2 files changed, 176 insertions(+), 98 deletions(-) diff --git a/theme/assets/js/dashboard-animator.js b/theme/assets/js/dashboard-animator.js index b8a7ef4..b28cbb0 100644 --- a/theme/assets/js/dashboard-animator.js +++ b/theme/assets/js/dashboard-animator.js @@ -14,16 +14,30 @@ class DashboardAnimator { // Chart dimensions this.barChartHeight = 120; - this.chartUpdateSpeed = 2000; // Base cycle speed in ms (controls smoothness) + this.chartUpdateSpeed = 1500; // Faster: 1.5s cycle for constant movement - // Cache DOM elements - this.barsGroup1 = svg.querySelectorAll('#bars-group-1 .bar'); - this.barsGroup2 = svg.querySelectorAll('#bars-group-2 .bar'); - this.valuesGroup1 = svg.querySelectorAll('#values-group-1 .chart-value'); - this.valuesGroup2 = svg.querySelectorAll('#values-group-2 .chart-value'); - this.linePath = svg.getElementById('line-path'); - this.lineFill = svg.getElementById('line-fill'); - this.pieSegments = svg.querySelectorAll('.pie-segment'); + // Cache DOM elements - try multiple selector methods for robustness + this.barsGroup1 = this.svg.querySelectorAll('#bars-group-1 rect.bar'); + this.barsGroup2 = this.svg.querySelectorAll('#bars-group-2 rect.bar'); + this.valuesGroup1 = this.svg.querySelectorAll('#values-group-1 text.chart-value'); + this.valuesGroup2 = this.svg.querySelectorAll('#values-group-2 text.chart-value'); + this.linePath = this.svg.querySelector('#line-path'); + this.lineFill = this.svg.querySelector('#line-fill'); + this.pieSegments = this.svg.querySelectorAll('g.pie-segment'); + + // Debug: Check if elements were found + const hasElements = this.barsGroup1.length > 0 || this.linePath || this.pieSegments.length > 0; + if (!hasElements) { + console.warn('[DashboardAnimator] No chart elements found. Debugging info:'); + console.warn(' SVG element:', this.svg); + console.warn(' bars-group-1:', this.barsGroup1.length); + console.warn(' line-path:', !!this.linePath); + console.warn(' pie-segment:', this.pieSegments.length); + // Try direct getElementById as fallback + console.warn(' Testing getElementById on document:', document.getElementById('bars-group-1')); + } else { + console.log('[DashboardAnimator] ✓ Found elements - Bars:', this.barsGroup1.length, 'Line:', !!this.linePath, 'Pie:', this.pieSegments.length); + } // Noise generators for smooth, realistic data this.noisePhase1 = Math.random() * Math.PI * 2; @@ -36,9 +50,20 @@ class DashboardAnimator { } init() { - if (!this.isReducedMotion) { + if (!this.isReducedMotion && (this.barsGroup1.length || this.linePath || this.pieSegments.length)) { + console.log('[DashboardAnimator] ✓ Starting animation loop'); + // Visual indicator that animator started + this.svg.style.cssText = 'border: 2px solid green; border-radius: 4px;'; + setTimeout(() => { + this.svg.style.border = ''; + }, 500); this.animate(); this.attachHoverListeners(); + } else if (this.isReducedMotion) { + console.log('[DashboardAnimator] Motion preference: reduced'); + } else { + console.log('[DashboardAnimator] ✗ No animatable elements found'); + this.svg.style.cssText = 'border: 2px solid red; border-radius: 4px;'; } } @@ -72,14 +97,20 @@ class DashboardAnimator { * Update bar chart with smooth animation */ updateBars(barsElements, valuesElements, maxValue = 100, isPercent = true) { + if (!barsElements || barsElements.length === 0) return; + barsElements.forEach((bar, index) => { const value = this.generateValue(this.noisePhase1, index, maxValue); const heightPercent = (value / maxValue) * 100; const height = (heightPercent / 100) * this.barChartHeight; - bar.setAttribute('height', height); + // For SVG bars to grow upward, adjust y position inversely + const y = this.barChartHeight - height; - if (valuesElements[index]) { + bar.setAttribute('height', height); + bar.setAttribute('y', y); + + if (valuesElements && valuesElements[index]) { valuesElements[index].textContent = isPercent ? Math.round(value) + '%' : Math.round(value); @@ -92,6 +123,8 @@ class DashboardAnimator { * Update line graph with smooth curve */ updateLineGraph() { + if (!this.linePath || !this.lineFill) return; + const time = (Date.now() - this.startTime) / this.chartUpdateSpeed; const points = []; @@ -119,6 +152,8 @@ class DashboardAnimator { * Update pie chart with smooth segment rotation */ updatePieChart() { + if (!this.pieSegments || this.pieSegments.length === 0) return; + const time = (Date.now() - this.startTime) / (this.chartUpdateSpeed * 0.5); const baseRotation = (time * 15) % 360; // Slow rotation @@ -134,10 +169,14 @@ class DashboardAnimator { */ animate = () => { if (!this.isPaused) { - this.updateBars(this.barsGroup1, this.valuesGroup1, 100, true); - this.updateBars(this.barsGroup2, this.valuesGroup2, 5000, false); - this.updateLineGraph(); - this.updatePieChart(); + try { + this.updateBars(this.barsGroup1, this.valuesGroup1, 100, true); + this.updateBars(this.barsGroup2, this.valuesGroup2, 5000, false); + this.updateLineGraph(); + this.updatePieChart(); + } catch (err) { + console.error('[DashboardAnimator] Animation error:', err); + } } this.animationId = requestAnimationFrame(this.animate); @@ -189,36 +228,84 @@ class DashboardAnimator { */ function initDashboardAnimation() { const dashboardCharts = document.querySelectorAll('.dashboard-chart'); + console.log('[initDashboardAnimation] Found', dashboardCharts.length, 'dashboard charts'); if (!dashboardCharts.length) return; - const observerOptions = { - root: null, - rootMargin: '50px', - threshold: 0.1 - }; - - const observer = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting) { - if (!entry.target._dashboardAnimator) { - entry.target._dashboardAnimator = new DashboardAnimator(entry.target); - } - } else { - // Pause animation when out of view for performance - if (entry.target._dashboardAnimator) { - entry.target._dashboardAnimator.pause(); + dashboardCharts.forEach((chart, idx) => { + // Check if element is likely in viewport + const rect = chart.getBoundingClientRect(); + const isVisible = rect.top < window.innerHeight && rect.bottom > 0; + console.log('[initDashboardAnimation] Chart', idx, 'visible:', isVisible, 'rect:', rect); + + if (isVisible || !chart._dashboardAnimator) { + // Start animation immediately if visible or if not set up yet + if (!chart._dashboardAnimator) { + console.log('[initDashboardAnimation] Creating DashboardAnimator for chart', idx); + try { + chart._dashboardAnimator = new DashboardAnimator(chart); + console.log('[initDashboardAnimation] ✓ DashboardAnimator created successfully'); + } catch (err) { + console.error('[initDashboardAnimation] ✗ Failed to create DashboardAnimator:', err); } } - }); - }, observerOptions); + } + }); - dashboardCharts.forEach(chart => observer.observe(chart)); + // Also set up IntersectionObserver for when elements come into view + if ('IntersectionObserver' in window) { + const observerOptions = { + root: null, + rootMargin: '100px', + threshold: 0.05 + }; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + console.log('[IntersectionObserver]', entry.isIntersecting ? 'Intersecting' : 'Not intersecting'); + if (entry.isIntersecting) { + if (!entry.target._dashboardAnimator) { + console.log('[IntersectionObserver] Creating new DashboardAnimator'); + try { + entry.target._dashboardAnimator = new DashboardAnimator(entry.target); + } catch (err) { + console.error('[IntersectionObserver] Failed:', err); + } + } else { + entry.target._dashboardAnimator.resume(); + } + } else { + if (entry.target._dashboardAnimator) { + entry.target._dashboardAnimator.pause(); + } + } + }); + }, observerOptions); + + dashboardCharts.forEach(chart => observer.observe(chart)); + } } // Auto-initialize when DOM is ready -if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initDashboardAnimation); -} else { - initDashboardAnimation(); +console.log('[Dashboard Animator] Script loaded, readyState:', document.readyState); + +function tryInitialize() { + console.log('[Dashboard Animator] tryInitialize called'); + const charts = document.querySelectorAll('.dashboard-chart'); + console.log('[Dashboard Animator] Found', charts.length, 'dashboard charts on page'); + if (charts.length > 0) { + console.log('[Dashboard Animator] Found charts, initializing now...'); + initDashboardAnimation(); + } else { + console.log('[Dashboard Animator] No charts found yet, retrying in 100ms...'); + setTimeout(tryInitialize, 100); + } +} + +if (document.readyState === 'loading') { + console.log('[Dashboard Animator] DOM still loading, waiting for DOMContentLoaded'); + document.addEventListener('DOMContentLoaded', tryInitialize); +} else { + console.log('[Dashboard Animator] DOM already loaded, initializing immediately'); + tryInitialize(); } diff --git a/theme/blocks/index.php b/theme/blocks/index.php index d63e8bf..5ce3e3b 100644 --- a/theme/blocks/index.php +++ b/theme/blocks/index.php @@ -1347,86 +1347,77 @@ function oribi_render_platform_row( $a ) { // Render dashboard chart animation $visual_html = '
- - - + + - - + + - Performance + Performance - - - - - + + + + + - API - Cache - DB - Queue - Worker + API + Cache + DB + Queue + Worker - 0% - 0% - 0% - 0% - 0% + 0% + 0% + 0% + 0% + 0% - Requests/sec + Requests/sec - - - - + + + + - Read - Write - Update - Delete + Read + Write + Update + Delete - 0 - 0 - 0 - 0 + 0 + 0 + 0 + 0 - Traffic Trend + Traffic Trend - - - - + + + + - + - Distribution + Distribution - + - + @@ -1434,18 +1425,18 @@ function oribi_render_platform_row( $a ) { - - 100% + + 100% - - Service A - - Service B + + Service A + + Service B - Service C + Service C - Service D + Service D
';