274 lines
13 KiB
JavaScript
274 lines
13 KiB
JavaScript
// --- 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<number>} 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 = '<p class="text-center fst-italic">Panel data hidden.</p>';
|
|
}
|
|
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 = `
|
|
<span class="chart-label">${panelName}</span>
|
|
<canvas id="${canvasId}" class="panel-chart-canvas"></canvas>
|
|
`;
|
|
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 = '<p class="text-center fst-italic">No panel data available yet.</p>';
|
|
}
|
|
}
|
|
|
|
// 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 };
|