// OPAT plotting module for the 4DSTAR Bundle Manager
// Handles interactive plotting functionality using Plotly.js
// Import dependencies (these will be injected when integrated)
let stateManager, domManager;
// Plotting UI elements
let plotIndexSelector, plotTableSelector, plotXAxis, plotYAxis, plotType, plotTitle;
let createPlotBtn, savePlotBtn, clearPlotBtn;
let plotContainer, plotPlaceholder, plotDisplay;
// Current plot state
let currentPlotData = null;
let currentPlotLayout = null;
// Initialize plotting UI elements
function initializePlottingElements() {
plotIndexSelector = document.getElementById('plot-index-selector');
plotTableSelector = document.getElementById('plot-table-selector');
plotXAxis = document.getElementById('plot-x-axis');
plotYAxis = document.getElementById('plot-y-axis');
plotType = document.getElementById('plot-type');
plotTitle = document.getElementById('plot-title');
createPlotBtn = document.getElementById('create-plot-btn');
savePlotBtn = document.getElementById('save-plot-btn');
clearPlotBtn = document.getElementById('clear-plot-btn');
plotContainer = document.getElementById('plot-container');
plotPlaceholder = document.getElementById('plot-placeholder');
plotDisplay = document.getElementById('plot-display');
// Check if elements were found
const elements = {
plotIndexSelector, plotTableSelector, plotXAxis, plotYAxis, plotType, plotTitle,
createPlotBtn, savePlotBtn, clearPlotBtn, plotContainer, plotPlaceholder, plotDisplay
};
const missingElements = Object.entries(elements)
.filter(([name, element]) => !element)
.map(([name]) => name);
if (missingElements.length > 0) {
console.warn('[OPAT_PLOTTING] Missing elements:', missingElements);
console.warn('[OPAT_PLOTTING] Will retry initialization when needed');
return; // Don't add event listeners if elements are missing
}
// Event listeners (only add if all elements are present)
plotIndexSelector.addEventListener('change', handlePlotIndexChange);
plotTableSelector.addEventListener('change', handlePlotTableChange);
createPlotBtn.addEventListener('click', handleCreatePlot);
savePlotBtn.addEventListener('click', handleSavePlot);
clearPlotBtn.addEventListener('click', handleClearPlot);
// Set up a backup event listener setup function for later use
setupEventListenersIfNeeded();
console.log('[OPAT_PLOTTING] All plotting elements found and initialized successfully');
}
// Setup event listeners if they weren't set up during initial initialization
function setupEventListenersIfNeeded() {
// This function can be called later to ensure event listeners are properly set up
// even if elements weren't available during initial setup
const selectElements = {
'plot-index-selector': handlePlotIndexChange,
'plot-table-selector': handlePlotTableChange
};
const buttonElements = {
'create-plot-btn': handleCreatePlot,
'save-plot-btn': handleSavePlot,
'clear-plot-btn': handleClearPlot
};
// Add change listeners to select elements
for (const [elementId, handler] of Object.entries(selectElements)) {
const element = document.getElementById(elementId);
if (element && !element.hasAttribute('data-plotting-change-listener')) {
element.addEventListener('change', handler);
element.setAttribute('data-plotting-change-listener', 'true');
console.log(`[OPAT_PLOTTING] Added change listener to ${elementId}`);
}
}
// Add click listeners to button elements
for (const [elementId, handler] of Object.entries(buttonElements)) {
const element = document.getElementById(elementId);
if (element && !element.hasAttribute('data-plotting-click-listener')) {
element.addEventListener('click', handler);
element.setAttribute('data-plotting-click-listener', 'true');
console.log(`[OPAT_PLOTTING] Added click listener to ${elementId}`);
}
}
}
// Populate plot index selector when OPAT file is loaded
function populatePlotIndexSelector() {
console.log('[OPAT_PLOTTING] populatePlotIndexSelector called');
// Always try to find the element fresh, in case it wasn't available during initial setup
const selector = document.getElementById('plot-index-selector');
if (!selector) {
console.error('[OPAT_PLOTTING] Could not find plot-index-selector element in DOM');
return;
}
// Update our reference
plotIndexSelector = selector;
const opatFile = stateManager.getOPATFile();
if (!opatFile) {
console.warn('[OPAT_PLOTTING] No OPAT file available');
plotIndexSelector.innerHTML = '';
return;
}
console.log('[OPAT_PLOTTING] OPAT file found with', opatFile.cards.size, 'cards');
plotIndexSelector.innerHTML = '';
let optionCount = 0;
for (const [indexName, card] of opatFile.cards) {
const option = document.createElement('option');
option.value = indexName;
option.textContent = `${indexName} (${card.tableData.size} tables)`;
plotIndexSelector.appendChild(option);
optionCount++;
console.log('[OPAT_PLOTTING] Added option:', indexName);
}
console.log(`[OPAT_PLOTTING] Successfully populated ${optionCount} index vectors`);
// Also initialize other selectors if they weren't found during initial setup
if (!plotTableSelector) {
plotTableSelector = document.getElementById('plot-table-selector');
}
if (!plotXAxis) {
plotXAxis = document.getElementById('plot-x-axis');
}
if (!plotYAxis) {
plotYAxis = document.getElementById('plot-y-axis');
}
// Ensure event listeners are set up
ensureEventListenersAreSetup();
}
// Ensure event listeners are properly set up (called when needed)
function ensureEventListenersAreSetup() {
console.log('[OPAT_PLOTTING] Ensuring event listeners are set up');
// Check and set up index selector listener
const indexSelector = document.getElementById('plot-index-selector');
if (indexSelector && !indexSelector.hasAttribute('data-plotting-change-listener')) {
indexSelector.addEventListener('change', handlePlotIndexChange);
indexSelector.setAttribute('data-plotting-change-listener', 'true');
console.log('[OPAT_PLOTTING] Added change listener to plot-index-selector');
}
// Check and set up table selector listener
const tableSelector = document.getElementById('plot-table-selector');
if (tableSelector && !tableSelector.hasAttribute('data-plotting-change-listener')) {
tableSelector.addEventListener('change', handlePlotTableChange);
tableSelector.setAttribute('data-plotting-change-listener', 'true');
console.log('[OPAT_PLOTTING] Added change listener to plot-table-selector');
}
// Check and set up create plot button listener
const createPlotBtn = document.getElementById('create-plot-btn');
if (createPlotBtn && !createPlotBtn.hasAttribute('data-plotting-click-listener')) {
createPlotBtn.addEventListener('click', handleCreatePlot);
createPlotBtn.setAttribute('data-plotting-click-listener', 'true');
console.log('[OPAT_PLOTTING] Added click listener to create-plot-btn');
}
// Check and set up save plot button listener
const savePlotBtn = document.getElementById('save-plot-btn');
if (savePlotBtn && !savePlotBtn.hasAttribute('data-plotting-click-listener')) {
savePlotBtn.addEventListener('click', handleSavePlot);
savePlotBtn.setAttribute('data-plotting-click-listener', 'true');
console.log('[OPAT_PLOTTING] Added click listener to save-plot-btn');
}
// Update our references
if (indexSelector) plotIndexSelector = indexSelector;
if (tableSelector) plotTableSelector = tableSelector;
if (createPlotBtn) createPlotButton = createPlotBtn;
if (savePlotBtn) savePlotButton = savePlotBtn;
}
// Handle index vector selection change
function handlePlotIndexChange() {
console.log('[OPAT_PLOTTING] handlePlotIndexChange called');
// Ensure we have references to the elements
if (!plotIndexSelector) plotIndexSelector = document.getElementById('plot-index-selector');
if (!plotTableSelector) plotTableSelector = document.getElementById('plot-table-selector');
if (!plotXAxis) plotXAxis = document.getElementById('plot-x-axis');
if (!plotYAxis) plotYAxis = document.getElementById('plot-y-axis');
if (!plotIndexSelector || !plotTableSelector || !plotXAxis || !plotYAxis) {
console.error('[OPAT_PLOTTING] Missing required elements for index change');
return;
}
const selectedIndex = plotIndexSelector.value;
console.log('[OPAT_PLOTTING] Selected index:', selectedIndex);
plotTableSelector.innerHTML = '';
plotXAxis.innerHTML = '';
plotYAxis.innerHTML = '';
if (!selectedIndex) {
console.log('[OPAT_PLOTTING] No index selected, returning');
return;
}
const opatFile = stateManager.getOPATFile();
if (!opatFile) return;
const card = opatFile.cards.get(selectedIndex);
if (!card) return;
// Populate table selector
for (const [tableName, tableData] of card.tableData) {
const option = document.createElement('option');
option.value = tableName;
option.textContent = `${tableName} (${tableData.N_R}×${tableData.N_C})`;
plotTableSelector.appendChild(option);
}
console.log(`[OPAT_PLOTTING] Populated ${card.tableData.size} tables for index ${selectedIndex}`);
}
// Handle table selection change
function handlePlotTableChange() {
console.log('[OPAT_PLOTTING] handlePlotTableChange called');
// Ensure we have references to the elements
if (!plotIndexSelector) plotIndexSelector = document.getElementById('plot-index-selector');
if (!plotTableSelector) plotTableSelector = document.getElementById('plot-table-selector');
if (!plotXAxis) plotXAxis = document.getElementById('plot-x-axis');
if (!plotYAxis) plotYAxis = document.getElementById('plot-y-axis');
if (!plotIndexSelector || !plotTableSelector || !plotXAxis || !plotYAxis) {
console.error('[OPAT_PLOTTING] Missing required elements for table change');
return;
}
const selectedIndex = plotIndexSelector.value;
const selectedTable = plotTableSelector.value;
console.log('[OPAT_PLOTTING] Selected index:', selectedIndex, 'Selected table:', selectedTable);
plotXAxis.innerHTML = '';
plotYAxis.innerHTML = '';
if (!selectedIndex || !selectedTable) {
console.log('[OPAT_PLOTTING] No index or table selected, returning');
return;
}
const opatFile = stateManager.getOPATFile();
if (!opatFile) {
console.error('[OPAT_PLOTTING] No OPAT file available');
return;
}
const card = opatFile.cards.get(selectedIndex);
if (!card) {
console.error('[OPAT_PLOTTING] Card not found for index:', selectedIndex);
return;
}
const tableData = card.tableData.get(selectedTable);
if (!tableData) {
console.error('[OPAT_PLOTTING] Table data not found for table:', selectedTable);
return;
}
// Get the table index entry for names
const tableIndexEntry = card.tableIndex.get(selectedTable);
if (!tableIndexEntry) {
console.error('[OPAT_PLOTTING] Table index entry not found for table:', selectedTable);
return;
}
console.log('[OPAT_PLOTTING] Table info:', {
rowName: tableIndexEntry.rowName,
columnName: tableIndexEntry.columnName,
numRows: tableData.N_R,
numColumns: tableData.N_C,
rowValuesLength: tableData.rowValues?.length,
columnValuesLength: tableData.columnValues?.length
});
// Populate axis selectors with row and column values
const variables = [];
// Add row values as the primary option (typically the independent variable)
if (tableData.rowValues && tableData.rowValues.length > 0) {
const rowName = tableIndexEntry.rowName.trim() || 'Row Values';
variables.push({
name: rowName,
value: '__row_values__',
isDefault: true,
type: 'row'
});
}
// Add column values as the secondary option
if (tableData.columnValues && tableData.columnValues.length > 0) {
const columnName = tableIndexEntry.columnName.trim() || 'Column Values';
variables.push({
name: columnName,
value: '__column_values__',
isDefault: true,
type: 'column'
});
}
// Add individual row options with their actual values
if (tableData.rowValues && tableData.rowValues.length > 0) {
const rowName = tableIndexEntry.rowName.trim() || 'Row';
for (let i = 0; i < Math.min(tableData.rowValues.length, 20); i++) { // Limit to 20 for UI performance
const value = tableData.rowValues[i];
const displayValue = typeof value === 'number' ? value.toPrecision(4) : value;
variables.push({
name: `Data Row ${i + 1} (${rowName} = ${displayValue})`,
value: `row_${i}`,
isDefault: false,
type: 'row_data',
index: i
});
}
}
// Add individual column options with their actual values
if (tableData.columnValues && tableData.columnValues.length > 0) {
const columnName = tableIndexEntry.columnName.trim() || 'Column';
for (let i = 0; i < Math.min(tableData.columnValues.length, 20); i++) { // Limit to 20 for UI performance
const value = tableData.columnValues[i];
const displayValue = typeof value === 'number' ? value.toPrecision(4) : value;
variables.push({
name: `Data Column ${i + 1} (${columnName} = ${displayValue})`,
value: `col_${i}`,
isDefault: false,
type: 'column_data',
index: i
});
}
}
// Add data columns as additional options (for accessing the actual table data)
for (let i = 0; i < Math.min(tableData.N_C, 10); i++) { // Limit for UI performance
variables.push({
name: `Table Data Column ${i + 1}`,
value: `data_col_${i}`,
isDefault: false,
type: 'data'
});
}
// Populate the select elements
variables.forEach((variable, index) => {
const xOption = document.createElement('option');
xOption.value = variable.value;
xOption.textContent = variable.name;
plotXAxis.appendChild(xOption);
const yOption = document.createElement('option');
yOption.value = variable.value;
yOption.textContent = variable.name;
plotYAxis.appendChild(yOption);
});
// Set default selections: row values for X-axis, column values for Y-axis
const rowVariable = variables.find(v => v.type === 'row');
const columnVariable = variables.find(v => v.type === 'column');
if (rowVariable) {
plotXAxis.value = rowVariable.value;
console.log('[OPAT_PLOTTING] Set default X-axis to:', rowVariable.name);
}
if (columnVariable) {
plotYAxis.value = columnVariable.value;
console.log('[OPAT_PLOTTING] Set default Y-axis to:', columnVariable.name);
}
console.log(`[OPAT_PLOTTING] Populated ${variables.length} variables for table ${selectedTable}`);
}
// Handle create plot button click
async function handleCreatePlot() {
console.log('[OPAT_PLOTTING] handleCreatePlot called');
// Ensure we have references to all elements
if (!plotIndexSelector) plotIndexSelector = document.getElementById('plot-index-selector');
if (!plotTableSelector) plotTableSelector = document.getElementById('plot-table-selector');
if (!plotXAxis) plotXAxis = document.getElementById('plot-x-axis');
if (!plotYAxis) plotYAxis = document.getElementById('plot-y-axis');
if (!plotType) plotType = document.getElementById('plot-type');
if (!plotTitle) plotTitle = document.getElementById('plot-title');
if (!plotIndexSelector || !plotTableSelector || !plotXAxis || !plotYAxis || !plotType || !plotTitle) {
console.error('[OPAT_PLOTTING] Missing required elements for plot creation');
return;
}
const selectedIndex = plotIndexSelector.value;
const selectedTable = plotTableSelector.value;
const xVariable = plotXAxis.value;
const yVariable = plotYAxis.value;
const plotTypeValue = plotType.value;
const titleText = plotTitle.value || 'OPAT Data Plot';
console.log('[OPAT_PLOTTING] Plot parameters:', {
selectedIndex, selectedTable, xVariable, yVariable, plotTypeValue, titleText
});
if (!selectedIndex || !selectedTable || !xVariable || !yVariable) {
console.warn('[OPAT_PLOTTING] Missing required fields');
alert('Please select all required fields (Index Vector, Table, X-Axis, Y-Axis)');
return;
}
try {
domManager.showSpinner();
// Get button references
const createBtn = document.getElementById('create-plot-btn');
const saveBtn = document.getElementById('save-plot-btn');
const clearBtn = document.getElementById('clear-plot-btn');
if (createBtn) createBtn.disabled = true;
const plotData = await generatePlotData(selectedIndex, selectedTable, xVariable, yVariable, plotTypeValue);
const layout = generatePlotLayout(titleText, xVariable, yVariable);
await createPlotlyPlot(plotData, layout);
// Enable save and clear buttons
if (saveBtn) saveBtn.disabled = false;
if (clearBtn) clearBtn.disabled = false;
console.log('[OPAT_PLOTTING] Plot created successfully');
} catch (error) {
console.error('[OPAT_PLOTTING] Error creating plot:', error);
alert('Error creating plot: ' + error.message);
} finally {
domManager.hideSpinner();
const createBtn = document.getElementById('create-plot-btn');
if (createBtn) createBtn.disabled = false;
}
}
// Generate plot data from OPAT table
async function generatePlotData(indexName, tableName, xVariable, yVariable, plotTypeValue) {
const opatFile = stateManager.getOPATFile();
const card = opatFile.cards.get(indexName);
const tableData = card.tableData.get(tableName);
const xData = extractVariableData(tableData, xVariable);
const yData = extractVariableData(tableData, yVariable);
let trace;
switch (plotTypeValue) {
case 'scatter':
trace = {
x: xData,
y: yData,
mode: 'markers',
type: 'scatter',
marker: {
color: '#3b82f6',
size: 6,
opacity: 0.7
},
name: `${tableName}`
};
break;
case 'line':
trace = {
x: xData,
y: yData,
mode: 'lines+markers',
type: 'scatter',
line: {
color: '#3b82f6',
width: 2
},
marker: {
color: '#3b82f6',
size: 4
},
name: `${tableName}`
};
break;
case 'heatmap':
// For heatmap, we need 2D data
const zData = reshapeDataForHeatmap(tableData);
trace = {
z: zData,
type: 'heatmap',
colorscale: 'Viridis',
name: `${tableName}`
};
break;
case 'contour':
// For contour, we need 2D data
const contourData = reshapeDataForHeatmap(tableData);
trace = {
z: contourData,
type: 'contour',
colorscale: 'Viridis',
name: `${tableName}`
};
break;
default:
throw new Error(`Unsupported plot type: ${plotTypeValue}`);
}
return [trace];
}
// Extract variable data from table
function extractVariableData(tableData, variable) {
console.log('[OPAT_PLOTTING] Extracting variable data for:', variable);
// Extract row values (the actual row axis values from the OPAT table)
if (variable === '__row_values__') {
if (tableData.rowValues && tableData.rowValues.length > 0) {
console.log('[OPAT_PLOTTING] Using row values, length:', tableData.rowValues.length);
return Array.from(tableData.rowValues);
} else {
console.warn('[OPAT_PLOTTING] No row values available, using indices');
return Array.from({ length: tableData.N_R }, (_, i) => i);
}
}
// Extract column values (the actual column axis values from the OPAT table)
if (variable === '__column_values__') {
if (tableData.columnValues && tableData.columnValues.length > 0) {
console.log('[OPAT_PLOTTING] Using column values, length:', tableData.columnValues.length);
return Array.from(tableData.columnValues);
} else {
console.warn('[OPAT_PLOTTING] No column values available, using indices');
return Array.from({ length: tableData.N_C }, (_, i) => i);
}
}
// Legacy support for row/column indices
if (variable === '__row_index__') {
return Array.from({ length: tableData.N_R }, (_, i) => i);
}
if (variable === '__col_index__') {
return Array.from({ length: tableData.N_C }, (_, i) => i);
}
// Extract data from a specific row (all columns for that row)
if (variable.startsWith('row_')) {
const rowIndex = parseInt(variable.split('_')[1]);
console.log('[OPAT_PLOTTING] Extracting data from row:', rowIndex);
const data = [];
for (let col = 0; col < tableData.N_C; col++) {
try {
const value = tableData.getValue(rowIndex, col, 0); // Use getValue method like OPAT explorer
data.push(typeof value === 'number' ? value : parseFloat(value) || 0);
} catch (error) {
console.warn('[OPAT_PLOTTING] Error extracting data at row', rowIndex, 'col', col, ':', error);
data.push(0);
}
}
return data;
}
// Extract data from a specific column (all rows for that column)
if (variable.startsWith('col_')) {
const colIndex = parseInt(variable.split('_')[1]);
console.log('[OPAT_PLOTTING] Extracting data from column:', colIndex);
const data = [];
for (let row = 0; row < tableData.N_R; row++) {
try {
const value = tableData.getValue(row, colIndex, 0); // Use getValue method like OPAT explorer
data.push(typeof value === 'number' ? value : parseFloat(value) || 0);
} catch (error) {
console.warn('[OPAT_PLOTTING] Error extracting data at row', row, 'col', colIndex, ':', error);
data.push(0);
}
}
return data;
}
// Extract data from specific data columns (legacy table data access)
if (variable.startsWith('data_col_')) {
const colIndex = parseInt(variable.split('_')[2]);
console.log('[OPAT_PLOTTING] Extracting data column:', colIndex);
const data = [];
for (let row = 0; row < tableData.N_R; row++) {
try {
const value = tableData.getValue(row, colIndex, 0); // Use getValue method like OPAT explorer
data.push(typeof value === 'number' ? value : parseFloat(value) || 0);
} catch (error) {
console.warn('[OPAT_PLOTTING] Error extracting data at row', row, 'col', colIndex, ':', error);
data.push(0);
}
}
return data;
}
throw new Error(`Unknown variable: ${variable}`);
}
// Reshape data for heatmap/contour plots
function reshapeDataForHeatmap(tableData) {
const data = [];
for (let row = 0; row < Math.min(tableData.N_R, 50); row++) { // Limit for performance
const rowData = [];
for (let col = 0; col < Math.min(tableData.N_C, 50); col++) {
try {
const value = tableData.getValue(row, col, 0); // Use getValue method like OPAT explorer
rowData.push(typeof value === 'number' ? value : parseFloat(value) || 0);
} catch (error) {
rowData.push(0);
}
}
data.push(rowData);
}
return data;
}
// Generate plot layout
function generatePlotLayout(title, xVariable, yVariable) {
const isDarkMode = document.body.classList.contains('dark-mode');
return {
title: {
text: title,
font: {
color: isDarkMode ? '#f3f4f6' : '#1f2937',
size: 16
}
},
xaxis: {
title: {
text: formatVariableName(xVariable),
font: {
color: isDarkMode ? '#d1d5db' : '#374151'
}
},
tickfont: {
color: isDarkMode ? '#9ca3af' : '#6b7280'
},
gridcolor: isDarkMode ? '#4b5563' : '#e5e7eb'
},
yaxis: {
title: {
text: formatVariableName(yVariable),
font: {
color: isDarkMode ? '#d1d5db' : '#374151'
}
},
tickfont: {
color: isDarkMode ? '#9ca3af' : '#6b7280'
},
gridcolor: isDarkMode ? '#4b5563' : '#e5e7eb'
},
plot_bgcolor: isDarkMode ? '#374151' : 'white',
paper_bgcolor: isDarkMode ? '#374151' : 'white',
font: {
color: isDarkMode ? '#f3f4f6' : '#1f2937'
},
margin: { t: 50, r: 50, b: 50, l: 50 }
};
}
// Format variable name for display
function formatVariableName(variable) {
if (variable === '__row_index__') return 'Row Index';
if (variable === '__col_index__') return 'Column Index';
if (variable.startsWith('col_')) {
const colIndex = parseInt(variable.split('_')[1]);
return `Column ${colIndex + 1}`;
}
return variable;
}
// Create Plotly plot
async function createPlotlyPlot(data, layout) {
console.log('[OPAT_PLOTTING] createPlotlyPlot called');
currentPlotData = data;
currentPlotLayout = layout;
// Get DOM elements
const placeholder = document.getElementById('plot-placeholder');
const display = document.getElementById('plot-display');
if (!placeholder || !display) {
console.error('[OPAT_PLOTTING] Missing plot container elements');
throw new Error('Plot container elements not found');
}
// Hide placeholder and show plot display
placeholder.classList.add('hidden');
display.classList.remove('hidden');
// Create the plot
await Plotly.newPlot(display, data, layout, {
responsive: true,
displayModeBar: true,
modeBarButtonsToAdd: ['downloadImage'],
toImageButtonOptions: {
format: 'png',
filename: 'opat_plot',
height: 500,
width: 700,
scale: 2
}
});
}
// Handle save plot button click
async function handleSavePlot() {
if (!currentPlotData || !currentPlotLayout) {
alert('No plot to save');
return;
}
try {
// Use Plotly's built-in download functionality
const filename = plotTitle.value ?
plotTitle.value.replace(/[^a-z0-9]/gi, '_').toLowerCase() :
'opat_plot';
await Plotly.downloadImage(plotDisplay, {
format: 'png',
width: 1200,
height: 800,
filename: filename
});
console.log('[OPAT_PLOTTING] Plot saved as PNG');
} catch (error) {
console.error('[OPAT_PLOTTING] Error saving plot:', error);
alert('Error saving plot: ' + error.message);
}
}
// Handle clear plot button click
function handleClearPlot() {
if (plotDisplay && currentPlotData) {
Plotly.purge(plotDisplay);
}
currentPlotData = null;
currentPlotLayout = null;
// Show placeholder and hide plot display (with null checks)
if (plotDisplay) {
plotDisplay.classList.add('hidden');
}
if (plotPlaceholder) {
plotPlaceholder.classList.remove('hidden');
}
// Disable save and clear buttons (with null checks)
if (savePlotBtn) {
savePlotBtn.disabled = true;
}
if (clearPlotBtn) {
clearPlotBtn.disabled = true;
}
console.log('[OPAT_PLOTTING] Plot cleared');
}
// Reset plotting state when OPAT file changes
function resetPlottingState() {
handleClearPlot();
// Reset selectors with null checks
if (plotIndexSelector) {
plotIndexSelector.innerHTML = '';
}
if (plotTableSelector) {
plotTableSelector.innerHTML = '';
}
if (plotXAxis) {
plotXAxis.innerHTML = '';
}
if (plotYAxis) {
plotYAxis.innerHTML = '';
}
if (plotTitle) {
plotTitle.value = '';
}
console.log('[OPAT_PLOTTING] Plotting state reset');
}
// Initialize dependencies (called when module is loaded)
function initializePlottingDependencies(deps) {
stateManager = deps.stateManager;
domManager = deps.domManager;
}
module.exports = {
initializePlottingDependencies,
initializePlottingElements,
populatePlotIndexSelector,
handlePlotIndexChange,
handlePlotTableChange,
handleCreatePlot,
handleSavePlot,
handleClearPlot,
resetPlottingState
};