354354 inside the axes. This can help them stand out on top of artists plotted
355355 inside the axes.
356356abcpad : float or unit-spec, default: :rc:`abc.pad`
357- The padding for the inner and outer titles and a-b-c labels.
357+ Horizontal offset to shift the a-b-c label position. Positive values move
358+ the label right, negative values move it left. This is separate from
359+ `abctitlepad`, which controls spacing between abc and title when co-located.
358360 %(units.pt)s
359361abc_kw, title_kw : dict-like, optional
360362 Additional settings used to update the a-b-c label and title
@@ -846,8 +848,10 @@ def __init__(self, *args, **kwargs):
846848 self ._auto_format = None # manipulated by wrapper functions
847849 self ._abc_border_kwargs = {}
848850 self ._abc_loc = None
849- self ._abc_pad = 0
850- self ._abc_title_pad = rc ["abc.titlepad" ]
851+ self ._abc_pad = 0 # User's horizontal offset for abc label (in points)
852+ self ._abc_title_pad = rc [
853+ "abc.titlepad"
854+ ] # Spacing between abc and title when co-located
851855 self ._title_above = rc ["title.above" ]
852856 self ._title_border_kwargs = {} # title border properties
853857 self ._title_loc = None
@@ -2986,6 +2990,8 @@ def _update_title(self, loc, title=None, **kwargs):
29862990 kw ["text" ] = title [self .number - 1 ]
29872991 else :
29882992 raise ValueError (f"Invalid title { title !r} . Must be string(s)." )
2993+ if any (key in kwargs for key in ("size" , "fontsize" )):
2994+ self ._title_dict [loc ]._ultraplot_manual_size = True
29892995 kw .update (kwargs )
29902996 self ._title_dict [loc ].update (kw )
29912997
@@ -2998,6 +3004,8 @@ def _update_title_position(self, renderer):
29983004 # NOTE: Critical to do this every time in case padding changes or
29993005 # we added or removed an a-b-c label in the same position as a title
30003006 width , height = self ._get_size_inches ()
3007+ if width <= 0 or height <= 0 :
3008+ return
30013009 x_pad = self ._title_pad / (72 * width )
30023010 y_pad = self ._title_pad / (72 * height )
30033011 for loc , obj in self ._title_dict .items ():
@@ -3010,7 +3018,8 @@ def _update_title_position(self, renderer):
30103018 # This is known matplotlib problem but especially annoying with top panels.
30113019 # NOTE: See axis.get_ticks_position for inspiration
30123020 pad = self ._title_pad
3013- abcpad = self ._abc_title_pad
3021+ # Horizontal separation between abc label and title when co-located (in points)
3022+ abc_title_sep_pts = self ._abc_title_pad
30143023 if self .xaxis .get_visible () and any (
30153024 tick .tick2line .get_visible () and not tick .label2 .get_visible ()
30163025 for tick in self .xaxis .majorTicks
@@ -3038,11 +3047,19 @@ def _update_title_position(self, renderer):
30383047
30393048 # Offset title away from a-b-c label
30403049 # NOTE: Title texts all use axes transform in x-direction
3041-
3042- # Offset title away from a-b-c label
3050+ # We need to convert padding values from points to axes coordinates (0-1 normalized)
30433051 atext , ttext = aobj .get_text (), tobj .get_text ()
30443052 awidth = twidth = 0
3045- pad = (abcpad / 72 ) / self ._get_size_inches ()[0 ]
3053+ width_inches = self ._get_size_inches ()[0 ]
3054+
3055+ # Convert abc-title separation from points to axes coordinates
3056+ # This is the spacing BETWEEN abc and title when they share the same location
3057+ abc_title_sep = (abc_title_sep_pts / 72 ) / width_inches
3058+
3059+ # Convert user's horizontal offset from points to axes coordinates
3060+ # This is the user-specified shift for the abc label position (via abcpad parameter)
3061+ abc_offset = (self ._abc_pad / 72 ) / width_inches
3062+
30463063 ha = aobj .get_ha ()
30473064
30483065 # Get dimensions of non-empty elements
@@ -3059,27 +3076,96 @@ def _update_title_position(self, renderer):
30593076 .width
30603077 )
30613078
3079+ # Shrink the title font if both texts share a location and would overflow
3080+ if (
3081+ atext
3082+ and ttext
3083+ and self ._abc_loc == self ._title_loc
3084+ and twidth > 0
3085+ and not getattr (tobj , "_ultraplot_manual_size" , False )
3086+ ):
3087+ scale = 1
3088+ base_x = tobj .get_position ()[0 ]
3089+ if ha == "left" :
3090+ available = 1 - (base_x + awidth + abc_title_sep )
3091+ if available < twidth and available > 0 :
3092+ scale = available / twidth
3093+ elif ha == "right" :
3094+ available = base_x + abc_offset - abc_title_sep - awidth
3095+ if available < twidth and available > 0 :
3096+ scale = available / twidth
3097+ elif ha == "center" :
3098+ # Conservative fit for centered titles sharing the abc location
3099+ left_room = base_x - 0.5 * (awidth + abc_title_sep )
3100+ right_room = 1 - (base_x + 0.5 * (awidth + abc_title_sep ))
3101+ max_room = min (left_room , right_room )
3102+ if max_room < twidth / 2 and max_room > 0 :
3103+ scale = (2 * max_room ) / twidth
3104+
3105+ if scale < 1 :
3106+ tobj .set_fontsize (tobj .get_fontsize () * scale )
3107+ twidth *= scale
3108+
30623109 # Calculate offsets based on alignment and content
30633110 aoffset = toffset = 0
30643111 if atext and ttext :
30653112 if ha == "left" :
3066- toffset = awidth + pad
3113+ toffset = awidth + abc_title_sep
30673114 elif ha == "right" :
3068- aoffset = - (twidth + pad )
3115+ aoffset = - (twidth + abc_title_sep )
30693116 elif ha == "center" :
3070- toffset = 0.5 * (awidth + pad )
3071- aoffset = - 0.5 * (twidth + pad )
3117+ toffset = 0.5 * (awidth + abc_title_sep )
3118+ aoffset = - 0.5 * (twidth + abc_title_sep )
30723119
30733120 # Apply positioning adjustments
3121+ # For abc label: apply offset from co-located title + user's horizontal offset
30743122 if atext :
30753123 aobj .set_x (
30763124 aobj .get_position ()[0 ]
30773125 + aoffset
3078- + ( self . _abc_pad / 72 ) / ( self . _get_size_inches ()[ 0 ] )
3126+ + abc_offset # User's horizontal shift (from abcpad parameter )
30793127 )
30803128 if ttext :
30813129 tobj .set_x (tobj .get_position ()[0 ] + toffset )
30823130
3131+ # Shrink title if it overlaps the abc label at a different location
3132+ if (
3133+ atext
3134+ and self ._abc_loc != self ._title_loc
3135+ and not getattr (
3136+ self ._title_dict [self ._title_loc ], "_ultraplot_manual_size" , False
3137+ )
3138+ ):
3139+ title_obj = self ._title_dict [self ._title_loc ]
3140+ title_text = title_obj .get_text ()
3141+ if title_text :
3142+ abc_bbox = aobj .get_window_extent (renderer ).transformed (
3143+ self .transAxes .inverted ()
3144+ )
3145+ title_bbox = title_obj .get_window_extent (renderer ).transformed (
3146+ self .transAxes .inverted ()
3147+ )
3148+ ax0 , ax1 = abc_bbox .x0 , abc_bbox .x1
3149+ tx0 , tx1 = title_bbox .x0 , title_bbox .x1
3150+ if tx0 < ax1 + abc_title_sep and tx1 > ax0 - abc_title_sep :
3151+ base_x = title_obj .get_position ()[0 ]
3152+ ha = title_obj .get_ha ()
3153+ max_width = 0
3154+ if ha == "left" :
3155+ if base_x <= ax0 - abc_title_sep :
3156+ max_width = (ax0 - abc_title_sep ) - base_x
3157+ elif ha == "right" :
3158+ if base_x >= ax1 + abc_title_sep :
3159+ max_width = base_x - (ax1 + abc_title_sep )
3160+ elif ha == "center" :
3161+ if base_x >= ax1 + abc_title_sep :
3162+ max_width = 2 * (base_x - (ax1 + abc_title_sep ))
3163+ elif base_x <= ax0 - abc_title_sep :
3164+ max_width = 2 * ((ax0 - abc_title_sep ) - base_x )
3165+ if 0 < max_width < title_bbox .width :
3166+ scale = max_width / title_bbox .width
3167+ title_obj .set_fontsize (title_obj .get_fontsize () * scale )
3168+
30833169 def _update_super_title (self , suptitle = None , ** kwargs ):
30843170 """
30853171 Update the figure super title.
0 commit comments