Skip to content

Raise an error when make_subplots padding doesn't fit in its cell - #5826

Open
shivenaggarwal wants to merge 1 commit into
plotly:mainfrom
shivenaggarwal:subplots-padding-validation
Open

shivenaggarwal wants to merge 1 commit into
plotly:mainfrom
shivenaggarwal:subplots-padding-validation

Conversation

@shivenaggarwal

Copy link
Copy Markdown

Link to issue

Closes #4144

Description of change

The l/r and b/t padding in make_subplots specs is in normalized plot coordinates. When it adds up to more than the width or height of a subplot's cell, the subplot gets an empty or inverted domain. plotly.js replaces that domain with [0, 1], so the subplot is drawn over the whole figure. In the issue, each of the 8 rows is only ~0.092 tall once the default vertical_spacing is taken out, so b + t = 0.1 doesn't fit and all 8 subplots overlap.

As suggested in the issue, make_subplots now validates the padding. For each subplot it computes the extent of its cell once, then checks l + r and b + t against it and applies the padding. This is the same extent the domain is computed from, so rowspan/colspan, row_heights/column_widths, start_cell and the spacing are all accounted for. If the padding leaves no room, make_subplots raises a ValueError. Padding that fits gives the same domains as before, and the existing exact-domain tests pass unchanged.

Demo

from plotly.subplots import make_subplots

fig = make_subplots(
    rows=8,
    row_heights=[0.1] * 8,
    specs=[[{"b": 0.05, "t": 0.05}] for _ in range(8)],
)

Before: there's no error. Every y domain is inverted, from [0.9578, 0.95] for yaxis down to [0.05, 0.0422] for yaxis8. plotly.js replaces all eight with [0, 1] (checked with fig.full_figure_for_development()), so the subplots are drawn on top of each other.

After:

ValueError:
The 'b' and 't' padding of the subplot at grid position (1, 1) must add up to less than 0.0921875, the height of its cell in normalized plot coordinates.
    Received 'b': 0.05, 't': 0.05

With vertical_spacing=0, each cell is 0.125 tall, so the same padding fits and each subplot keeps its own domain.

Testing strategy

Added tests to tests/test_core/test_subplots/test_make_subplots.py:

  • test_make_subplots_padding_error covers b/t and l/r padding larger than the cell, padding that exactly fills the cell, uneven row_heights with both start_cell values, colspan/rowspan padding larger than the cells spanned, and a domain subplot. Each case checks the grid position and limit in the message.
  • test_make_subplots_padding_within_cell checks that padding just under the limit still gives the expected domains. So does padding on spanning subplots that is larger than one cell but smaller than the span.
  • test_make_subplots_padding_issue_4144 covers the issue's example, with and without vertical_spacing.

test_specs_rounding_rounds_down (from #4153) used the example from #4124 (b + t = 0.2 for cells ~0.092 tall). It only checked that no domain went above 1, but those domains were inverted. That input now raises, so the test expects the error and is renamed test_specs_padding_larger_than_cell.

The new error tests fail on main. These all pass locally:

  • pytest tests/test_core
  • tests/test_optional/test_px
  • tests/test_optional/test_figure_factory
  • ruff format --check .

Additional information (optional)

  • Padding that exactly fills the cell is rejected too, because plotly.js also replaces a zero-size domain with [0, 1]. A cell with no padding is never flagged, so vertical_spacing=1 / (rows - 1) behaves as before. make_subplots allows that spacing, even though it leaves the rows empty.
  • plotly.js also replaces axis domains narrower than 1/4096 with [0, 1]. Padding that leaves less than that still passes this check. I kept the rule as "the padding must leave room" rather than copying that constant from plotly.js, but I can add it if you prefer.
  • The existing rounding of y domains just outside [0, 1] is unchanged. It is still needed for floating-point noise: make_subplots(rows=8) has a top edge of 1.0000000000000002.
  • I didn't change the docstrings. I can add a note that l/r/t/b are in normalized plot coordinates and must fit in the cell, if you'd like.

Guidelines

The l/r and b/t padding in the make_subplots specs is in normalized plot
coordinates, so it is easy to pass more padding than a cell is wide or
tall. For example, 8 rows are only ~0.092 tall each once the default
vertical_spacing is taken out, so 0.1 of padding doesn't fit. The
subplot then gets an empty or inverted domain, which plotly.js replaces
with [0, 1], so each such subplot is drawn over the whole figure and
they all overlap.

make_subplots now checks the padding of each subplot against the extent
of its cell, the same extent its domain is computed from, so rowspan,
colspan, row_heights, column_widths and the spacing are taken into
account. If the padding leaves no room for the subplot, a ValueError
gives the subplot's grid position, the padding values and the cell size
they must add up to less than. Padding that fits gives the same domains
as before.

test_specs_rounding_rounds_down used the example from plotly#4124, whose
padding of 0.2 is more than twice the height of its cells, and only
checked that no domain went above 1. That input now raises, so the test
checks for the error instead.
@shivenaggarwal
shivenaggarwal force-pushed the subplots-padding-validation branch from b09fb5a to 8a58de1 Compare October 19, 2026 03:23
@camdecoster

Copy link
Copy Markdown
Contributor

Thanks for the PR! Could you please explain how this PR will help you in your own real-world project or day-to-day work?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Certain padding values cause plots to overtake all other subplots

2 participants