diff --git a/CHANGELOG.md b/CHANGELOG.md index 1632f922032..677b518b5b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Fix `mpl_to_plotly` conversion of matplotlib contour lines and line collections: close contour rings ending with `Z` codes, support dash styles, convert date x-axes, separate disjoint subpaths with `None` separators, hide line collection traces from the legend by default, and group consecutive same-style lines into single traces [[#5770](https://github.com/plotly/plotly.py/pull/5770)], with thanks to @robertoffmoura for the contribution! - Fix `plotly.io.write_images` ignoring `layout.width`/`layout.height` (and the template's) and exporting every figure at the default 700x500 size; it now uses the same width/height priority as `write_image` and `to_image` [[#5784](https://github.com/plotly/plotly.py/pull/5784)], with thanks to @breken-ai for the contribution! - Fix color utilities mutating the list of colors passed to them [[#5836](https://github.com/plotly/plotly.py/pull/5836)], with thanks to @Enesuygurs for the contribution! +- Fix `make_subplots` silently overlapping subplots when a cell covered by a `colspan` or `rowspan` also holds a subplot in `specs`; a `ValueError` naming the cell is now raised instead [[#5858](https://github.com/plotly/plotly.py/pull/5858)], with thanks to @LukeTheoJohnson for the contribution! ## [7.1.0] - 2026-09-15 diff --git a/plotly/_subplots.py b/plotly/_subplots.py index 7f9b79ebf51..1fa85fd1c93 100644 --- a/plotly/_subplots.py +++ b/plotly/_subplots.py @@ -681,6 +681,15 @@ def _check_hv_spacing(dimsize, spacing, name, dimvarname, dimname): "Some 'rowspan' value is too large for this subplot grid." ) + for rr in range(r, r_spanned + 1): + for cc in range(c, c_spanned + 1): + if (rr, cc) != (r, c) and specs[rr][cc] is not None: + raise ValueError( + f"The subplot at grid position ({r + 1}, {c + 1}) " + f"spans the cell at ({rr + 1}, {cc + 1}), so " + f"specs[{rr}][{cc}] must be None." + ) + # Get x domain using grid and colspan x_s = grid[r][c][0] + spec["l"] diff --git a/tests/test_core/test_subplots/test_make_subplots.py b/tests/test_core/test_subplots/test_make_subplots.py index 52503b0ddef..0bb5b866400 100644 --- a/tests/test_core/test_subplots/test_make_subplots.py +++ b/tests/test_core/test_subplots/test_make_subplots.py @@ -81,6 +81,16 @@ def test_specs_rowspan_too_big(self): with self.assertRaises(Exception): tls.make_subplots(rows=3, specs=[[{}], [None], [{"rowspan": 2}]]) + def test_specs_colspan_overlaps_subplot(self): + with pytest.raises(ValueError, match=r"specs\[0\]\[1\] must be None"): + subplots.make_subplots(cols=3, specs=[[{"colspan": 2}, {}, None]]) + + def test_specs_rowspan_overlaps_subplot(self): + with pytest.raises(ValueError, match=r"specs\[1\]\[0\] must be None"): + subplots.make_subplots( + rows=2, cols=2, specs=[[{"rowspan": 2}, {}], [{}, {}]] + ) + def test_insets_wrong_type(self): with self.assertRaises(Exception): tls.make_subplots(insets="not going to work")