diff --git a/CHANGELOG.md b/CHANGELOG.md index 0583fec45c..40f9e240fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Fix concurrent first access to lazily initialized graph object properties, which could raise `ValueError("Invalid value")` [[#3441](https://github.com/plotly/plotly.py/issues/3441)], with thanks to @hb1915 for the contribution! - Fix `px.sunburst`, `px.treemap` and `px.icicle` listing sectors in a different order on every run when `path` is used with a Polars DataFrame; sectors now follow their order of first appearance for all dataframe backends [[#5765](https://github.com/plotly/plotly.py/issues/5765)], with thanks to @Irahan2 for the contribution! +- Fix `px.imshow` with `animation_frame` and `facet_col_wrap` drawing the first facet of the second animation frame in the empty cell of an incomplete facet grid, where it stayed while the animation played [[#5783](https://github.com/plotly/plotly.py/pull/5783)], with thanks to @breken-ai for the contribution! ## [7.1.0] - 2026-09-15 diff --git a/plotly/express/_imshow.py b/plotly/express/_imshow.py index 576b114d51..dcd4716cc0 100644 --- a/plotly/express/_imshow.py +++ b/plotly/express/_imshow.py @@ -611,7 +611,10 @@ def imshow( nslices_facets = nslices_facet_row * nslices_facet_col frame_list = [] for index, trace in enumerate(traces): - if ((facet_col or facet_row) and index < nrows * ncols) or index == 0: + # Only the first animation frame goes into fig.data. With facet_col_wrap, + # nrows * ncols can exceed the number of facets, so compare with + # nslices_facets to leave the unused cells of the grid empty. + if ((facet_col or facet_row) and index < nslices_facets) or index == 0: # Calculate row and col position # index is ordered by (facet_row, facet_col) from itertools.product # When facet_col_wrap is used (and facet_row is None), traces are laid out diff --git a/tests/test_optional/test_px/test_imshow.py b/tests/test_optional/test_px/test_imshow.py index 3a0e230eb9..c62b0a97c7 100644 --- a/tests/test_optional/test_px/test_imshow.py +++ b/tests/test_optional/test_px/test_imshow.py @@ -469,6 +469,25 @@ def test_animation_and_facet(binary_string): assert len(fig.data) == img.shape[1] +@pytest.mark.parametrize("binary_string", [False, True]) +def test_animation_and_facet_col_wrap_incomplete_grid(binary_string): + # 5 facets wrapped in 2 columns leave the 6th cell of the 3x2 grid empty. + # That cell must stay empty, instead of showing the first facet of the + # second animation frame. + img = np.arange(2 * 5 * 3 * 4).reshape(2, 5, 3, 4).astype(np.uint8) + fig = px.imshow( + img, + animation_frame=0, + facet_col=1, + facet_col_wrap=2, + binary_string=binary_string, + ) + nslices_facet = img.shape[1] + assert len(fig.frames) == img.shape[0] + assert len(fig.data) == nslices_facet + assert [trace.name for trace in fig.data] == [str(i) for i in range(5)] + + @pytest.mark.parametrize("facet_row", [0, 1, 2, -1]) @pytest.mark.parametrize("binary_string", [False, True]) def test_facet_row(facet_row, binary_string):