@@ -209,6 +209,9 @@ def __init__(self, **kwargs):
209209 self ._localaxes = [] # track all Axes
210210 self .subfigs = []
211211 self ._children = [] # All artists except SubFigure and Axes
212+ self ._children_by_layer = {"base" : self ._children , "overlay" : []}
213+ # Note: "patch" layer is added by Figure/SubFigure.__init__ after
214+ # self.patch is created, since FigureBase has no self.patch of its own.
212215 self .stale = True
213216 self .suppressComposite = None
214217 self .set (** kwargs )
@@ -238,14 +241,8 @@ def patches(self):
238241 def texts (self ):
239242 return _FigureArtistList (self , 'texts' , valid_types = Text )
240243
241- def _get_draw_artists (self , renderer ):
242- """Also runs apply_aspect"""
243- artists = self .get_children ()
244-
245- artists .remove (self .patch )
246- artists = sorted (
247- (artist for artist in artists if not artist .get_animated ()),
248- key = lambda artist : artist .get_zorder ())
244+ def _apply_aspects (self , renderer ):
245+ """Apply aspect ratios to all axes and children."""
249246 for ax in self ._localaxes :
250247 locator = ax .get_axes_locator ()
251248 ax .apply_aspect (locator (ax , renderer ) if locator else None )
@@ -255,8 +252,33 @@ def _get_draw_artists(self, renderer):
255252 locator = child .get_axes_locator ()
256253 child .apply_aspect (
257254 locator (child , renderer ) if locator else None )
255+
256+ def _get_draw_artists (self , renderer , layer ):
257+ artists = self .get_children (layer = layer )
258+ artists = sorted (
259+ (artist for artist in artists if not artist .get_animated ()),
260+ key = lambda artist : artist .get_zorder ())
258261 return artists
259262
263+ def _draw_layer (self , renderer , layer ):
264+ """
265+ Draw the specified layer of artists.
266+
267+ Parameters
268+ ----------
269+ renderer : `.RendererBase`
270+ layer : str
271+ The layer to draw.
272+ """
273+ artists = self ._get_draw_artists (renderer , layer = layer )
274+ if not artists :
275+ return
276+
277+ renderer .open_group (layer )
278+ mimage ._draw_list_compositing_images (
279+ renderer , self , artists , self .suppressComposite )
280+ renderer .close_group (layer )
281+
260282 def autofmt_xdate (
261283 self , bottom = 0.2 , rotation = 30 , ha = 'right' , which = 'major' ):
262284 """
@@ -302,17 +324,61 @@ def autofmt_xdate(
302324 self .subplots_adjust (bottom = bottom )
303325 self .stale = True
304326
305- def get_children (self ):
327+ def get_children (self , * , layer = None ):
306328 """Get a list of artists contained in the figure."""
307- return [self .patch ,
308- * self .artists ,
309- * self ._localaxes ,
310- * self .lines ,
311- * self .patches ,
312- * self .texts ,
313- * self .images ,
314- * self .legends ,
315- * self .subfigs ]
329+ patch_list = self ._children_by_layer .get ("patch" , [])
330+
331+ if layer is None :
332+ sources = [
333+ a for key , lst in self ._children_by_layer .items ()
334+ if key != "patch"
335+ for a in lst
336+ ]
337+ elif layer == "patch" :
338+ return list (patch_list )
339+ else :
340+ sources = self ._children_by_layer .get (layer , [])
341+
342+ artists = []
343+ lines = []
344+ patches = []
345+ texts = []
346+ images = []
347+ legends = []
348+
349+ for a in sources :
350+ if isinstance (a , mimage .FigureImage ):
351+ images .append (a )
352+ elif isinstance (a , mlegend .Legend ):
353+ legends .append (a )
354+ elif isinstance (a , Line2D ):
355+ lines .append (a )
356+ elif isinstance (a , Patch ):
357+ patches .append (a )
358+ elif isinstance (a , Text ):
359+ texts .append (a )
360+ else :
361+ artists .append (a )
362+
363+ prefix = patch_list if layer is None else []
364+
365+ if layer is None or layer == "base" :
366+ return [* prefix ,
367+ * artists ,
368+ * self ._localaxes ,
369+ * lines ,
370+ * patches ,
371+ * texts ,
372+ * images ,
373+ * legends ,
374+ * self .subfigs ]
375+ else :
376+ return [* artists ,
377+ * lines ,
378+ * patches ,
379+ * texts ,
380+ * images ,
381+ * legends ]
316382
317383 def get_figure (self , root = None ):
318384 """
@@ -585,7 +651,7 @@ def set_frameon(self, b):
585651
586652 frameon = property (get_frameon , set_frameon )
587653
588- def add_artist (self , artist , clip = False ):
654+ def add_artist (self , artist , clip = False , * , layer = None ):
589655 """
590656 Add an `.Artist` to the figure.
591657
@@ -601,15 +667,18 @@ def add_artist(self, artist, clip=False):
601667 ``figure.transSubfigure``.
602668 clip : bool, default: False
603669 Whether the added artist should be clipped by the figure patch.
670+ layer : str, default: None
671+ The layer to add the artist to. If None, the base layer is used.
604672
605673 Returns
606674 -------
607675 `~matplotlib.artist.Artist`
608676 The added artist.
609677 """
610678 artist .set_figure (self )
611- self ._children .append (artist )
612- artist ._remove_method = self ._children .remove
679+ target = self ._children_by_layer [layer or "base" ]
680+ target .append (artist )
681+ artist ._remove_method = target .remove
613682
614683 if not artist .is_transform_set ():
615684 artist .set_transform (self .transSubfigure )
@@ -1076,6 +1145,11 @@ def clear(self, keep_observers=False):
10761145 self .delaxes (ax ) # Remove ax from self._axstack.
10771146
10781147 self ._children = []
1148+ self ._children_by_layer = {
1149+ "patch" : [self .patch ],
1150+ "base" : self ._children ,
1151+ "overlay" : [],
1152+ }
10791153 self .subplotpars .reset ()
10801154 if not keep_observers :
10811155 self ._axobservers = cbook .CallbackRegistry ()
@@ -2385,6 +2459,9 @@ def __init__(self, parent, subplotspec, *,
23852459 in_layout = False , transform = self .transSubfigure )
23862460 self ._set_artist_props (self .patch )
23872461 self .patch .set_antialiased (False )
2462+ # Now that self.patch exists, register it as its own layer so that
2463+ # _draw_layer(renderer, "patch") draws it first before "base".
2464+ self ._children_by_layer ["patch" ] = [self .patch ]
23882465
23892466 @property
23902467 def canvas (self ):
@@ -2494,13 +2571,15 @@ def draw(self, renderer):
24942571 if not self .get_visible ():
24952572 return
24962573
2497- artists = self ._get_draw_artists (renderer )
2498-
24992574 try :
25002575 renderer .open_group ('subfigure' , gid = self .get_gid ())
2501- self .patch .draw (renderer )
2502- mimage ._draw_list_compositing_images (
2503- renderer , self , artists , self .get_figure (root = True ).suppressComposite )
2576+ self ._apply_aspects (renderer )
2577+ # Patch layer: always first (figure background)
2578+ self ._draw_layer (renderer , "patch" )
2579+ # Pass 1: base layer
2580+ self ._draw_layer (renderer , "base" )
2581+ # Pass 2: overlay layer
2582+ self ._draw_layer (renderer , "overlay" )
25042583 renderer .close_group ('subfigure' )
25052584
25062585 finally :
@@ -2731,6 +2810,9 @@ def __init__(self,
27312810 in_layout = False )
27322811 self ._set_artist_props (self .patch )
27332812 self .patch .set_antialiased (False )
2813+ # even if this line is removed it not cause any error
2814+ # as self.clear() below rebuilds the dictionary
2815+ self ._children_by_layer ["patch" ] = [self .patch ]
27342816
27352817 self ._set_base_canvas ()
27362818
@@ -3348,7 +3430,6 @@ def draw(self, renderer):
33483430
33493431 with self ._render_lock :
33503432
3351- artists = self ._get_draw_artists (renderer )
33523433 try :
33533434 renderer .open_group ('figure' , gid = self .get_gid ())
33543435 if self .axes and self .get_layout_engine () is not None :
@@ -3358,9 +3439,15 @@ def draw(self, renderer):
33583439 pass
33593440 # ValueError can occur when resizing a window.
33603441
3361- self .patch .draw (renderer )
3362- mimage ._draw_list_compositing_images (
3363- renderer , self , artists , self .suppressComposite )
3442+ self ._apply_aspects (renderer )
3443+
3444+ # Patch layer: always first (figure background)
3445+ self ._draw_layer (renderer , "patch" )
3446+ # Pass 1: base layer
3447+ self ._draw_layer (renderer , "base" )
3448+
3449+ # Pass 2: overlay layer
3450+ self ._draw_layer (renderer , "overlay" )
33643451
33653452 renderer .close_group ('figure' )
33663453 finally :
0 commit comments