// --- Chart Management --- // Keep chart instances internal to this module let chartInstancesScada = {}; let chartInstancesDrawing = {}; // --- Chart Configurations --- const scadaChartLabels = ['Found in SCADA', 'Not Found in SCADA']; // const scadaChartColors = ['rgb(13, 110, 253)', 'rgb(220, 53, 69)']; // Removed const drawingChartLabels = ['Found in Drawing', 'Not Found in Drawing']; // const drawingChartColors = ['rgb(25, 135, 84)', 'rgb(220, 53, 69)']; // Removed // Unified colors: Green for Found, Red for Not Found const commonChartColors = ['rgb(25, 135, 84)', 'rgb(220, 53, 69)']; // --- Chart Click Handler --- // Note: This needs access to showDetailsModal, which might need to be passed or refactored. function handleChartClick(event, elements, chart, context) { if (elements.length > 0 && selectedProjectName) { // Relies on global selectedProjectName for now const clickedElementIndex = elements[0].index; const isOverallChart = chart.canvas.id.startsWith('overall-'); const identifier = isOverallChart ? '__overall__' : chart.canvas.id.replace(`chart-${context}-`, ''); const categoryType = clickedElementIndex === 0 ? 'found' : 'notFound'; // TODO: Refactor showDetailsModal call - currently global // showDetailsModal(selectedProjectName, identifier, categoryType, context); // Call modalManager function instead modalManagerShowDetails(selectedProjectName, identifier, categoryType, context); } } // --- Generic Helper to create chart config --- // Note: Relies on global currentProjectData and selectedProjectName for tooltips function createChartConfig(chartCounts, total, context, identifier, projectName) { const labels = context === 'scada' ? scadaChartLabels : drawingChartLabels; // const colors = context === 'scada' ? scadaChartColors : drawingChartColors; // Use common colors instead const datasetLabel = context === 'scada' ? 'SCADA Match' : 'Drawing Match'; // Retrieve the correct project's progress data for tooltip calculation // TODO: Find a way to access state without global reliance (e.g., pass data in) const projectProgress = (currentProjectData[projectName] && currentProjectData[projectName].progress) ? currentProjectData[projectName].progress : {}; return { type: 'pie', data: { labels: labels, datasets: [{ label: datasetLabel, data: chartCounts, backgroundColor: commonChartColors, // Use unified colors hoverOffset: 4 }] }, options: { responsive: true, maintainAspectRatio: false, onClick: (event, elements, chart) => handleChartClick(event, elements, chart, context), // Pass context plugins: { legend: { display: false }, tooltip: { callbacks: { label: function(ctxTooltip) { let label = ctxTooltip.label || ''; if (label) label += ': '; const value = ctxTooltip.parsed; if (value !== null) label += value; // Use total passed for panel charts, access stored data for overall const chartTotal = (identifier === 'overall' && projectProgress.overall) ? projectProgress.overall.total_csv : total; if (chartTotal && chartTotal > 0 && value !== null) { label += ` (${((value / chartTotal) * 100).toFixed(1)}%)`; } return label; } } } } } }; } // --- Update Functions --- /** * Updates or creates the overall progress chart for a context (scada/drawing). * @param {string} context - 'scada' or 'drawing' * @param {string} canvasId - The ID of the canvas element. * @param {Array} chartCounts - The data array for the chart (e.g., [found, notFound]). * @param {number} total - The total number of items for percentage calculation. * @param {boolean} isVisible - Whether the section containing the chart is currently visible. */ function updateOverallChart(context, canvasId, chartCounts, total, isVisible) { console.log(`[ChartManager] updateOverallChart called for context: ${context}, canvasId: ${canvasId}`); // Log entry console.log(`[ChartManager] Received chartCounts: ${JSON.stringify(chartCounts)}, total: ${total}`); // Log data const chartInstances = context === 'scada' ? chartInstancesScada : chartInstancesDrawing; const chartKey = 'overall'; // Find the parent container based on context const parentContainer = context === 'scada' ? uiElements.overallScadaProgress : uiElements.overallDrawingProgress; console.log(`[ChartManager] Parent container for ${context}:`, parentContainer); // Log container found if (!parentContainer) { console.error(`[ChartManager] Parent container for overall ${context} chart not found.`); return; } let canvas = document.getElementById(canvasId); console.log(`[ChartManager] Existing canvas found for ${canvasId}:`, canvas); // Check if the found canvas is unusable (zero dimensions) if (canvas && (canvas.offsetWidth === 0 || canvas.offsetHeight === 0)) { console.warn(`[ChartManager] Found canvas ${canvasId} has zero dimensions. Removing and recreating.`); // Destroy the associated Chart.js instance if it exists if (chartInstances[chartKey]) { console.log(`[ChartManager] Destroying Chart.js instance for zero-dimension canvas ${canvasId}.`); chartInstances[chartKey].destroy(); delete chartInstances[chartKey]; } canvas.remove(); // Remove the faulty canvas element canvas = null; // Force recreation below } // If canvas doesn't exist (or was removed), create and append it if (!canvas) { console.log(`[ChartManager] Canvas ${canvasId} needs creation.`); canvas = document.createElement('canvas'); canvas.id = canvasId; canvas.className = 'overall-chart-canvas'; parentContainer.innerHTML = ''; // Clear the container first before adding canvas parentContainer.appendChild(canvas); console.log(`[ChartManager] Canvas ${canvasId} created and appended.`); } // Now canvas definitely exists (or we returned early) if (chartInstances[chartKey]) { // Update existing chart data if it changed if (JSON.stringify(chartInstances[chartKey].data.datasets[0].data) !== JSON.stringify(chartCounts)) { console.log(`Updating overall ${context} chart data.`); chartInstances[chartKey].data.datasets[0].data = chartCounts; chartInstances[chartKey].update('none'); // Update without animation } } else { // No existing chart instance, create new one // Create new chart console.log(`Creating overall ${context} chart.`); const ctx = canvas.getContext('2d'); if (ctx) { chartInstances[chartKey] = new Chart(ctx, createChartConfig(chartCounts, total, context, chartKey, selectedProjectName)); // Relies on global selectedProjectName } else { console.error(`[ChartManager] Failed to get 2D context for canvas ${canvasId}.`); } } } /** * Updates or creates/destroys panel-specific charts for a context. * @param {string} context - 'scada' or 'drawing' * @param {HTMLElement} panelsContainer - The container element for panel charts. * @param {object} panelsData - The progress data object for all panels. * @param {boolean} isVisible - Whether the section containing the charts is currently visible. */ function updatePanelCharts(context, panelsContainer, panelsData, isVisible) { const chartInstances = context === 'scada' ? chartInstancesScada : chartInstancesDrawing; const incomingPanelNames = new Set(Object.keys(panelsData || {}).sort()); const existingInstanceNames = new Set(Object.keys(chartInstances).filter(k => k !== 'overall')); if (!isVisible) { // If section is not visible, destroy all existing panel charts for this context console.log(`[ChartManager] Destroying hidden panel charts for context: ${context}`); existingInstanceNames.forEach(panelName => { if (chartInstances[panelName]) { chartInstances[panelName].destroy(); delete chartInstances[panelName]; } }); // Remove any dynamically created chart containers if section is hidden panelsContainer.querySelectorAll(`.chart-container[id^="chart-container-${context}-"]`).forEach(el => el.remove()); if (!panelsContainer.querySelector('p')) { // Add placeholder if empty panelsContainer.innerHTML = '

