@@ -532,6 +532,82 @@ def test_legend_explicit_handles_labels_override_auto_collection():
532532 assert leg .get_texts ()[0 ].get_text () == "custom_label"
533533
534534
535+ def test_legend_ref_argument ():
536+ """Test using 'ref' to decouple legend location from content axes."""
537+ fig , axs = uplt .subplots (nrows = 2 , ncols = 2 )
538+ axs [0 , 0 ].plot ([], [], label = "line1" ) # Row 0
539+ axs [1 , 0 ].plot ([], [], label = "line2" ) # Row 1
540+
541+ # Place legend below Row 0 (axs[0, :]) using content from Row 1 (axs[1, :])
542+ leg = fig .legend (ax = axs [1 , :], ref = axs [0 , :], loc = "bottom" )
543+
544+ assert leg is not None
545+
546+ # Should be a single legend because span is inferred from ref
547+ assert not isinstance (leg , tuple )
548+
549+ texts = [t .get_text () for t in leg .get_texts ()]
550+ assert "line2" in texts
551+ assert "line1" not in texts
552+
553+
554+ def test_legend_ref_argument_no_ax ():
555+ """Test using 'ref' where 'ax' is implied to be 'ref'."""
556+ fig , axs = uplt .subplots (nrows = 1 , ncols = 1 )
557+ axs [0 ].plot ([], [], label = "line1" )
558+
559+ # ref provided, ax=None. Should behave like ax=ref.
560+ leg = fig .legend (ref = axs [0 ], loc = "bottom" )
561+ assert leg is not None
562+
563+ # Should be a single legend
564+ assert not isinstance (leg , tuple )
565+
566+ texts = [t .get_text () for t in leg .get_texts ()]
567+ assert "line1" in texts
568+
569+
570+ def test_ref_with_explicit_handles ():
571+ """Test using ref with explicit handles and labels."""
572+ fig , axs = uplt .subplots (ncols = 2 )
573+ h = axs [0 ].plot ([0 , 1 ], [0 , 1 ], label = "line" )
574+
575+ # Place legend below both axes (ref=axs) using explicit handle
576+ leg = fig .legend (handles = h , labels = ["explicit" ], ref = axs , loc = "bottom" )
577+
578+ assert leg is not None
579+ texts = [t .get_text () for t in leg .get_texts ()]
580+ assert texts == ["explicit" ]
581+
582+
583+ def test_ref_with_non_edge_location ():
584+ """Test using ref with an inset location (should not infer span)."""
585+ fig , axs = uplt .subplots (ncols = 2 )
586+ axs [0 ].plot ([0 , 1 ], label = "test" )
587+
588+ # ref=axs (list of 2).
589+ # 'upper left' is inset. Should fallback to first axis.
590+ leg = fig .legend (ref = axs , loc = "upper left" )
591+
592+ assert leg is not None
593+ if isinstance (leg , tuple ):
594+ leg = leg [0 ]
595+ # Should be associated with axs[0] (or a panel of it? Inset is child of axes)
596+ # leg.axes is the axes containing the legend. For inset, it's the parent axes?
597+ # No, legend itself is an artist. leg.axes should be axs[0].
598+ assert leg .axes is axs [0 ]
599+
600+
601+ def test_ref_with_single_axis ():
602+ """Test using ref with a single axis object."""
603+ fig , axs = uplt .subplots (ncols = 2 )
604+ axs [0 ].plot ([0 , 1 ], label = "line" )
605+
606+ # ref=axs[1]. loc='bottom'.
607+ leg = fig .legend (ref = axs [1 ], ax = axs [0 ], loc = "bottom" )
608+ assert leg is not None
609+
610+
535611def _decode_panel_span (panel_ax , axis ):
536612 ss = panel_ax .get_subplotspec ().get_topmost_subplotspec ()
537613 r1 , r2 , c1 , c2 = ss ._get_rows_columns ()
0 commit comments