Skip to content

[FEATURE]: pixel-based spacing for subplot domains (so titles can't overlap the plot above) #7835

@SharadhNaidu

Description

@SharadhNaidu

Description

Over in plotly.py there's a long-standing papercut with make_subplots: if you set a small vertical_spacing and also pass subplot_titles, the titles overlap the subplot above them (plotly/plotly.py#5606).

The underlying limitation looks like it's in plotly.js. Subplot titles are paper-referenced annotations pinned to the top of each subplot's domain. Their height is a pixel quantity (it scales with font size), but vertical_spacing is expressed as a plot fraction and ends up as the gap between adjacent domain bands. So whether a title fits depends on the rendered pixel height of the plot area — which isn't known until draw time, and changes when the containing div is resized.

That's why this can't be solved cleanly on the plotly.py side: when make_subplots builds the figure it doesn't know the final height (it's often set later, or left responsive), so any fraction it picks for the gap is wrong at some height. The reporter flagged this directly in the original issue:

"axis.domain only supports units of plot fraction, not sure if there is a way to shift it by a certain number of pixels."

And @emilykl's steer on the plotly.py issue was:

"in order to support this fix, Plotly.js will need to be modified to support pixel offsets for domain, or something along those lines... please open an issue in Plotly.js linking to this issue and proposing a change to the API."

So this is that issue. Since the final subplot layout is resolved at render time, this looks like the right layer to sort it out. There are two shapes this could take — I've laid out both below.

Approach A — let domain spacing carry a pixel component (resolved at draw time)

Allow the gap between subplot domains (or the domain bounds themselves) to be specified partly in pixels, resolved against the real plot-area size during layout, the same way margin already is. Conceptually it's domain = fraction ± pixels, with the pixel part applied when the actual height is known. This is general-purpose — anyone who needs a fixed-pixel gutter between subplots benefits, not just titled ones — and it's the most direct reading of "pixel offsets for domain".

Approach B — make subplot titles reserve their own space via the existing automargin machinery

Instead of a new public attribute, let subplot titles participate in the draw-time margin/automargin pass so each title reserves a pixel-sized band and pushes the neighbouring domain down at render time. No new config for users; the overlap just stops happening. The trade-off is that it shifts existing baselines automatically and is specific to titles rather than a general spacing primitive.

Approach A is more general-purpose; Approach B has a smaller public API surface and reuses machinery that already exists. I'm not trying to prescribe an API here — the goal is to work out whether pixel-aware domain spacing, title-aware layout reservation, or some other render-time mechanism fits the existing layout model best. Happy with whichever direction the team prefers.

Why should this feature be added?

This is a common configuration (compact stacked subplots with titles — dashboards, multi-metric time series) and today it produces visibly broken output with no real workaround from the Python API. plotly.py can't fix it without this, because the decision depends on the rendered pixel height. A pixel-aware spacing primitive would close plotly/plotly.py#5606 and, more broadly, give anyone a way to reserve fixed-pixel space between subplots regardless of figure height.

Mocks/Designs

Minimal repro (Python, but the overlap is produced by plotly.js at render time):

from plotly.subplots import make_subplots
import plotly.graph_objects as go
import random

fig = make_subplots(
    rows=6, cols=1,
    shared_xaxes=True,
    vertical_spacing=0.02,
    row_heights=[0.35, 0.13, 0.13, 0.13, 0.13, 0.13],
    subplot_titles=['Heart Rate Variability', 'Walking', 'Cycling', 'Hiking', 'Other', 'Pilates'],
)
for i in range(6):
    fig.add_trace(
        go.Scatter(x=[0, 1, 2], y=[random.randint(0, 10) for _ in range(3)]),
        row=i + 1, col=1,
    )
fig.show()

Each title (a paper annotation at the top of its domain) grows upward into the small fraction gap above it. When that gap is small relative to the rendered pixel height of the title, the title overlaps the subplot above — and because the gap is a fraction while the title height is fixed pixels, whether it overlaps depends on the figure's rendered height. Screenshot of the overlap is on the linked issue: plotly/plotly.py#5606.

Sketch of the Approach A API (illustrative only):

// gap between subplot domains, resolved at draw time
domain: { ..., pad: 30 }     // 30px reserved, independent of figure height

Notes

plotly.js already does pixel-aware, draw-time layout in two places, which is why I think either approach is feasible without new machinery:

  • automargin grows the figure margins for long tick labels at render time, once pixel sizes are known.
  • annotation yshift / xshift are pixel offsets that already coexist with fractional/paper positioning.

Approach B reuses the first directly; Approach A is the same idea generalised to domain spacing.

Linked issue: plotly/plotly.py#5606. Happy to take a crack at the implementation once there's agreement on which direction to go.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions