From f1035853783ae4a2b0d150972c70e9f20a8be164 Mon Sep 17 00:00:00 2001 From: Brianna Major Date: Tue, 16 Jun 2026 14:41:35 -0400 Subject: [PATCH 1/2] Refactor Plotly code Create plotUtils.js with shared Plotly utilities --- .../core/static/net_maestro/customStyles.css | 2 +- .../core/static/net_maestro/plots/heatmap.js | 72 +--- .../net_maestro/plots/networkTimePlot.js | 139 ++----- .../net_maestro/plots/parallelCoords.js | 147 ++------ .../static/net_maestro/plots/plotUtils.js | 349 ++++++++++++++++++ .../static/net_maestro/plots/scatterPlot.js | 240 +++--------- .../core/static/net_maestro/plots/timePlot.js | 254 +++---------- package-lock.json | 54 ++- package.json | 5 + 9 files changed, 564 insertions(+), 698 deletions(-) create mode 100644 net_maestro/core/static/net_maestro/plots/plotUtils.js diff --git a/net_maestro/core/static/net_maestro/customStyles.css b/net_maestro/core/static/net_maestro/customStyles.css index c909bf2..43243ed 100644 --- a/net_maestro/core/static/net_maestro/customStyles.css +++ b/net_maestro/core/static/net_maestro/customStyles.css @@ -5,7 +5,7 @@ /* Prevent flash of unstyled content with Alpine.js */ [x-cloak] { - /* biome-ignore lint/complexity/noImportantStyles: ensure this is honored */ + /* biome-ignore lint/complexity/noImportantStyles: must override any inline/component styles until Alpine initializes */ display: none !important; } diff --git a/net_maestro/core/static/net_maestro/plots/heatmap.js b/net_maestro/core/static/net_maestro/plots/heatmap.js index a88d056..d00522b 100644 --- a/net_maestro/core/static/net_maestro/plots/heatmap.js +++ b/net_maestro/core/static/net_maestro/plots/heatmap.js @@ -4,6 +4,7 @@ */ import _ from 'lodash'; import Plotly from 'plotly'; +import { DARK_LAYOUT, initPlot, purgePlot, setupAxisState, setupLoadWatcher } from './plotUtils.js'; export const heatmapPlot = () => ({ heatmapPlotEl: null, @@ -25,36 +26,14 @@ export const heatmapPlot = () => ({ * Initialize the component and set up watchers. */ init() { - // Restore UI state - const savedState = this.$store.uiStateStore.getUIState('heatmapPlot'); - this.selectedMetric = savedState.selectedMetric ?? 'num_messages'; - - this.$watch('selectedMetric', (newValue) => { - if (newValue) { - this.$store.uiStateStore.saveUIState('heatmapPlot', { selectedMetric: newValue }); - } - }); - - // Load data if a run is already selected - if (this.$store.dataStore.selectedRunId) { - this.load(); - } - - // Watch for new data loads - this.$watch('$store.dataStore.loadTick', () => { - this.load(); - }); + setupAxisState(this, 'heatmapPlot', [{ prop: 'selectedMetric', default: 'num_messages' }]); + setupLoadWatcher(this, () => this.load()); }, /** * Initialize the Plotly heatmap plot. */ initPlot() { - if (this.isPlotInitialized) { - return; - } - this.heatmapPlotEl = document.getElementById('heatmapPlot'); - const data = [ { type: 'heatmap', @@ -65,30 +44,21 @@ export const heatmapPlot = () => ({ }, ]; const layout = { - // biome-ignore-start lint/style/useNamingConvention: library interface names - paper_bgcolor: '#1d232a', - plot_bgcolor: '#1d232a', - font: { - color: 'white', - }, - margin: { - t: 50, - b: 50, - l: 50, - r: 50, - pad: 4, - }, + ...DARK_LAYOUT, xaxis: { title: 'Receiving LP ID', }, yaxis: { title: 'Sending LP ID', }, - // biome-ignore-end lint/style/useNamingConvention: library interface names }; - const config = { responsive: true }; - Plotly.newPlot(this.heatmapPlotEl, data, layout, config); - this.isPlotInitialized = true; + initPlot({ + component: this, + elementId: 'heatmapPlot', + elementProp: 'heatmapPlotEl', + data, + layout, + }); }, async load() { @@ -109,10 +79,7 @@ export const heatmapPlot = () => ({ }, purge() { - if (this.heatmapPlotEl) { - Plotly.purge(this.heatmapPlotEl); - this.isPlotInitialized = false; - } + purgePlot(this, 'heatmapPlotEl'); }, createHeatmapMatrix() { @@ -169,19 +136,7 @@ export const heatmapPlot = () => ({ }, ], { - // biome-ignore-start lint/style/useNamingConvention: library interface names - paper_bgcolor: '#1d232a', - plot_bgcolor: '#1d232a', - font: { - color: 'white', - }, - margin: { - t: 50, - b: 50, - l: 50, - r: 50, - pad: 4, - }, + ...DARK_LAYOUT, xaxis: { title: 'Receiving LP ID', }, @@ -191,7 +146,6 @@ export const heatmapPlot = () => ({ coloraxis: { colorbar: { title: title }, }, - // biome-ignore-end lint/style/useNamingConvention: library interface names }, ); }, diff --git a/net_maestro/core/static/net_maestro/plots/networkTimePlot.js b/net_maestro/core/static/net_maestro/plots/networkTimePlot.js index 3c751f0..29dfe4d 100644 --- a/net_maestro/core/static/net_maestro/plots/networkTimePlot.js +++ b/net_maestro/core/static/net_maestro/plots/networkTimePlot.js @@ -1,4 +1,14 @@ import Plotly from 'plotly'; +import { + axisConfig, + createValueList, + DARK_LAYOUT, + getLabel, + initPlot, + purgePlot, + setupAxisState, + setupLoadWatcher, +} from './plotUtils.js'; export const networkTimePlot = () => ({ records: [], @@ -17,14 +27,7 @@ export const networkTimePlot = () => ({ ]; }, get yAxisValues() { - const excludedColumns = ['lp_id', 'component_id', 'real_time', 'virtual_time']; - const filteredColumns = this.columns.filter( - (column) => column && !excludedColumns.includes(column), - ); - return filteredColumns.map((value) => ({ - key: value, - label: value.replaceAll('_', ' '), - })); + return createValueList(this.columns, ['lp_id', 'component_id', 'real_time', 'virtual_time']); }, /** @@ -32,83 +35,30 @@ export const networkTimePlot = () => ({ * Called automatically by Alpine.js when component mounts. */ init() { - // Restore UI state - const savedState = this.$store.uiStateStore.getUIState('networkTimePlot'); - this.selectedXAxis = savedState.selectedXAxis ?? 'virtual_time'; - this.selectedYAxis = savedState.selectedYAxis ?? 'send_count'; - - this.$watch('selectedXAxis', (newValue) => { - if (newValue) { - this.$store.uiStateStore.saveUIState('networkTimePlot', { selectedXAxis: newValue }); - } - }); - - this.$watch('selectedYAxis', (newValue) => { - if (newValue) { - this.$store.uiStateStore.saveUIState('networkTimePlot', { selectedYAxis: newValue }); - } - }); - - // Load data if a run is already selected - if (this.$store.dataStore.selectedRunId) { - this.load(); - } - - // Watch for new data loads - this.$watch('$store.dataStore.loadTick', () => { - this.load(); - }); + setupAxisState(this, 'networkTimePlot', [ + { prop: 'selectedXAxis', default: 'virtual_time' }, + { prop: 'selectedYAxis', default: 'send_count' }, + ]); + setupLoadWatcher(this, () => this.load()); }, /** * Initialize the Plotly networkTime plot. */ initPlot() { - if (this.isPlotInitialized) { - return; - } - this.networkTimePlotEl = document.getElementById('networkTimePlot'); - if (!this.networkTimePlotEl) { - return; - } - - const data = [ - { - x: [], - y: [], - showlegend: true, - }, - ]; const layout = { - // biome-ignore-start lint/style/useNamingConvention: library interface names - xaxis: { - title: { - text: 'Virtual Time', - }, - rangemode: 'tozero', - color: 'white', - }, - yaxis: { - title: { - text: 'Send Count', - }, - rangemode: 'tozero', - color: 'white', - }, - paper_bgcolor: '#1d232a', - plot_bgcolor: '#1d232a', - margin: { - l: 50, - r: 50, - b: 50, - t: 50, - pad: 4, - }, - // biome-ignore-end lint/style/useNamingConvention: library interface names + ...DARK_LAYOUT, + xaxis: axisConfig('Virtual Time', { rangemode: 'tozero' }), + yaxis: axisConfig('Send Count', { rangemode: 'tozero' }), }; - const config = { responsive: true }; - Plotly.newPlot(this.networkTimePlotEl, data, layout, config); - this.isPlotInitialized = true; + const data = [{ x: [], y: [], showlegend: true }]; + initPlot({ + component: this, + elementId: 'networkTimePlot', + elementProp: 'networkTimePlotEl', + data, + layout, + }); }, async load() { @@ -130,10 +80,7 @@ export const networkTimePlot = () => ({ }, purge() { - if (this.networkTimePlotEl) { - Plotly.purge(this.networkTimePlotEl); - this.isPlotInitialized = false; - } + purgePlot(this, 'networkTimePlotEl'); }, /** @@ -168,33 +115,9 @@ export const networkTimePlot = () => ({ })); Plotly.react(this.networkTimePlotEl, traces, { - // biome-ignore-start lint/style/useNamingConvention: library interface names - xaxis: { - title: { - text: - this.xAxisValues.find((item) => item.key === this.selectedXAxis)?.label ?? - this.selectedXAxis, - }, - color: 'white', - }, - yaxis: { - title: { - text: - this.yAxisValues.find((item) => item.key === this.selectedYAxis)?.label ?? - this.selectedYAxis, - }, - color: 'white', - }, - paper_bgcolor: '#1d232a', - plot_bgcolor: '#1d232a', - margin: { - l: 50, - r: 50, - b: 50, - t: 50, - pad: 4, - }, - // biome-ignore-end lint/style/useNamingConvention: library interface names + ...DARK_LAYOUT, + xaxis: axisConfig(getLabel(this.xAxisValues, this.selectedXAxis, this.selectedXAxis)), + yaxis: axisConfig(getLabel(this.yAxisValues, this.selectedYAxis, this.selectedYAxis)), }); }, }); diff --git a/net_maestro/core/static/net_maestro/plots/parallelCoords.js b/net_maestro/core/static/net_maestro/plots/parallelCoords.js index 117a6d0..84c975c 100644 --- a/net_maestro/core/static/net_maestro/plots/parallelCoords.js +++ b/net_maestro/core/static/net_maestro/plots/parallelCoords.js @@ -3,6 +3,13 @@ * Displays multi-dimensional ROSS data using Plotly's parallel coordinates chart. */ import Plotly from 'plotly'; +import { + DARK_LAYOUT, + initPlot, + purgePlot, + setupLoadWatcher, + setupParallelSyncWatchers, +} from './plotUtils.js'; export const parallelCoords = () => ({ parallelPlotEl: null, @@ -23,80 +30,8 @@ export const parallelCoords = () => ({ * Initialize the component and set up watchers. */ init() { - // Load data if a run is already selected - if (this.$store.dataStore.selectedRunId) { - this.load(); - } - - // Watch for new data loads - this.$watch('$store.dataStore.loadTick', () => { - this.load(); - }); - - this.setupSyncWatchers(); - }, - - setupSyncWatchers() { - const sync = this.$store.plotSyncStore; - - this.$watch('$store.plotSyncStore.parameterRanges', (ranges) => { - if (sync.lastUpdatedBy === this.plotId || !this.isPlotInitialized) { - return; - } - for (let i = 0; i < this.plotDimensions.length; i++) { - const dimKey = this.plotDimensions[i].key; - const range = ranges[dimKey]; - if (range) { - this.applySyncedDimension(i, [range.min, range.max]); - } - } - }); - - this.$watch('$store.plotSyncStore.resetTick', () => { - if (sync.lastUpdatedBy === this.plotId || !this.isPlotInitialized) { - return; - } - this.resetAllDimensions(); - }); - }, - - async applySyncedDimension(index, constraintRange) { - if (!this.parallelPlotEl) { - return; - } - this.isSyncing = true; - const dimensions = this.parallelPlotEl.data[0]?.dimensions; - if (dimensions?.[index]) { - dimensions[index].constraintrange = constraintRange; - try { - await Plotly.restyle(this.parallelPlotEl, { dimensions: [dimensions] }); - } finally { - this.isSyncing = false; - } - } else { - this.isSyncing = false; - } - }, - - async resetAllDimensions() { - if (!this.parallelPlotEl) { - return; - } - this.isSyncing = true; - const dimensions = this.parallelPlotEl.data[0]?.dimensions; - if (dimensions) { - for (const dim of dimensions) { - dim.constraintrange = undefined; - dim.range = undefined; - } - try { - await Plotly.restyle(this.parallelPlotEl, { dimensions: [dimensions] }); - } finally { - this.isSyncing = false; - } - } else { - this.isSyncing = false; - } + setupLoadWatcher(this, () => this.load()); + setupParallelSyncWatchers(this, 'parallelPlotEl', this.plotDimensions); }, /** @@ -132,11 +67,6 @@ export const parallelCoords = () => ({ * Initialize the Plotly parallel coordinates plot. */ initPlot() { - if (this.isPlotInitialized) { - return; - } - this.parallelPlotEl = document.getElementById('parallelCoords'); - const data = [ { type: 'parcoords', @@ -148,28 +78,19 @@ export const parallelCoords = () => ({ dimensions: [{}], }, ]; - const layout = { - // biome-ignore-start lint/style/useNamingConvention: library interface names - paper_bgcolor: '#1d232a', - plot_bgcolor: '#1d232a', - font: { - color: 'white', - }, - margin: { - t: 50, - b: 50, - l: 50, - r: 50, - pad: 4, - }, - // biome-ignore-end lint/style/useNamingConvention: library interface names - }; - const config = { responsive: true }; - Plotly.newPlot(this.parallelPlotEl, data, layout, config); - this.isPlotInitialized = true; - - this.parallelPlotEl.on('plotly_restyle', () => { - this.onRestyle(); + const layout = DARK_LAYOUT; + initPlot({ + component: this, + elementId: 'parallelCoords', + elementProp: 'parallelPlotEl', + data, + layout, + eventHandlers: [ + { + event: 'plotly_restyle', + handler: () => this.onRestyle(), + }, + ], }); }, @@ -191,10 +112,7 @@ export const parallelCoords = () => ({ }, purge() { - if (this.parallelPlotEl) { - Plotly.purge(this.parallelPlotEl); - this.isPlotInitialized = false; - } + purgePlot(this, 'parallelPlotEl'); }, /** @@ -218,23 +136,6 @@ export const parallelCoords = () => ({ })), }; - const layout = { - // biome-ignore-start lint/style/useNamingConvention: library interface names - paper_bgcolor: '#1d232a', - plot_bgcolor: '#1d232a', - font: { - color: 'white', - }, - margin: { - t: 50, - b: 50, - l: 50, - r: 50, - pad: 4, - }, - // biome-ignore-end lint/style/useNamingConvention: library interface names - }; - - Plotly.react(this.parallelPlotEl, [trace], layout); + Plotly.react(this.parallelPlotEl, [trace], DARK_LAYOUT); }, }); diff --git a/net_maestro/core/static/net_maestro/plots/plotUtils.js b/net_maestro/core/static/net_maestro/plots/plotUtils.js new file mode 100644 index 0000000..cfb01e4 --- /dev/null +++ b/net_maestro/core/static/net_maestro/plots/plotUtils.js @@ -0,0 +1,349 @@ +/** + * Shared utilities for Plotly plot components. + * Centralizes common patterns for layout, initialization, and synchronization. + * + * NOTE: The synchronization code (setupXYSyncWatchers, setupParallelSyncWatchers, handleRelayout) + * currently only supports simulation plots. When multiple event or model plots are added, the + * synchronization system will need to be extended to support syncing within each plot type + * (model plots with model plots, event plots with event plots, etc.). + */ +import Plotly from 'plotly'; + +/** + * Dark theme layout configuration for Plotly plots. + */ +export const DARK_LAYOUT = { + // biome-ignore-start lint/style/useNamingConvention: Plotly layout API requires snake_case keys + paper_bgcolor: '#1d232a', + plot_bgcolor: '#1d232a', + // biome-ignore-end lint/style/useNamingConvention: Plotly layout API requires snake_case keys + font: { + color: 'white', + }, + margin: { + l: 50, + r: 50, + b: 50, + t: 50, + pad: 4, + }, +}; + +/** + * Creates an axis configuration with title and styling. + * @param {string} title - Axis title text + * @param {Object} options - Additional axis options + * @returns {Object} Plotly axis configuration + */ +export function axisConfig(title, options = {}) { + return { + title: { + text: title, + }, + color: 'white', + ...options, + }; +} + +/** + * Initializes a plot with standard configuration. + * @param {Object} options - Configuration options + * @param {Object} options.component - Component instance + * @param {string} options.elementId - DOM element ID for the plot + * @param {string} options.elementProp - Property name storing the element reference + * @param {Array} options.data - Plotly data array + * @param {Object} options.layout - Plotly layout configuration + * @param {Array} options.eventHandlers - Array of {event, handler} objects + */ +export function initPlot({ component, elementId, elementProp, data, layout, eventHandlers = [] }) { + if (component.isPlotInitialized) { + return; + } + const el = document.getElementById(elementId); + if (!el) { + return; + } + component[elementProp] = el; + + const config = { responsive: true }; + Plotly.newPlot(el, data, layout, config); + component.isPlotInitialized = true; + + for (const { event, handler } of eventHandlers) { + el.on(event, handler); + } +} + +/** + * Purges a plot and resets initialization state. + * @param {Object} component - Component instance + * @param {string} elementProp - Property name storing the element reference + */ +export function purgePlot(component, elementProp) { + const el = component[elementProp]; + if (el) { + Plotly.purge(el); + component.isPlotInitialized = false; + } +} + +/** + * Sets up axis state persistence using uiStateStore. + * @param {Object} component - Component instance + * @param {string} plotName - Name of the plot for state key + * @param {Array} axisConfigs - Array of {prop, default} objects + */ +export function setupAxisState(component, plotName, axisConfigs) { + const savedState = component.$store.uiStateStore.getUIState(plotName); + for (const { prop, default: defaultValue } of axisConfigs) { + component[prop] = savedState[prop] ?? defaultValue; + component.$watch(prop, (newValue) => { + if (newValue) { + component.$store.uiStateStore.saveUIState(plotName, { [prop]: newValue }); + } + }); + } +} + +/** + * Sets up a watcher for dataStore.loadTick to trigger data loading. + * @param {Object} component - Component instance + * @param {Function} loadFn - Function to call when loadTick changes + */ +export function setupLoadWatcher(component, loadFn) { + // Load data if a run is already selected + if (component.$store.dataStore.selectedRunId) { + loadFn(); + } + // Watch for new data loads + component.$watch('$store.dataStore.loadTick', () => { + loadFn(); + }); +} + +/** + * Handles plotly_relayout events for X/Y axis synchronization. + * @param {Object} component - Component instance + * @param {Object} eventData - Plotly relayout event data + * @param {Function} getXAxis - Function returning current X axis key + * @param {Function} getYAxis - Function returning current Y axis key + */ +export function handleRelayout(component, eventData, getXAxis, getYAxis) { + if (component.isSyncing) { + return; + } + const sync = component.$store.plotSyncStore; + + // Detect double-click reset + if (eventData['xaxis.autorange'] || eventData['yaxis.autorange']) { + sync.resetAll(component.plotId); + return; + } + + const updates = _collectRangeUpdates(eventData, getXAxis, getYAxis); + if (updates.length > 0) { + sync.updateRanges(updates, component.plotId); + } +} + +/** + * Collects range updates from relayout event data. + * @param {Object} eventData - Relayout event data + * @param {Function} getXAxis - Function returning current X axis key + * @param {Function} getYAxis - Function returning current Y axis key + * @returns {Array} Array of {parameter, range} objects + */ +function _collectRangeUpdates(eventData, getXAxis, getYAxis) { + const updates = []; + if (eventData['xaxis.range[0]'] != null) { + updates.push({ + parameter: getXAxis(), + range: { min: eventData['xaxis.range[0]'], max: eventData['xaxis.range[1]'] }, + }); + } + if (eventData['yaxis.range[0]'] != null) { + updates.push({ + parameter: getYAxis(), + range: { min: eventData['yaxis.range[0]'], max: eventData['yaxis.range[1]'] }, + }); + } + return updates; +} + +/** + * Sets up X/Y axis synchronization watchers. + * @param {Object} component - Component instance + * @param {string} elementProp - Property name storing the element reference + * @param {Function} getXAxis - Function returning current X axis key + * @param {Function} getYAxis - Function returning current Y axis key + */ +export function setupXYSyncWatchers(component, elementProp, getXAxis, getYAxis) { + const sync = component.$store.plotSyncStore; + + // Watch for range changes from other plots + component.$watch('$store.plotSyncStore.parameterRanges', (ranges) => { + if (sync.lastUpdatedBy === component.plotId) { + return; + } + const xRange = ranges[getXAxis()]; + const yRange = ranges[getYAxis()]; + if (!(xRange || yRange)) { + return; + } + _applySyncedRange(component, elementProp, xRange, yRange); + }); + + // Watch for reset from other plots + component.$watch('$store.plotSyncStore.resetTick', async () => { + if (sync.lastUpdatedBy === component.plotId || !component.isPlotInitialized) { + return; + } + component.isSyncing = true; + try { + await Plotly.relayout(component[elementProp], { + 'xaxis.autorange': true, + 'yaxis.autorange': true, + }); + } finally { + component.isSyncing = false; + } + }); +} + +/** + * Applies synced X/Y axis ranges from another plot. + * @param {Object} component - Component instance + * @param {string} elementProp - Property name storing the element reference + * @param {Object} xRange - X axis range {min, max} + * @param {Object} yRange - Y axis range {min, max} + */ +async function _applySyncedRange(component, elementProp, xRange, yRange) { + const el = component[elementProp]; + if (!(el && component.isPlotInitialized)) { + return; + } + const update = {}; + if (xRange) { + update['xaxis.range'] = [xRange.min, xRange.max]; + } + if (yRange) { + update['yaxis.range'] = [yRange.min, yRange.max]; + } + if (Object.keys(update).length === 0) { + return; + } + component.isSyncing = true; + try { + await Plotly.relayout(el, update); + } finally { + component.isSyncing = false; + } +} + +/** + * Sets up parallel coordinates dimension synchronization watchers. + * @param {Object} component - Component instance + * @param {string} elementProp - Property name storing the element reference + * @param {Array} dimensions - Array of dimension objects with key property + */ +export function setupParallelSyncWatchers(component, elementProp, dimensions) { + const sync = component.$store.plotSyncStore; + + component.$watch('$store.plotSyncStore.parameterRanges', (ranges) => { + if (sync.lastUpdatedBy === component.plotId || !component.isPlotInitialized) { + return; + } + for (let i = 0; i < dimensions.length; i++) { + const dimKey = dimensions[i].key; + const range = ranges[dimKey]; + if (range) { + _applySyncedDimension(component, elementProp, i, [range.min, range.max]); + } + } + }); + + component.$watch('$store.plotSyncStore.resetTick', () => { + if (sync.lastUpdatedBy === component.plotId || !component.isPlotInitialized) { + return; + } + _resetAllDimensions(component, elementProp); + }); +} + +/** + * Applies synced dimension constraint range from another plot. + * @param {Object} component - Component instance + * @param {string} elementProp - Property name storing the element reference + * @param {number} index - Dimension index + * @param {Array} constraintRange - [min, max] constraint range + */ +async function _applySyncedDimension(component, elementProp, index, constraintRange) { + const el = component[elementProp]; + if (!el) { + return; + } + component.isSyncing = true; + const dimensions = el.data[0]?.dimensions; + if (dimensions?.[index]) { + dimensions[index].constraintrange = constraintRange; + try { + await Plotly.restyle(el, { dimensions: [dimensions] }); + } finally { + component.isSyncing = false; + } + } else { + component.isSyncing = false; + } +} + +/** + * Resets all dimension constraints. + * @param {Object} component - Component instance + * @param {string} elementProp - Property name storing the element reference + */ +async function _resetAllDimensions(component, elementProp) { + const el = component[elementProp]; + if (!el) { + return; + } + component.isSyncing = true; + const dimensions = el.data[0]?.dimensions; + if (dimensions) { + for (const dim of dimensions) { + dim.constraintrange = undefined; + dim.range = undefined; + } + try { + await Plotly.restyle(el, { dimensions: [dimensions] }); + } finally { + component.isSyncing = false; + } + } else { + component.isSyncing = false; + } +} + +/** + * Creates a value list from columns, excluding specified columns. + * @param {Array} columns - Column names + * @param {Array} excludedColumns - Columns to exclude + * @returns {Array} Array of {key, label} objects + */ +export function createValueList(columns, excludedColumns = []) { + const filteredColumns = columns.filter((column) => column && !excludedColumns.includes(column)); + return filteredColumns.map((value) => ({ + key: value, + label: value.replaceAll('_', ' '), + })); +} + +/** + * Gets label for a key from a value list, with fallback. + * @param {Array} valueList - Array of {key, label} objects + * @param {string} key - Key to look up + * @param {string} fallback - Fallback label if key not found + * @returns {string} Label or fallback + */ +export function getLabel(valueList, key, fallback) { + return valueList.find((item) => item.key === key)?.label ?? fallback; +} diff --git a/net_maestro/core/static/net_maestro/plots/scatterPlot.js b/net_maestro/core/static/net_maestro/plots/scatterPlot.js index 9a1ed90..669f9e0 100644 --- a/net_maestro/core/static/net_maestro/plots/scatterPlot.js +++ b/net_maestro/core/static/net_maestro/plots/scatterPlot.js @@ -3,6 +3,18 @@ * Displays ROSS simulation data as a scatter plot with configurable axes. */ import Plotly from 'plotly'; +import { + axisConfig, + createValueList, + DARK_LAYOUT, + getLabel, + handleRelayout, + initPlot, + purgePlot, + setupAxisState, + setupLoadWatcher, + setupXYSyncWatchers, +} from './plotUtils.js'; export const scatterPlot = () => ({ // Component state @@ -18,191 +30,54 @@ export const scatterPlot = () => ({ isSyncing: false, get valueList() { - const excludedColumns = ['PE_ID', 'real_time', 'virtual_time']; - const filteredColumns = this.columns.filter( - (column) => column && !excludedColumns.includes(column), - ); - return filteredColumns.map((value) => ({ - key: value, - label: value.replaceAll('_', ' '), - })); + return createValueList(this.columns, ['PE_ID', 'real_time', 'virtual_time']); }, /** * Initialize the component and set up watchers. */ init() { - // Restore UI state - const savedState = this.$store.uiStateStore.getUIState('scatterPlot'); - this.selectedXAxis = savedState.selectedXAxis ?? 'events_processed'; - this.selectedYAxis = savedState.selectedYAxis ?? 'events_rolled_back'; - - this.$watch('selectedXAxis', (newValue) => { - if (newValue) { - this.$store.uiStateStore.saveUIState('scatterPlot', { selectedXAxis: newValue }); - } - }); - - this.$watch('selectedYAxis', (newValue) => { - if (newValue) { - this.$store.uiStateStore.saveUIState('scatterPlot', { selectedYAxis: newValue }); - } - }); - - // Load data if a run is already selected - if (this.$store.dataStore.selectedRunId) { - this.load(); - } - - // Watch for new data loads - this.$watch('$store.dataStore.loadTick', () => { - this.load(); - }); - - this.setupSyncWatchers(); - }, - - setupSyncWatchers() { - const sync = this.$store.plotSyncStore; - - this.$watch('$store.plotSyncStore.parameterRanges', (ranges) => { - if (sync.lastUpdatedBy === this.plotId) { - return; - } - const xRange = ranges[this.selectedXAxis]; - const yRange = ranges[this.selectedYAxis]; - if (!(xRange || yRange)) { - return; - } - this.applySyncedRange(xRange, yRange); - }); - - this.$watch('$store.plotSyncStore.resetTick', async () => { - if (sync.lastUpdatedBy === this.plotId || !this.isPlotInitialized) { - return; - } - this.isSyncing = true; - try { - await Plotly.relayout(this.scatterPlotEl, { - 'xaxis.autorange': true, - 'yaxis.autorange': true, - }); - } finally { - this.isSyncing = false; - } - }); - }, - - async applySyncedRange(xRange, yRange) { - if (!(this.scatterPlotEl && this.isPlotInitialized)) { - return; - } - const update = {}; - if (xRange) { - update['xaxis.range'] = [xRange.min, xRange.max]; - } - if (yRange) { - update['yaxis.range'] = [yRange.min, yRange.max]; - } - if (Object.keys(update).length === 0) { - return; - } - this.isSyncing = true; - try { - await Plotly.relayout(this.scatterPlotEl, update); - } finally { - this.isSyncing = false; - } - }, - - onRelayout(eventData) { - if (this.isSyncing) { - return; - } - const sync = this.$store.plotSyncStore; - - if (eventData['xaxis.autorange'] || eventData['yaxis.autorange']) { - sync.resetAll(this.plotId); - return; - } - - const updates = this.collectRangeUpdates(eventData); - if (updates.length > 0) { - sync.updateRanges(updates, this.plotId); - } - }, - - collectRangeUpdates(eventData) { - const updates = []; - if (eventData['xaxis.range[0]'] != null) { - updates.push({ - parameter: this.selectedXAxis, - range: { min: eventData['xaxis.range[0]'], max: eventData['xaxis.range[1]'] }, - }); - } - if (eventData['yaxis.range[0]'] != null) { - updates.push({ - parameter: this.selectedYAxis, - range: { min: eventData['yaxis.range[0]'], max: eventData['yaxis.range[1]'] }, - }); - } - return updates; + setupAxisState(this, 'scatterPlot', [ + { prop: 'selectedXAxis', default: 'events_processed' }, + { prop: 'selectedYAxis', default: 'events_rolled_back' }, + ]); + setupLoadWatcher(this, () => this.load()); + setupXYSyncWatchers( + this, + 'scatterPlotEl', + () => this.selectedXAxis, + () => this.selectedYAxis, + ); }, /** * Initialize the Plotly scatter plot. */ initPlot() { - if (this.isPlotInitialized) { - return; - } - this.scatterPlotEl = document.getElementById('scatterPlot'); - if (!this.scatterPlotEl) { - return; - } - - const data = [ - { - x: [], - y: [], - mode: 'markers', - type: 'scatter', - showlegend: true, - }, - ]; const layout = { - // biome-ignore-start lint/style/useNamingConvention: library interface names - xaxis: { - title: { - text: 'Events Processed', - }, - rangemode: 'tozero', - color: 'white', - }, - yaxis: { - title: { - text: 'Events Rolled Back', - }, - rangemode: 'tozero', - color: 'white', - }, - paper_bgcolor: '#1d232a', - plot_bgcolor: '#1d232a', - margin: { - l: 50, - r: 50, - b: 50, - t: 50, - pad: 4, - }, - // biome-ignore-end lint/style/useNamingConvention: library interface names + ...DARK_LAYOUT, + xaxis: axisConfig('Events Processed'), + yaxis: axisConfig('Events Rolled Back'), }; - const config = { responsive: true }; - Plotly.newPlot(this.scatterPlotEl, data, layout, config); - this.isPlotInitialized = true; - - this.scatterPlotEl.on('plotly_relayout', (eventData) => { - this.onRelayout(eventData); + const data = [{ x: [], y: [], mode: 'markers', type: 'scatter', showlegend: true }]; + initPlot({ + component: this, + elementId: 'scatterPlot', + elementProp: 'scatterPlotEl', + data, + layout, + eventHandlers: [ + { + event: 'plotly_relayout', + handler: (eventData) => + handleRelayout( + this, + eventData, + () => this.selectedXAxis, + () => this.selectedYAxis, + ), + }, + ], }); }, @@ -227,10 +102,7 @@ export const scatterPlot = () => ({ }, purge() { - if (this.scatterPlotEl) { - Plotly.purge(this.scatterPlotEl); - this.isPlotInitialized = false; - } + purgePlot(this, 'scatterPlotEl'); }, /** @@ -258,22 +130,8 @@ export const scatterPlot = () => ({ }, }, { - xaxis: { - title: { - text: - this.valueList.find((item) => item.key === this.selectedXAxis)?.label ?? - this.selectedXAxis, - }, - color: 'white', - }, - yaxis: { - title: { - text: - this.valueList.find((item) => item.key === this.selectedYAxis)?.label ?? - this.selectedYAxis, - }, - color: 'white', - }, + xaxis: axisConfig(getLabel(this.valueList, this.selectedXAxis, this.selectedXAxis)), + yaxis: axisConfig(getLabel(this.valueList, this.selectedYAxis, this.selectedYAxis)), }, ); }, diff --git a/net_maestro/core/static/net_maestro/plots/timePlot.js b/net_maestro/core/static/net_maestro/plots/timePlot.js index 62cfe77..f866339 100644 --- a/net_maestro/core/static/net_maestro/plots/timePlot.js +++ b/net_maestro/core/static/net_maestro/plots/timePlot.js @@ -3,6 +3,18 @@ * Displays ROSS simulation data over time with configurable axes. */ import Plotly from 'plotly'; +import { + axisConfig, + createValueList, + DARK_LAYOUT, + getLabel, + handleRelayout, + initPlot, + purgePlot, + setupAxisState, + setupLoadWatcher, + setupXYSyncWatchers, +} from './plotUtils.js'; export const timePlot = () => ({ records: [], @@ -23,15 +35,7 @@ export const timePlot = () => ({ ]; }, get yAxisValues() { - const excludedColumns = ['PE_ID', 'real_time', 'virtual_time']; - const filteredColumns = this.columns.filter( - (column) => column && !excludedColumns.includes(column), - ); - - return filteredColumns.map((value) => ({ - key: value, - label: value.replaceAll('_', ' '), - })); + return createValueList(this.columns, ['PE_ID', 'real_time', 'virtual_time']); }, /** @@ -39,178 +43,47 @@ export const timePlot = () => ({ * Called automatically by Alpine.js when component mounts. */ init() { - // Restore UI state - const savedState = this.$store.uiStateStore.getUIState('timePlot'); - this.selectedXAxis = savedState.selectedXAxis ?? 'virtual_time'; - this.selectedYAxis = savedState.selectedYAxis ?? 'events_processed'; - - this.$watch('selectedXAxis', (newValue) => { - if (newValue) { - this.$store.uiStateStore.saveUIState('timePlot', { selectedXAxis: newValue }); - } - }); - - this.$watch('selectedYAxis', (newValue) => { - if (newValue) { - this.$store.uiStateStore.saveUIState('timePlot', { selectedYAxis: newValue }); - } - }); - - // Load data if a run is already selected - if (this.$store.dataStore.selectedRunId) { - this.load(); - } - - // Watch for new data loads - this.$watch('$store.dataStore.loadTick', () => { - this.load(); - }); - - this.setupSyncWatchers(); - }, - - setupSyncWatchers() { - const sync = this.$store.plotSyncStore; - - // Watch for range changes from other plots - this.$watch('$store.plotSyncStore.parameterRanges', (ranges) => { - if (sync.lastUpdatedBy === this.plotId) { - return; - } - const xRange = ranges[this.selectedXAxis]; - const yRange = ranges[this.selectedYAxis]; - if (!(xRange || yRange)) { - return; - } - this.applySyncedRange(xRange, yRange); - }); - - // Watch for reset from other plots - this.$watch('$store.plotSyncStore.resetTick', async () => { - if (sync.lastUpdatedBy === this.plotId || !this.isPlotInitialized) { - return; - } - this.isSyncing = true; - try { - await Plotly.relayout(this.timePlotEl, { - 'xaxis.autorange': true, - 'yaxis.autorange': true, - }); - } finally { - this.isSyncing = false; - } - }); - }, - - async applySyncedRange(xRange, yRange) { - if (!(this.timePlotEl && this.isPlotInitialized)) { - return; - } - const update = {}; - if (xRange) { - update['xaxis.range'] = [xRange.min, xRange.max]; - } - if (yRange) { - update['yaxis.range'] = [yRange.min, yRange.max]; - } - if (Object.keys(update).length === 0) { - return; - } - this.isSyncing = true; - try { - await Plotly.relayout(this.timePlotEl, update); - } finally { - this.isSyncing = false; - } - }, - - onRelayout(eventData) { - if (this.isSyncing) { - return; - } - const sync = this.$store.plotSyncStore; - - // Detect double-click reset - if (eventData['xaxis.autorange'] || eventData['yaxis.autorange']) { - sync.resetAll(this.plotId); - return; - } - - const updates = this.collectRangeUpdates(eventData); - if (updates.length > 0) { - sync.updateRanges(updates, this.plotId); - } - }, - - collectRangeUpdates(eventData) { - const updates = []; - if (eventData['xaxis.range[0]'] != null) { - updates.push({ - parameter: this.selectedXAxis, - range: { min: eventData['xaxis.range[0]'], max: eventData['xaxis.range[1]'] }, - }); - } - if (eventData['yaxis.range[0]'] != null) { - updates.push({ - parameter: this.selectedYAxis, - range: { min: eventData['yaxis.range[0]'], max: eventData['yaxis.range[1]'] }, - }); - } - return updates; + setupAxisState(this, 'timePlot', [ + { prop: 'selectedXAxis', default: 'virtual_time' }, + { prop: 'selectedYAxis', default: 'events_processed' }, + ]); + setupLoadWatcher(this, () => this.load()); + setupXYSyncWatchers( + this, + 'timePlotEl', + () => this.selectedXAxis, + () => this.selectedYAxis, + ); }, /** * Initialize the Plotly time plot. */ initPlot() { - if (this.isPlotInitialized) { - return; - } - this.timePlotEl = document.getElementById('timePlot'); - if (!this.timePlotEl) { - return; - } - - const data = [ - { - x: [], - y: [], - showlegend: true, - }, - ]; const layout = { - // biome-ignore-start lint/style/useNamingConvention: library interface names - xaxis: { - title: { - text: 'Virtual Time', - }, - rangemode: 'tozero', - color: 'white', - }, - yaxis: { - title: { - text: 'Events Processed', - }, - rangemode: 'tozero', - color: 'white', - }, - paper_bgcolor: '#1d232a', - plot_bgcolor: '#1d232a', - margin: { - l: 50, - r: 50, - b: 50, - t: 50, - pad: 4, - }, - // biome-ignore-end lint/style/useNamingConvention: library interface names + ...DARK_LAYOUT, + xaxis: axisConfig('Virtual Time', { rangemode: 'tozero' }), + yaxis: axisConfig('Events Processed', { rangemode: 'tozero' }), }; - const config = { responsive: true }; - Plotly.newPlot(this.timePlotEl, data, layout, config); - this.isPlotInitialized = true; - - this.timePlotEl.on('plotly_relayout', (eventData) => { - this.onRelayout(eventData); + const data = [{ x: [], y: [], showlegend: true }]; + initPlot({ + component: this, + elementId: 'timePlot', + elementProp: 'timePlotEl', + data, + layout, + eventHandlers: [ + { + event: 'plotly_relayout', + handler: (eventData) => + handleRelayout( + this, + eventData, + () => this.selectedXAxis, + () => this.selectedYAxis, + ), + }, + ], }); }, @@ -235,10 +108,7 @@ export const timePlot = () => ({ }, purge() { - if (this.timePlotEl) { - Plotly.purge(this.timePlotEl); - this.isPlotInitialized = false; - } + purgePlot(this, 'timePlotEl'); }, /** @@ -272,33 +142,9 @@ export const timePlot = () => ({ })); Plotly.react(this.timePlotEl, traces, { - // biome-ignore-start lint/style/useNamingConvention: library interface names - xaxis: { - title: { - text: - this.xAxisValues.find((item) => item.key === this.selectedXAxis)?.label ?? - this.selectedXAxis, - }, - color: 'white', - }, - yaxis: { - title: { - text: - this.yAxisValues.find((item) => item.key === this.selectedYAxis)?.label ?? - this.selectedYAxis, - }, - color: 'white', - }, - paper_bgcolor: '#1d232a', - plot_bgcolor: '#1d232a', - margin: { - l: 50, - r: 50, - b: 50, - t: 50, - pad: 4, - }, - // biome-ignore-end lint/style/useNamingConvention: library interface names + ...DARK_LAYOUT, + xaxis: axisConfig(getLabel(this.xAxisValues, this.selectedXAxis, this.selectedXAxis)), + yaxis: axisConfig(getLabel(this.yAxisValues, this.selectedYAxis, this.selectedYAxis)), }); }, }); diff --git a/package-lock.json b/package-lock.json index 6b0d492..5ff99a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,11 @@ "packages": { "": { "name": "net-maestro", + "dependencies": { + "alpinejs": "^3.0.0", + "lodash": "^4.18.1", + "plotly": "npm:plotly.js-dist-min@^3.0.0" + }, "devDependencies": { "@biomejs/biome": "2.4.16", "@resonant/biome-config": "0.3.2" @@ -79,9 +84,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -99,9 +101,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -119,9 +118,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -139,9 +135,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -194,6 +187,43 @@ "peerDependencies": { "@biomejs/biome": "~2.4.5" } + }, + "node_modules/@vue/reactivity": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz", + "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.1.5" + } + }, + "node_modules/@vue/shared": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz", + "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==", + "license": "MIT" + }, + "node_modules/alpinejs": { + "version": "3.15.12", + "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.12.tgz", + "integrity": "sha512-nJvPAQVNPdZZ0NrExJ/kzQco3ijR8LwvCOadQecllESiqT4NyZ/57sN9V2XyvhlBGAbmlKYgeWZvYdKq99ij/Q==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "~3.1.1" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/plotly": { + "name": "plotly.js-dist-min", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-3.7.0.tgz", + "integrity": "sha512-IRWNnBJZmKss3URDnicBK2nvt/VTSi/MD1GnUscAYFjwSuN6g/CTde5R1UC0RYtblehj8rkT4BL8r05e/c8j5Q==", + "license": "MIT" } } } diff --git a/package.json b/package.json index fee170b..a3714b9 100644 --- a/package.json +++ b/package.json @@ -8,5 +8,10 @@ "devDependencies": { "@biomejs/biome": "2.4.16", "@resonant/biome-config": "0.3.2" + }, + "dependencies": { + "alpinejs": "^3.0.0", + "lodash": "^4.18.1", + "plotly": "npm:plotly.js-dist-min@^3.0.0" } } From de698a63eaecb7f324dcad74011b325d6dd8e73d Mon Sep 17 00:00:00 2001 From: Brianna Major Date: Sun, 12 Jul 2026 09:25:37 -0400 Subject: [PATCH 2/2] Rename shared plotUtils initPlot to setupPlot The local initPlot() method internally called the same-named imported initPlot() from plotUtils.js, overloading the identifier with two different meanings. Rename the shared utility to `setupPlot` to remove the ambiguity. --- net_maestro/core/static/net_maestro/plots/heatmap.js | 10 ++++++++-- .../core/static/net_maestro/plots/networkTimePlot.js | 4 ++-- .../core/static/net_maestro/plots/parallelCoords.js | 4 ++-- net_maestro/core/static/net_maestro/plots/plotUtils.js | 4 ++-- .../core/static/net_maestro/plots/scatterPlot.js | 4 ++-- net_maestro/core/static/net_maestro/plots/timePlot.js | 4 ++-- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/net_maestro/core/static/net_maestro/plots/heatmap.js b/net_maestro/core/static/net_maestro/plots/heatmap.js index d00522b..a821bd1 100644 --- a/net_maestro/core/static/net_maestro/plots/heatmap.js +++ b/net_maestro/core/static/net_maestro/plots/heatmap.js @@ -4,7 +4,13 @@ */ import _ from 'lodash'; import Plotly from 'plotly'; -import { DARK_LAYOUT, initPlot, purgePlot, setupAxisState, setupLoadWatcher } from './plotUtils.js'; +import { + DARK_LAYOUT, + purgePlot, + setupAxisState, + setupLoadWatcher, + setupPlot, +} from './plotUtils.js'; export const heatmapPlot = () => ({ heatmapPlotEl: null, @@ -52,7 +58,7 @@ export const heatmapPlot = () => ({ title: 'Sending LP ID', }, }; - initPlot({ + setupPlot({ component: this, elementId: 'heatmapPlot', elementProp: 'heatmapPlotEl', diff --git a/net_maestro/core/static/net_maestro/plots/networkTimePlot.js b/net_maestro/core/static/net_maestro/plots/networkTimePlot.js index 29dfe4d..de5146f 100644 --- a/net_maestro/core/static/net_maestro/plots/networkTimePlot.js +++ b/net_maestro/core/static/net_maestro/plots/networkTimePlot.js @@ -4,10 +4,10 @@ import { createValueList, DARK_LAYOUT, getLabel, - initPlot, purgePlot, setupAxisState, setupLoadWatcher, + setupPlot, } from './plotUtils.js'; export const networkTimePlot = () => ({ @@ -52,7 +52,7 @@ export const networkTimePlot = () => ({ yaxis: axisConfig('Send Count', { rangemode: 'tozero' }), }; const data = [{ x: [], y: [], showlegend: true }]; - initPlot({ + setupPlot({ component: this, elementId: 'networkTimePlot', elementProp: 'networkTimePlotEl', diff --git a/net_maestro/core/static/net_maestro/plots/parallelCoords.js b/net_maestro/core/static/net_maestro/plots/parallelCoords.js index 84c975c..9dc9027 100644 --- a/net_maestro/core/static/net_maestro/plots/parallelCoords.js +++ b/net_maestro/core/static/net_maestro/plots/parallelCoords.js @@ -5,10 +5,10 @@ import Plotly from 'plotly'; import { DARK_LAYOUT, - initPlot, purgePlot, setupLoadWatcher, setupParallelSyncWatchers, + setupPlot, } from './plotUtils.js'; export const parallelCoords = () => ({ @@ -79,7 +79,7 @@ export const parallelCoords = () => ({ }, ]; const layout = DARK_LAYOUT; - initPlot({ + setupPlot({ component: this, elementId: 'parallelCoords', elementProp: 'parallelPlotEl', diff --git a/net_maestro/core/static/net_maestro/plots/plotUtils.js b/net_maestro/core/static/net_maestro/plots/plotUtils.js index cfb01e4..1975e0b 100644 --- a/net_maestro/core/static/net_maestro/plots/plotUtils.js +++ b/net_maestro/core/static/net_maestro/plots/plotUtils.js @@ -46,7 +46,7 @@ export function axisConfig(title, options = {}) { } /** - * Initializes a plot with standard configuration. + * Sets up a plot with standard configuration. * @param {Object} options - Configuration options * @param {Object} options.component - Component instance * @param {string} options.elementId - DOM element ID for the plot @@ -55,7 +55,7 @@ export function axisConfig(title, options = {}) { * @param {Object} options.layout - Plotly layout configuration * @param {Array} options.eventHandlers - Array of {event, handler} objects */ -export function initPlot({ component, elementId, elementProp, data, layout, eventHandlers = [] }) { +export function setupPlot({ component, elementId, elementProp, data, layout, eventHandlers = [] }) { if (component.isPlotInitialized) { return; } diff --git a/net_maestro/core/static/net_maestro/plots/scatterPlot.js b/net_maestro/core/static/net_maestro/plots/scatterPlot.js index 669f9e0..335b3d5 100644 --- a/net_maestro/core/static/net_maestro/plots/scatterPlot.js +++ b/net_maestro/core/static/net_maestro/plots/scatterPlot.js @@ -9,10 +9,10 @@ import { DARK_LAYOUT, getLabel, handleRelayout, - initPlot, purgePlot, setupAxisState, setupLoadWatcher, + setupPlot, setupXYSyncWatchers, } from './plotUtils.js'; @@ -60,7 +60,7 @@ export const scatterPlot = () => ({ yaxis: axisConfig('Events Rolled Back'), }; const data = [{ x: [], y: [], mode: 'markers', type: 'scatter', showlegend: true }]; - initPlot({ + setupPlot({ component: this, elementId: 'scatterPlot', elementProp: 'scatterPlotEl', diff --git a/net_maestro/core/static/net_maestro/plots/timePlot.js b/net_maestro/core/static/net_maestro/plots/timePlot.js index f866339..ef0f012 100644 --- a/net_maestro/core/static/net_maestro/plots/timePlot.js +++ b/net_maestro/core/static/net_maestro/plots/timePlot.js @@ -9,10 +9,10 @@ import { DARK_LAYOUT, getLabel, handleRelayout, - initPlot, purgePlot, setupAxisState, setupLoadWatcher, + setupPlot, setupXYSyncWatchers, } from './plotUtils.js'; @@ -66,7 +66,7 @@ export const timePlot = () => ({ yaxis: axisConfig('Events Processed', { rangemode: 'tozero' }), }; const data = [{ x: [], y: [], showlegend: true }]; - initPlot({ + setupPlot({ component: this, elementId: 'timePlot', elementProp: 'timePlotEl',