Page Unresponsive warnings during rapid Plotly.react updates with large SVG traces
0 reputation · 08 Feb 2024, 12:27 UTC
0 reputation · 08 Feb 2024, 12:27 UTC
When a dashboard updates a Plotly figure through repeated Plotly.react calls with large SVG trace arrays, the browser can emit a "Page Unresponsive" warning because the main thread remains blocked during the render cycle. Because Plotly.js lacks a built-in asynchronous cancellation mechanism for renders already dispatched to the DOM engine, developers cannot reliably abort an in-flight update when newer data arrives.
The uncertainty is compounded by an unresolved behavior: when multiple rapid react calls are queued before a previous render completes, it is unclear whether the library guarantees consistent state synchronization or whether stale intermediate renders may overwrite newer data. Switching to WebGL-based traces such as scattergl reduces main-thread pressure, but the performance gap is version-dependent and varies by browser engine.
What is the expected behavior for state consistency when overlapping Plotly.react calls are issued on large SVG datasets? Is there a documented strategy for preventing main-thread exhaustion without relying on WebGL fallbacks? How should applications detect and discard superseded renders when cancellation is not natively supported?
32775 reputation · 08 Feb 2024, 14:08 UTC
Expected behavior: Overlapping Plotly.react calls are serialized by the JavaScript runtime, not executed concurrently. Each call reconciles against the graph div's current internal figure and mutates the SVG synchronously. The final DOM state is whichever call executes last, which is not necessarily the call that requested the newest data. If an older async response resolves after a newer one, it can overwrite the newer render.
Confirmed facts: Plotly.react performs incremental SVG/DOM mutation in a single synchronous task. There is no supported API to abort a render once it begins, and no returned promise can cancel the blocking work. The browser's "Page Unresponsive" warning appears because the main thread is occupied for the duration of that task.
Likely explanation for stale overwrites: When multiple async sources or rapid successive calls enqueue tasks, earlier queued calls still run to completion before later tasks execute. Without application-level guards, an older response can call react after a newer response has already applied, producing an intermediate or stale state.
Maintain one pending figure and one requestAnimationFrame handle per graph div. On each update, store the latest figure, cancel any pending frame, and schedule a single frame that renders only the stored figure. This guarantees at most one react call per animation frame and prevents intermediate renders from piling up.
let pendingFigure = null;
let rafHandle = null;
function scheduleUpdate(figure) {
pendingFigure = figure;
if (rafHandle !== null) {
cancelAnimationFrame(rafHandle);
}
rafHandle = requestAnimationFrame(() => {
rafHandle = null;
if (pendingFigure !== null) {
const fig = pendingFigure;
pendingFigure = null;
Plotly.react('graph', fig.data, fig.layout);
}
});
}
Capture a monotonically increasing epoch before starting an async fetch or worker job. Re-check the token immediately before Plotly.react. If the epoch changed, skip the call. This prevents a slow older response from overwriting a newer one.
let currentEpoch = 0;
async function loadAndRender(query) {
const epoch = ++currentEpoch;
const data = await fetchData(query);
if (epoch !== currentEpoch) return;
scheduleUpdate(data);
}
Plotly.restyle or Plotly.update when only data or layout attributes change, rather than a full react.Plotly.newPlot for routine updates, as it can be more expensive and may accumulate SVG nodes.Confirm long tasks with a browser performance profile rather than in-page timers. Check that coalesced updates produce at most one long task per frame. Sample the graph div's SVG child count before and after repeated updates to detect node accumulation, which is version- and trace-shape-sensitive.
Do not assume scattergl is universally faster; WebGL gains vary by point count, trace type, browser engine, and Plotly version. Test the exact version and browsers your application ships.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.