Repository: matplotlib/matplotlib

Issue:
[Bug]: subfigure artists not drawn interactively
### Bug summary

When artists are added to a subfigure in interactive mode, they do not appear until I force a draw by resizing the window.

### Code for reproduction

```Python
$ ipython --matplotlib=qt
Python 3.12.3 | packaged by conda-forge | (main, Apr 15 2024, 18:38:13) [GCC 12.3.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.24.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import matplotlib.pyplot as plt

In [2]: fig = plt.figure()

In [3]: sfig1, sfig2 = fig.subfigures(ncols=2)

In [4]: sfig2.suptitle("My Title")
Out[4]: Text(0.5, 0.98, 'My Title')

In [5]: ax = sfig1.subplots()

In [6]: ax.plot([1, 3, 2])
Out[6]: [<matplotlib.lines.Line2D at 0x73a220908ce0>]
```


### Actual outcome

Apparently empty figure
![image](https://github.com/matplotlib/matplotlib/assets/10599679/c6f531b3-cdb1-4af3-89ad-7f8d24c322b5)


### Expected outcome

If I resize the window, the artists appear
![image](https://github.com/matplotlib/matplotlib/assets/10599679/223737ec-3f78-4d58-9e3c-c2bff4833a85)


### Additional information

_No response_

### Operating system

Ubuntu

### Matplotlib Version

`main`

### Matplotlib Backend

QtAgg and TkAgg

### Python version

3.12.3

### Jupyter version

_No response_

### Installation

git checkout


Discussion:
Very likely the issue is that `obj.stale` is not propogating up from the sub-figure to the parent.
Huh. I never use interactive mode so didn't test that! Seems a pretty big oversight.  Thanks for finding. 
```diff

diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py
index 9139b2ed26..9f764cc233 100644
--- a/lib/matplotlib/figure.py
+++ b/lib/matplotlib/figure.py
@@ -1636,6 +1636,8 @@ default: %(va)s
         sf = SubFigure(self, subplotspec, **kwargs)
         self.subfigs += [sf]
         sf._remove_method = self.subfigs.remove
+        sf.stale_callback = _stale_figure_callback
+        self.stale = True
         return sf
 
     def sca(self, a):

```

is the fix.  I don't have time right now to add a test etc.  I'll try to get to it later today but will not be at all angry if someone takes over.



Patch:
diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py
index 9139b2ed262f..9f764cc2332f 100644
--- a/lib/matplotlib/figure.py
+++ b/lib/matplotlib/figure.py
@@ -1636,6 +1636,8 @@ def add_subfigure(self, subplotspec, **kwargs):
         sf = SubFigure(self, subplotspec, **kwargs)
         self.subfigs += [sf]
         sf._remove_method = self.subfigs.remove
+        sf.stale_callback = _stale_figure_callback
+        self.stale = True
         return sf
 
     def sca(self, a):


Test patch:
diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py
index e8edcf61815d..5a8894b10496 100644
--- a/lib/matplotlib/tests/test_figure.py
+++ b/lib/matplotlib/tests/test_figure.py
@@ -1741,3 +1741,27 @@ def test_subfigure_row_order():
     sf_arr = fig.subfigures(4, 3)
     for a, b in zip(sf_arr.ravel(), fig.subfigs):
         assert a is b
+
+
+def test_subfigure_stale_propagation():
+    fig = plt.figure()
+
+    fig.draw_without_rendering()
+    assert not fig.stale
+
+    sfig1 = fig.subfigures()
+    assert fig.stale
+
+    fig.draw_without_rendering()
+    assert not fig.stale
+    assert not sfig1.stale
+
+    sfig2 = sfig1.subfigures()
+    assert fig.stale
+
+    fig.draw_without_rendering()
+    assert not fig.stale
+    assert not sfig2.stale
+
+    sfig2.stale = True
+    assert fig.stale