refactoring 4
This commit is contained in:
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"open_air": true,
|
"open_air": true,
|
||||||
"axis": "abs",
|
"axis": "phase",
|
||||||
"data_limitation": "ph_only_1",
|
"data_limitation": "ph_only_1",
|
||||||
"cut": 1.413,
|
"cut": 0.988,
|
||||||
"max": 2.4,
|
"max": 2.4,
|
||||||
"gain": 1.2,
|
"gain": 1.2,
|
||||||
"start_freq": 100.0,
|
"start_freq": 100.0,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"y_min": -80,
|
"y_min": -80,
|
||||||
"y_max": 30,
|
"y_max": 40,
|
||||||
"show_phase": true
|
"show_phase": false
|
||||||
}
|
}
|
||||||
@ -199,12 +199,6 @@ class VNADashboard {
|
|||||||
* Handle keyboard shortcuts
|
* Handle keyboard shortcuts
|
||||||
*/
|
*/
|
||||||
handleKeyboardShortcuts(event) {
|
handleKeyboardShortcuts(event) {
|
||||||
// Ctrl/Cmd + E: Export data
|
|
||||||
if ((event.ctrlKey || event.metaKey) && event.key === 'e') {
|
|
||||||
event.preventDefault();
|
|
||||||
this.ui.triggerExportData();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ctrl/Cmd + Shift + R: Reconnect WebSocket
|
// Ctrl/Cmd + Shift + R: Reconnect WebSocket
|
||||||
if ((event.ctrlKey || event.metaKey) && event.key === 'r' && event.shiftKey) {
|
if ((event.ctrlKey || event.metaKey) && event.key === 'r' && event.shiftKey) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|||||||
@ -5,6 +5,15 @@
|
|||||||
|
|
||||||
import { formatProcessorName, safeClone, downloadJSON } from './utils.js';
|
import { formatProcessorName, safeClone, downloadJSON } from './utils.js';
|
||||||
import { ChartSettingsManager } from './charts/chart-settings.js';
|
import { ChartSettingsManager } from './charts/chart-settings.js';
|
||||||
|
import {
|
||||||
|
defaultPlotlyLayout,
|
||||||
|
defaultPlotlyConfig,
|
||||||
|
createPlotlyPlot,
|
||||||
|
updatePlotlyPlot,
|
||||||
|
togglePlotlyFullscreen,
|
||||||
|
downloadPlotlyImage,
|
||||||
|
cleanupPlotly
|
||||||
|
} from './plotly-utils.js';
|
||||||
|
|
||||||
export class ChartManager {
|
export class ChartManager {
|
||||||
constructor(config, notifications) {
|
constructor(config, notifications) {
|
||||||
@ -30,38 +39,6 @@ export class ChartManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.settingsManager = new ChartSettingsManager();
|
this.settingsManager = new ChartSettingsManager();
|
||||||
|
|
||||||
this.plotlyConfig = {
|
|
||||||
displayModeBar: true,
|
|
||||||
modeBarButtonsToRemove: ['select2d','lasso2d','hoverClosestCartesian','hoverCompareCartesian','toggleSpikelines'],
|
|
||||||
displaylogo: false,
|
|
||||||
responsive: false,
|
|
||||||
doubleClick: 'reset',
|
|
||||||
toImageButtonOptions: { format: 'png', filename: 'vna_chart', height: 600, width: 800, scale: 1 }
|
|
||||||
};
|
|
||||||
|
|
||||||
this.plotlyLayout = {
|
|
||||||
plot_bgcolor: 'transparent',
|
|
||||||
paper_bgcolor: 'transparent',
|
|
||||||
font: { family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif', size: 12, color: '#f1f5f9' },
|
|
||||||
colorway: ['#3b82f6','#22c55e','#eab308','#ef4444','#8b5cf6','#06b6d4','#f59e0b','#10b981'],
|
|
||||||
margin: { l: 60, r: 50, t: 50, b: 60 },
|
|
||||||
showlegend: true,
|
|
||||||
legend: {
|
|
||||||
orientation: 'v',
|
|
||||||
x: 1.02,
|
|
||||||
y: 1,
|
|
||||||
xanchor: 'left',
|
|
||||||
yanchor: 'top',
|
|
||||||
bgcolor: 'rgba(30, 41, 59, 0.9)',
|
|
||||||
bordercolor: '#475569',
|
|
||||||
borderwidth: 1,
|
|
||||||
font: { size: 10, color: '#f1f5f9' }
|
|
||||||
},
|
|
||||||
xaxis: { gridcolor: '#334155', zerolinecolor: '#475569', color: '#cbd5e1', fixedrange: false },
|
|
||||||
yaxis: { gridcolor: '#334155', zerolinecolor: '#475569', color: '#cbd5e1', fixedrange: false },
|
|
||||||
autosize: true, width: null, height: null
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
@ -110,21 +87,13 @@ export class ChartManager {
|
|||||||
this.chartsGrid.appendChild(card);
|
this.chartsGrid.appendChild(card);
|
||||||
|
|
||||||
const plotContainer = card.querySelector('.chart-card__plot');
|
const plotContainer = card.querySelector('.chart-card__plot');
|
||||||
const layout = {
|
const layoutOverrides = {
|
||||||
...this.plotlyLayout,
|
|
||||||
title: { text: formatProcessorName(processorId), font: { size: 16, color: '#f1f5f9' } },
|
title: { text: formatProcessorName(processorId), font: { size: 16, color: '#f1f5f9' } },
|
||||||
width: plotContainer.clientWidth || 500,
|
width: plotContainer.clientWidth || 500,
|
||||||
height: plotContainer.clientHeight || 420
|
height: plotContainer.clientHeight || 420
|
||||||
};
|
};
|
||||||
Plotly.newPlot(plotContainer, [], layout, this.plotlyConfig);
|
|
||||||
|
|
||||||
if (window.ResizeObserver) {
|
createPlotlyPlot(plotContainer, [], layoutOverrides);
|
||||||
const ro = new ResizeObserver(() => {
|
|
||||||
if (plotContainer && plotContainer.clientWidth > 0) Plotly.Plots.resize(plotContainer);
|
|
||||||
});
|
|
||||||
ro.observe(plotContainer);
|
|
||||||
plotContainer._resizeObserver = ro;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.charts.set(processorId, { element: card, plotContainer, isVisible: true, settingsInitialized: false });
|
this.charts.set(processorId, { element: card, plotContainer, isVisible: true, settingsInitialized: false });
|
||||||
this.performanceStats.chartsCreated++;
|
this.performanceStats.chartsCreated++;
|
||||||
@ -146,15 +115,12 @@ export class ChartManager {
|
|||||||
try {
|
try {
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
this.queueUpdate(processorId, async () => {
|
this.queueUpdate(processorId, async () => {
|
||||||
const updateLayout = {
|
const layoutOverrides = {
|
||||||
...this.plotlyLayout,
|
|
||||||
...(plotlyConfig.layout || {}),
|
...(plotlyConfig.layout || {}),
|
||||||
title: { text: formatProcessorName(processorId), font: { size: 16, color: '#f1f5f9' } }
|
title: { text: formatProcessorName(processorId), font: { size: 16, color: '#f1f5f9' } }
|
||||||
};
|
};
|
||||||
delete updateLayout.width;
|
|
||||||
delete updateLayout.height;
|
|
||||||
|
|
||||||
await Plotly.react(chart.plotContainer, plotlyConfig.data || [], updateLayout, this.plotlyConfig);
|
await updatePlotlyPlot(chart.plotContainer, plotlyConfig.data || [], layoutOverrides);
|
||||||
|
|
||||||
this.updateChartMetadata(processorId);
|
this.updateChartMetadata(processorId);
|
||||||
|
|
||||||
@ -297,8 +263,7 @@ export class ChartManager {
|
|||||||
removeChart(id) {
|
removeChart(id) {
|
||||||
const c = this.charts.get(id);
|
const c = this.charts.get(id);
|
||||||
if (c) {
|
if (c) {
|
||||||
if (c.plotContainer?._resizeObserver) { c.plotContainer._resizeObserver.disconnect(); c.plotContainer._resizeObserver = null; }
|
cleanupPlotly(c.plotContainer);
|
||||||
if (c.plotContainer) Plotly.purge(c.plotContainer);
|
|
||||||
c.element.remove();
|
c.element.remove();
|
||||||
this.charts.delete(id);
|
this.charts.delete(id);
|
||||||
this.chartData.delete(id);
|
this.chartData.delete(id);
|
||||||
@ -323,12 +288,7 @@ export class ChartManager {
|
|||||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||||
const baseFilename = `${id}_${timestamp}`;
|
const baseFilename = `${id}_${timestamp}`;
|
||||||
|
|
||||||
await Plotly.downloadImage(c.plotContainer, {
|
await downloadPlotlyImage(c.plotContainer, `${baseFilename}_plot`);
|
||||||
format: 'png',
|
|
||||||
width: 1200,
|
|
||||||
height: 800,
|
|
||||||
filename: `${baseFilename}_plot`
|
|
||||||
});
|
|
||||||
|
|
||||||
const processorData = this.prepareDownloadData(id);
|
const processorData = this.prepareDownloadData(id);
|
||||||
if (processorData) {
|
if (processorData) {
|
||||||
@ -371,24 +331,10 @@ export class ChartManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleFullscreen(id) {
|
async toggleFullscreen(id) {
|
||||||
const c = this.charts.get(id);
|
const c = this.charts.get(id);
|
||||||
if (!c?.element) return;
|
if (!c?.element) return;
|
||||||
if (!document.fullscreenElement) {
|
await togglePlotlyFullscreen(c.element, c.plotContainer);
|
||||||
c.element.requestFullscreen()?.then(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (c.plotContainer) {
|
|
||||||
const r = c.plotContainer.getBoundingClientRect();
|
|
||||||
Plotly.relayout(c.plotContainer, { width: r.width, height: r.height });
|
|
||||||
Plotly.Plots.resize(c.plotContainer);
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
}).catch(console.error);
|
|
||||||
} else {
|
|
||||||
document.exitFullscreen()?.then(() => {
|
|
||||||
setTimeout(() => c.plotContainer && Plotly.Plots.resize(c.plotContainer), 100);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hideEmptyState() {
|
hideEmptyState() {
|
||||||
|
|||||||
176
vna_system/web_ui/static/js/modules/plotly-utils.js
Normal file
176
vna_system/web_ui/static/js/modules/plotly-utils.js
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
/**
|
||||||
|
* Plotly Utilities
|
||||||
|
* Shared functions for Plotly chart rendering and management
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default Plotly layout for dark theme
|
||||||
|
*/
|
||||||
|
export const defaultPlotlyLayout = {
|
||||||
|
plot_bgcolor: 'transparent',
|
||||||
|
paper_bgcolor: 'transparent',
|
||||||
|
font: { family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif', size: 12, color: '#f1f5f9' },
|
||||||
|
colorway: ['#3b82f6','#22c55e','#eab308','#ef4444','#8b5cf6','#06b6d4','#f59e0b','#10b981'],
|
||||||
|
margin: { l: 60, r: 50, t: 50, b: 60 },
|
||||||
|
showlegend: true,
|
||||||
|
legend: {
|
||||||
|
orientation: 'v',
|
||||||
|
x: 1.02,
|
||||||
|
y: 1,
|
||||||
|
xanchor: 'left',
|
||||||
|
yanchor: 'top',
|
||||||
|
bgcolor: 'rgba(30, 41, 59, 0.9)',
|
||||||
|
bordercolor: '#475569',
|
||||||
|
borderwidth: 1,
|
||||||
|
font: { size: 10, color: '#f1f5f9' }
|
||||||
|
},
|
||||||
|
xaxis: { gridcolor: '#334155', zerolinecolor: '#475569', color: '#cbd5e1', fixedrange: false },
|
||||||
|
yaxis: { gridcolor: '#334155', zerolinecolor: '#475569', color: '#cbd5e1', fixedrange: false },
|
||||||
|
autosize: true,
|
||||||
|
width: null,
|
||||||
|
height: null
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default Plotly config
|
||||||
|
*/
|
||||||
|
export const defaultPlotlyConfig = {
|
||||||
|
displayModeBar: true,
|
||||||
|
modeBarButtonsToRemove: ['select2d','lasso2d','hoverClosestCartesian','hoverCompareCartesian','toggleSpikelines'],
|
||||||
|
displaylogo: false,
|
||||||
|
responsive: false,
|
||||||
|
doubleClick: 'reset',
|
||||||
|
toImageButtonOptions: { format: 'png', filename: 'vna_chart', height: 600, width: 800, scale: 1 }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup resize observer for Plotly plot
|
||||||
|
* @param {HTMLElement} container - Plot container element
|
||||||
|
*/
|
||||||
|
export function setupPlotlyResize(container) {
|
||||||
|
if (!window.ResizeObserver || !container) return;
|
||||||
|
|
||||||
|
const ro = new ResizeObserver(() => {
|
||||||
|
if (container && container.clientWidth > 0 && typeof Plotly !== 'undefined') {
|
||||||
|
Plotly.Plots.resize(container);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ro.observe(container);
|
||||||
|
container._resizeObserver = ro;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup Plotly plot and observers
|
||||||
|
* @param {HTMLElement} container - Plot container element
|
||||||
|
*/
|
||||||
|
export function cleanupPlotly(container) {
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
if (container._resizeObserver) {
|
||||||
|
container._resizeObserver.disconnect();
|
||||||
|
container._resizeObserver = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (container._fullData && typeof Plotly !== 'undefined') {
|
||||||
|
Plotly.purge(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Plotly plot with default styling
|
||||||
|
* @param {HTMLElement} container - Container element
|
||||||
|
* @param {Array} data - Plotly data traces
|
||||||
|
* @param {Object} layoutOverrides - Layout overrides
|
||||||
|
* @param {Object} configOverrides - Config overrides
|
||||||
|
*/
|
||||||
|
export function createPlotlyPlot(container, data = [], layoutOverrides = {}, configOverrides = {}) {
|
||||||
|
if (!container || typeof Plotly === 'undefined') return;
|
||||||
|
|
||||||
|
const layout = {
|
||||||
|
...defaultPlotlyLayout,
|
||||||
|
...layoutOverrides
|
||||||
|
};
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
...defaultPlotlyConfig,
|
||||||
|
...configOverrides
|
||||||
|
};
|
||||||
|
|
||||||
|
Plotly.newPlot(container, data, layout, config);
|
||||||
|
setupPlotlyResize(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update existing Plotly plot
|
||||||
|
* @param {HTMLElement} container - Container element
|
||||||
|
* @param {Array} data - Plotly data traces
|
||||||
|
* @param {Object} layoutOverrides - Layout overrides
|
||||||
|
*/
|
||||||
|
export async function updatePlotlyPlot(container, data = [], layoutOverrides = {}) {
|
||||||
|
if (!container || typeof Plotly === 'undefined') return;
|
||||||
|
|
||||||
|
const layout = {
|
||||||
|
...defaultPlotlyLayout,
|
||||||
|
...layoutOverrides
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove width/height to allow autosize
|
||||||
|
delete layout.width;
|
||||||
|
delete layout.height;
|
||||||
|
|
||||||
|
await Plotly.react(container, data, layout, defaultPlotlyConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle fullscreen for element with Plotly plot
|
||||||
|
* @param {HTMLElement} element - Element to fullscreen
|
||||||
|
* @param {HTMLElement} plotContainer - Plot container inside element
|
||||||
|
*/
|
||||||
|
export async function togglePlotlyFullscreen(element, plotContainer) {
|
||||||
|
if (!element || typeof Plotly === 'undefined') return;
|
||||||
|
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
try {
|
||||||
|
await element.requestFullscreen?.();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (plotContainer) {
|
||||||
|
const rect = plotContainer.getBoundingClientRect();
|
||||||
|
Plotly.relayout(plotContainer, { width: rect.width, height: rect.height });
|
||||||
|
Plotly.Plots.resize(plotContainer);
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fullscreen request failed:', error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await document.exitFullscreen?.();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (plotContainer) {
|
||||||
|
Plotly.Plots.resize(plotContainer);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Exit fullscreen failed:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download Plotly plot as image
|
||||||
|
* @param {HTMLElement} container - Plot container
|
||||||
|
* @param {string} filename - Output filename (without extension)
|
||||||
|
* @param {Object} options - Download options
|
||||||
|
*/
|
||||||
|
export async function downloadPlotlyImage(container, filename = 'plot', options = {}) {
|
||||||
|
if (!container || typeof Plotly === 'undefined') return;
|
||||||
|
|
||||||
|
const defaultOptions = {
|
||||||
|
format: 'png',
|
||||||
|
width: 1200,
|
||||||
|
height: 800,
|
||||||
|
filename
|
||||||
|
};
|
||||||
|
|
||||||
|
await Plotly.downloadImage(container, { ...defaultOptions, ...options });
|
||||||
|
}
|
||||||
@ -7,6 +7,12 @@ import { PresetManager } from './settings/preset-manager.js';
|
|||||||
import { CalibrationManager } from './settings/calibration-manager.js';
|
import { CalibrationManager } from './settings/calibration-manager.js';
|
||||||
import { ReferenceManager } from './settings/reference-manager.js';
|
import { ReferenceManager } from './settings/reference-manager.js';
|
||||||
import { Debouncer, ButtonState, downloadJSON } from './utils.js';
|
import { Debouncer, ButtonState, downloadJSON } from './utils.js';
|
||||||
|
import {
|
||||||
|
createPlotlyPlot,
|
||||||
|
togglePlotlyFullscreen,
|
||||||
|
downloadPlotlyImage,
|
||||||
|
cleanupPlotly
|
||||||
|
} from './plotly-utils.js';
|
||||||
|
|
||||||
export class SettingsManager {
|
export class SettingsManager {
|
||||||
constructor(notifications, websocket, acquisition) {
|
constructor(notifications, websocket, acquisition) {
|
||||||
@ -318,50 +324,12 @@ export class SettingsManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const layout = {
|
const layoutOverrides = {
|
||||||
...plotConfig.layout,
|
...plotConfig.layout,
|
||||||
title: { text: title, font: { size: 16, color: '#f1f5f9' } },
|
title: { text: title, font: { size: 16, color: '#f1f5f9' } }
|
||||||
plot_bgcolor: 'transparent',
|
|
||||||
paper_bgcolor: 'transparent',
|
|
||||||
font: { family: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif', size: 12, color: '#f1f5f9' },
|
|
||||||
autosize: true,
|
|
||||||
width: null,
|
|
||||||
height: null,
|
|
||||||
margin: { l: 60, r: 50, t: 50, b: 60 },
|
|
||||||
showlegend: true,
|
|
||||||
legend: {
|
|
||||||
orientation: 'v',
|
|
||||||
x: 1.02,
|
|
||||||
y: 1,
|
|
||||||
xanchor: 'left',
|
|
||||||
yanchor: 'top',
|
|
||||||
bgcolor: 'rgba(30, 41, 59, 0.9)',
|
|
||||||
bordercolor: '#475569',
|
|
||||||
borderwidth: 1,
|
|
||||||
font: { size: 10, color: '#f1f5f9' }
|
|
||||||
},
|
|
||||||
xaxis: {
|
|
||||||
...plotConfig.layout.xaxis,
|
|
||||||
gridcolor: '#334155',
|
|
||||||
zerolinecolor: '#475569',
|
|
||||||
color: '#cbd5e1',
|
|
||||||
fixedrange: false
|
|
||||||
},
|
|
||||||
yaxis: {
|
|
||||||
...plotConfig.layout.yaxis,
|
|
||||||
gridcolor: '#334155',
|
|
||||||
zerolinecolor: '#475569',
|
|
||||||
color: '#cbd5e1',
|
|
||||||
fixedrange: false
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = {
|
const configOverrides = {
|
||||||
displayModeBar: true,
|
|
||||||
modeBarButtonsToRemove: ['select2d', 'lasso2d', 'hoverClosestCartesian', 'hoverCompareCartesian', 'toggleSpikelines'],
|
|
||||||
displaylogo: false,
|
|
||||||
responsive: false,
|
|
||||||
doubleClick: 'reset',
|
|
||||||
toImageButtonOptions: {
|
toImageButtonOptions: {
|
||||||
format: 'png',
|
format: 'png',
|
||||||
filename: `calibration-plot-${Date.now()}`,
|
filename: `calibration-plot-${Date.now()}`,
|
||||||
@ -371,41 +339,12 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Plotly.newPlot(container, plotConfig.data, layout, config);
|
createPlotlyPlot(container, plotConfig.data, layoutOverrides, configOverrides);
|
||||||
|
|
||||||
if (window.ResizeObserver) {
|
|
||||||
const ro = new ResizeObserver(() => {
|
|
||||||
if (container && container.clientWidth > 0) {
|
|
||||||
Plotly.Plots.resize(container);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ro.observe(container);
|
|
||||||
container._resizeObserver = ro;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleFullscreen(card) {
|
async toggleFullscreen(card) {
|
||||||
if (!document.fullscreenElement) {
|
|
||||||
card.requestFullscreen?.().then(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
const plot = card.querySelector('.chart-card__plot');
|
const plot = card.querySelector('.chart-card__plot');
|
||||||
if (plot && typeof Plotly !== 'undefined') {
|
await togglePlotlyFullscreen(card, plot);
|
||||||
const rect = plot.getBoundingClientRect();
|
|
||||||
Plotly.relayout(plot, { width: rect.width, height: rect.height });
|
|
||||||
Plotly.Plots.resize(plot);
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
}).catch(console.error);
|
|
||||||
} else {
|
|
||||||
document.exitFullscreen?.().then(() => {
|
|
||||||
setTimeout(() => {
|
|
||||||
const plot = card.querySelector('.chart-card__plot');
|
|
||||||
if (plot && typeof Plotly !== 'undefined') {
|
|
||||||
Plotly.Plots.resize(plot);
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupModalCloseHandlers(modal) {
|
setupModalCloseHandlers(modal) {
|
||||||
@ -436,13 +375,8 @@ export class SettingsManager {
|
|||||||
modal.classList.remove('modal--active');
|
modal.classList.remove('modal--active');
|
||||||
document.body.style.overflow = '';
|
document.body.style.overflow = '';
|
||||||
|
|
||||||
if (typeof Plotly !== 'undefined') {
|
|
||||||
const containers = modal.querySelectorAll('[id^="calibration-plot-"]');
|
const containers = modal.querySelectorAll('[id^="calibration-plot-"]');
|
||||||
containers.forEach(c => {
|
containers.forEach(c => cleanupPlotly(c));
|
||||||
if (c._resizeObserver) { c._resizeObserver.disconnect(); c._resizeObserver = null; }
|
|
||||||
if (c._fullData) Plotly.purge(c);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentPlotsData = null;
|
this.currentPlotsData = null;
|
||||||
}
|
}
|
||||||
@ -453,10 +387,8 @@ export class SettingsManager {
|
|||||||
const calibrationName = this.currentPlotsData?.calibration_name || 'unknown';
|
const calibrationName = this.currentPlotsData?.calibration_name || 'unknown';
|
||||||
const base = `${calibrationName}_${standardName}_${ts}`;
|
const base = `${calibrationName}_${standardName}_${ts}`;
|
||||||
|
|
||||||
if (plotContainer && typeof Plotly !== 'undefined') {
|
if (plotContainer) {
|
||||||
await Plotly.downloadImage(plotContainer, {
|
await downloadPlotlyImage(plotContainer, `${base}_plot`);
|
||||||
format: 'png', width: 1200, height: 800, filename: `${base}_plot`
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = this.prepareCalibrationDownloadData(standardName);
|
const data = this.prepareCalibrationDownloadData(standardName);
|
||||||
|
|||||||
Reference in New Issue
Block a user