Panel data hidden.

'; } return; // Don't proceed further if the section is hidden } // Clear placeholder/hidden message if we have data and are visible const placeholder = panelsContainer.querySelector('p'); if(placeholder) placeholder.remove(); // Update/Create charts for incoming panels if (incomingPanelNames.size > 0) { incomingPanelNames.forEach(panelName => { const panel = panelsData[panelName]; const panelTotal = (panel && panel.total) || 0; let panelChartCounts = [0, 0]; if (panel) { if (context === 'scada') { panelChartCounts = [(panel.found_both || 0) + (panel.found_scada_only || 0), (panel.found_drawing_only || 0) + (panel.missing_both || 0)]; } else { // drawing panelChartCounts = [(panel.found_both || 0) + (panel.found_drawing_only || 0), (panel.found_scada_only || 0) + (panel.missing_both || 0)]; } } const chartKey = panelName; const canvasId = `chart-${context}-${panelName}`; const containerId = `chart-container-${context}-${panelName}`; if (chartInstances[chartKey]) { // Update existing chart if (JSON.stringify(chartInstances[chartKey].data.datasets[0].data) !== JSON.stringify(panelChartCounts)) { chartInstances[chartKey].data.datasets[0].data = panelChartCounts; // Update tooltip data source if needed (or ensure createChartConfig handles it) chartInstances[chartKey].update('none'); } } else { // Create new chart element and instance let container = document.getElementById(containerId); if (!container) { console.log(`Creating new ${context} panel elements and chart (visible) for: ${panelName}`); container = document.createElement('div'); container.id = containerId; container.className = 'chart-container'; container.innerHTML = ` ${panelName} `; panelsContainer.appendChild(container); } const canvas = document.getElementById(canvasId); if (canvas) { const ctx = canvas.getContext('2d'); chartInstances[chartKey] = new Chart(ctx, createChartConfig(panelChartCounts, panelTotal, context, chartKey, selectedProjectName)); // Relies on global selectedProjectName } else { console.error(`Canvas element with ID ${canvasId} not found after creation.`); } } }); } else { // No panel data, ensure placeholder is shown if (!panelsContainer.querySelector('p')) { panelsContainer.innerHTML = '

No panel data available yet.

'; } } // Remove charts and elements for panels that no longer exist existingInstanceNames.forEach(panelName => { if (!incomingPanelNames.has(panelName)) { console.log(`Removing ${context} panel elements and chart for: ${panelName}`); if (chartInstances[panelName]) { chartInstances[panelName].destroy(); delete chartInstances[panelName]; } const chartElement = document.getElementById(`chart-container-${context}-${panelName}`); if (chartElement) { chartElement.remove(); } } }); } /** * Destroys all tracked chart instances. */ function chartManagerDestroyAll() { console.log("[ChartManager] Destroying all chart instances."); Object.values(chartInstancesScada).forEach(chart => chart?.destroy()); chartInstancesScada = {}; Object.values(chartInstancesDrawing).forEach(chart => chart?.destroy()); chartInstancesDrawing = {}; } // --- Export (if using modules in the future) --- // export { updateOverallChart, updatePanelCharts, chartManagerDestroyAll };