You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add Plotly chart support for MAIDR highlighting across all trace types
Plotly renders SVG differently from matplotlib/seaborn (different element types, attributes, and DOM structures), so each trace type's SVG-to-data mapping is adapted to handle both renderers
A new Plotly adapter normalizes the DOM before MAIDR initialization, keeping renderer-specific logic isolated from core navigation and accessibility features
Changes Made
src/adapter/plotly.ts New adapter that detects Plotly plots and normalizes DOM structure (subplot wrapping, CSS layout, modebar, focus delegation)
src/index.tsx Call Plotly adapter before initMaidr() when a Plotly plot is detected
src/model/scatter.ts Parse transform="translate(x, y)" as fallback when x/y attributes are absent (Plotly uses with transform, not with x/y)
src/model/bar.ts Align SVG elements to data points when Plotly omits zero-height bars from the DOM; insert invisible placeholders for missing bars
src/model/box.ts Support direct q1/q3 CSS selectors for Plotly, which renders Q1/Q3 as separate elements instead of deriving them from IQR rect edges
src/model/heatmap.ts Detect Plotly's single heatmap and generate transparent overlay elements so individual cells can be highlighted
src/model/line.ts Accept comma-separated path coordinates; interpolate highlight positions when Plotly simplifies paths by removing collinear points
src/model/segmented.ts Make zero-value skipping conditional — only skip when DOM count < data count (Plotly histograms), map 1:1 when counts match (Plotly stacked bars)
src/type/grammar.ts Add optional q1 and q3 fields to BoxSelector interface
src/util/svg.ts Use appendChild() instead of insertAdjacentElement('afterend') for correct DOM ordering in Plotly SVG
Code Review for PR 577: feat: support plotly plots
CRITICAL ISSUES
src/util/svg.ts - createCircleElement DOM change affects ALL chart types: The diff changes insertAdjacentElement(afterend) to appendChild(). These have different semantics - afterend inserts the element as a sibling after parent.parentElement, while appendChild inserts it as a last child inside parent.parentElement. Since createCircleElement is used by all chart types, this silently changes DOM structure for existing matplotlib/seaborn scatter charts. A regression test or explanation is needed.
src/adapter/plotly.ts - MutationObservers never disconnected (memory leak): Both setupStrokeMirror and setupLayoutObserver create MutationObserver instances that are never stored or disconnected. MAIDR uses the Disposable pattern precisely for this. When Controller.dispose() fires on focusout, these observers keep running, accumulating on every focus/blur cycle. normalizePlotlySvg should return a cleanup function or integrate with Disposable.
src/index.tsx - Unsafe double type cast: normalizePlotlySvg(plot as unknown as SVGSVGElement, maidr) - isPlotlyPlot returns true when plot.closest('.plotly-graph-div') !== null, so plot could be any HTMLElement, not an SVGSVGElement. The double cast bypasses TypeScript safety and risks a runtime error. Consider widening the parameter type or querying for svg.main-svg inside plot before passing it.
HIGH SEVERITY
src/adapter/plotly.ts - Global document.querySelector won't scale to multiple charts: fixModebarTabOrder, setupLayoutObserver, and setupClickToFocus all query globally. With two Plotly charts on a page, only the first modebar gets fixed and the layout observer targets the wrong react-container. Scope all queries to the chart container.
src/adapter/plotly.ts - Infinite requestAnimationFrame loop: In setupLayoutObserver, if MAIDR fails to create the article element, observe() calls requestAnimationFrame(observe) indefinitely. Add a max retry count or timeout.
src/adapter/plotly.ts - Accessibility regression (outline: none !important): The injected CSS removes focus outline on the MAIDR focusable div. MAIDR is an accessibility tool - this violates WCAG 2.4.7 Focus Visible. Please use box-shadow or another visible alternative instead of removing the outline.
src/adapter/plotly.ts - Click capture phase intercepts Plotly interactions: setupClickToFocus uses capture=true, firing before Plotly's own handlers. Clicking a data point for a Plotly tooltip will be intercepted and redirected to MAIDR focus. Consider bubble phase or checking click target before stealing focus.
src/model/heatmap.ts - Overlay rects never cleaned up: createOverlayRects appends rect elements with no cleanup. On repeated focus/blur cycles (new Controller each time), the heatmap parent accumulates duplicate rects causing rendering artifacts and stale highlight targets.
MEDIUM SEVERITY
src/model/line.ts - Interpolation assumes monotonically increasing x: Range derivation uses coordinates[0].x as min and coordinates[last].x as max; segment search assumes left-to-right path. Non-monotonic lines will produce wrong highlight positions.
src/model/bar.ts - Zero-value bar assumption is fragile: Alignment logic assumes every zero-value point was omitted by Plotly. Plotly does render zero-height bars in some configs (e.g., with a base offset). A clarifying comment would help prevent future regressions.
src/adapter/plotly.ts - Schema selector parsing fragile: Extracting axes id via sel.match(/id="..."/) is fragile. Single-quoted attributes or different attribute ordering silently fails.
POSITIVE OBSERVATIONS
The src/adapter/plotly.ts module is the right architectural choice; renderer-specific logic is cleanly isolated.
The regex fix in line.ts ([\s,]+ instead of \s+) correctly handles comma-separated SVG path coordinates and benefits all renderers.
The skipZeros conditionality in segmented.ts is a clean improvement.
The optional q1/q3 selectors in grammar.ts are backwards-compatible and well-documented.
Inline comments throughout the adapter are clear and helpful.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request
Description
Changes Made
src/adapter/plotly.ts New adapter that detects Plotly plots and normalizes DOM structure (subplot wrapping, CSS layout, modebar, focus delegation)
src/index.tsx Call Plotly adapter before initMaidr() when a Plotly plot is detected
src/model/scatter.ts Parse transform="translate(x, y)" as fallback when x/y attributes are absent (Plotly uses with transform, not with x/y)
src/model/bar.ts Align SVG elements to data points when Plotly omits zero-height bars from the DOM; insert invisible placeholders for missing bars
src/model/box.ts Support direct q1/q3 CSS selectors for Plotly, which renders Q1/Q3 as separate elements instead of deriving them from IQR rect edges
src/model/heatmap.ts Detect Plotly's single
heatmap and generate transparent overlay elements so individual cells can be highlighted
src/model/line.ts Accept comma-separated path coordinates; interpolate highlight positions when Plotly simplifies paths by removing collinear points
src/model/segmented.ts Make zero-value skipping conditional — only skip when DOM count < data count (Plotly histograms), map 1:1 when counts match (Plotly stacked bars)
src/type/grammar.ts Add optional q1 and q3 fields to BoxSelector interface
src/util/svg.ts Use appendChild() instead of insertAdjacentElement('afterend') for correct DOM ordering in Plotly SVG
Checklist
ManualTestingProcess.md, and all tests related to this pull request pass.