2828 from matplotlib .offsetbox import OffsetBox , PackerBase
2929
3030 from plotnine import ggplot , guide_colorbar , guide_legend , theme
31+ from plotnine ._mpl .offsetbox import FlexibleAnchoredOffsetbox
3132 from plotnine .composition import Compose
3233 from plotnine .iapi import labels_view
3334 from plotnine .scales .scale import scale
@@ -358,9 +359,21 @@ def draw(self) -> Optional[OffsetBox]:
358359 for g in gdefs :
359360 g .theme .apply ()
360361
361- legends = assemble_legend_artists (
362- gdefs , guide_boxes , self .elements , figure
363- )
362+ # Rendering in a guide_area is simpler because we render all guides
363+ # together and at the center
364+ use_guide_area = (
365+ self ._owner ._guide_area
366+ if isinstance (self ._owner , Compose )
367+ else None
368+ ) is not None
369+ if use_guide_area :
370+ legends = assemble_guide_area_legend (
371+ guide_boxes , self .elements , figure
372+ )
373+ else :
374+ legends = assemble_legend_artists (
375+ gdefs , guide_boxes , self .elements , figure
376+ )
364377
365378 # Attach legend offsetboxes to the figure and register them
366379 for aob in legends .boxes :
@@ -410,42 +423,6 @@ def assemble_legend_artists(
410423 figure :
411424 The figure the offsetboxes will be anchored to.
412425 """
413- from matplotlib .font_manager import FontProperties
414- from matplotlib .offsetbox import HPacker , VPacker
415-
416- from .._mpl .offsetbox import FlexibleAnchoredOffsetbox
417-
418- # Combine all the guides into a single box
419- # The direction matters only when there is more than legend
420- lookup : dict [Orientation , type [PackerBase ]] = {
421- "horizontal" : HPacker ,
422- "vertical" : VPacker ,
423- }
424-
425- def _anchored_offset_box (boxes : list [PackerBase ]):
426- """
427- Put a group of guides into a single box for drawing
428- """
429- packer = lookup [elements .box ]
430-
431- box = packer (
432- children = boxes , # type: ignore
433- align = elements .box_just ,
434- pad = elements .box_margin ,
435- sep = elements .spacing ,
436- )
437-
438- return FlexibleAnchoredOffsetbox (
439- xy_loc = (0.5 , 0.5 ),
440- child = box ,
441- pad = 1 ,
442- frameon = False ,
443- prop = FontProperties (size = 1 , stretch = 0 ),
444- bbox_to_anchor = (0 , 0 ),
445- bbox_transform = figure .transFigure ,
446- borderpad = 0.0 ,
447- )
448-
449426 # Group together guides for each position
450427 groups : dict [
451428 tuple [Side , float ] | tuple [tuple [float , float ], tuple [float , float ]],
@@ -459,7 +436,7 @@ def _anchored_offset_box(boxes: list[PackerBase]):
459436
460437 # Create an anchoredoffsetbox for each group/position
461438 for (position , just ), group in groups .items ():
462- aob = _anchored_offset_box (group )
439+ aob = _anchored_offset_box (group , elements , figure )
463440 if isinstance (position , str ) and isinstance (just , (float , int )):
464441 setattr (legends , position , outside_legend (aob , just ))
465442 else :
@@ -470,6 +447,79 @@ def _anchored_offset_box(boxes: list[PackerBase]):
470447 return legends
471448
472449
450+ def assemble_guide_area_legend (
451+ boxes : list [PackerBase ],
452+ elements : GuidesElements ,
453+ figure : Figure ,
454+ ) -> legend_artists :
455+ """
456+ Pack collected guides into one centered legend for a `guide_area`
457+
458+ All trained guides are combined into a single AnchoredOffsetbox
459+ centered in panel coordinates, irrespective of their individual
460+ `legend_position` settings. Used when the rendering owner is a
461+ `Compose` whose `_guide_area` selects a host cell.
462+
463+ Parameters
464+ ----------
465+ boxes :
466+ The per-guide drawn boxes that will be packed together.
467+ elements :
468+ Theme-resolved layout elements (direction, box justification,
469+ margins, spacing).
470+ figure :
471+ The figure the offsetbox will be anchored to.
472+
473+ Returns
474+ -------
475+ out :
476+ A `legend_artists` whose only entry is a single inside
477+ legend at `(0.5, 0.5)`.
478+ """
479+ aob = _anchored_offset_box (boxes , elements , figure )
480+ legends = legend_artists ()
481+ legends .inside .append (inside_legend (aob , (0.5 , 0.5 ), (0.5 , 0.5 )))
482+ return legends
483+
484+
485+ def _anchored_offset_box (
486+ boxes : list [PackerBase ],
487+ elements : GuidesElements ,
488+ figure : Figure ,
489+ ) -> FlexibleAnchoredOffsetbox :
490+ """
491+ Pack a list of guide boxes into a single AnchoredOffsetbox
492+ """
493+ from matplotlib .font_manager import FontProperties
494+ from matplotlib .offsetbox import HPacker , VPacker
495+
496+ from .._mpl .offsetbox import FlexibleAnchoredOffsetbox
497+
498+ lookup : dict [Orientation , type [PackerBase ]] = {
499+ "horizontal" : HPacker ,
500+ "vertical" : VPacker ,
501+ }
502+ packer = lookup [elements .box ]
503+
504+ box = packer (
505+ children = boxes , # type: ignore
506+ align = elements .box_just ,
507+ pad = elements .box_margin ,
508+ sep = elements .spacing ,
509+ )
510+
511+ return FlexibleAnchoredOffsetbox (
512+ xy_loc = (0.5 , 0.5 ),
513+ child = box ,
514+ pad = 1 ,
515+ frameon = False ,
516+ prop = FontProperties (size = 1 , stretch = 0 ),
517+ bbox_to_anchor = (0 , 0 ),
518+ bbox_transform = figure .transFigure ,
519+ borderpad = 0.0 ,
520+ )
521+
522+
473523def _merge_guides (gdefs : Sequence [guide ]) -> list [guide ]:
474524 """
475525 Group guides by hash and fold each group
0 commit comments