@@ -1729,6 +1729,53 @@ def _dispatch_normalize_render(
17291729 if title is not None :
17301730 (ax_top if ax_top is not None else ax_diff ).set_title (title )
17311731
1732+ # Phase 13.34.DF FIX1 BUG-010: figure-level suptitle when auto_title=True
1733+ # for normalize= single-curve path. Same bug class as Phase 13.32 FIX1
1734+ # BUG-002 (faceted path); different dispatcher.
1735+ # Pattern copied from BUG-002 fix in _dispatch_faceted_render
1736+ # (drawer.py:2736-2774). Verifications applied:
1737+ # - dispatcher local vars: y_list/x_list (not y_expr/x_expr),
1738+ # passthrough (not plot_kwargs), selection is explicit arg.
1739+ # - group_by=None (single-curve normalize has no group_by param).
1740+ # - top=0.92 (matches BUG-002, not 0.88 from initial bug report).
1741+ # - _auto_title.py:97 return dict keys: 'main'/'sub' (no 'title').
1742+ _auto_title_val = passthrough .get ('auto_title' , False )
1743+ if _auto_title_val and not title :
1744+ try :
1745+ from .plots ._auto_title import (
1746+ parse_auto_title_parts , build_auto_title , resolve_auto_title
1747+ )
1748+ _at = resolve_auto_title (_auto_title_val )
1749+ if _at :
1750+ parts = parse_auto_title_parts (_at )
1751+ _y_str = (y_list if isinstance (y_list , str )
1752+ else (str (y_list [0 ]) if y_list else '' ))
1753+ _x_str = (x_list if isinstance (x_list , str )
1754+ else (str (x_list [0 ]) if x_list else '' ))
1755+ td = build_auto_title (
1756+ _x_str , _y_str ,
1757+ group_by = None , # no group_by in single-curve normalize
1758+ selection = selection ,
1759+ weights = None ,
1760+ parts = parts ,
1761+ )
1762+ _main = td .get ('main' , '' )
1763+ _sub = td .get ('sub' )
1764+ fig .suptitle (
1765+ f"{ _main } \n { _sub } " if _sub else _main ,
1766+ fontsize = get_style_value ("axes.titlesize" , 14 ),
1767+ )
1768+ plt .subplots_adjust (top = 0.92 )
1769+ except Exception :
1770+ # Failsafe — same defensive pattern as BUG-002 fix.
1771+ _y_str = (y_list if isinstance (y_list , str )
1772+ else f"[{ ',' .join (map (str , y_list ))} ]" )
1773+ _x_str = (x_list if isinstance (x_list , str )
1774+ else str (x_list [0 ] if x_list else '' ))
1775+ fig .suptitle (f"{ _y_str } vs { _x_str } " ,
1776+ fontsize = get_style_value ("axes.titlesize" , 14 ))
1777+ plt .subplots_adjust (top = 0.92 )
1778+
17321779 # --- 9. Build stats dict (M1 scope per v1.1 §7) ------------------------
17331780 # Drop profile_data from user-facing stats — internal-only.
17341781 # Provide normalize-specific keys per the AD-81 stats contract.
@@ -2031,6 +2078,46 @@ def _dispatch_normalize_grouped_render(
20312078 if title is not None :
20322079 (ax_top if ax_top is not None else ax_diff ).set_title (title )
20332080
2081+ # Phase 13.34.DF FIX1 BUG-010: figure-level suptitle for normalize+group_by.
2082+ # Same pattern as fix in _dispatch_normalize_render (single-curve) and
2083+ # in _dispatch_faceted_render (BUG-002). Difference: group_by IS a
2084+ # parameter of this dispatcher, so pass it to build_auto_title.
2085+ _auto_title_val = passthrough .get ('auto_title' , False )
2086+ if _auto_title_val and not title :
2087+ try :
2088+ from .plots ._auto_title import (
2089+ parse_auto_title_parts , build_auto_title , resolve_auto_title
2090+ )
2091+ _at = resolve_auto_title (_auto_title_val )
2092+ if _at :
2093+ parts = parse_auto_title_parts (_at )
2094+ _y_str = (y_list if isinstance (y_list , str )
2095+ else (str (y_list [0 ]) if y_list else '' ))
2096+ _x_str = (x_list if isinstance (x_list , str )
2097+ else (str (x_list [0 ]) if x_list else '' ))
2098+ td = build_auto_title (
2099+ _x_str , _y_str ,
2100+ group_by = group_by ,
2101+ selection = selection ,
2102+ weights = None ,
2103+ parts = parts ,
2104+ )
2105+ _main = td .get ('main' , '' )
2106+ _sub = td .get ('sub' )
2107+ fig .suptitle (
2108+ f"{ _main } \n { _sub } " if _sub else _main ,
2109+ fontsize = get_style_value ("axes.titlesize" , 14 ),
2110+ )
2111+ plt .subplots_adjust (top = 0.92 )
2112+ except Exception :
2113+ _y_str = (y_list if isinstance (y_list , str )
2114+ else f"[{ ',' .join (map (str , y_list ))} ]" )
2115+ _x_str = (x_list if isinstance (x_list , str )
2116+ else str (x_list [0 ] if x_list else '' ))
2117+ fig .suptitle (f"{ _y_str } vs { _x_str } " ,
2118+ fontsize = get_style_value ("axes.titlesize" , 14 ))
2119+ plt .subplots_adjust (top = 0.92 )
2120+
20342121 # --- 11. Build stats dict (M2 grouped contract) -----------------------
20352122 stats_dict : Dict [str , Any ] = {
20362123 'normalize_mode' : normalize if isinstance (normalize , str ) else 'callable' ,
@@ -2294,6 +2381,49 @@ def _dispatch_normalize_faceted_render(
22942381 if title is not None :
22952382 fig .suptitle (title )
22962383
2384+ # Phase 13.34.DF FIX1 BUG-010: figure-level suptitle for normalize+facet_by.
2385+ # Same pattern as fixes in _dispatch_normalize_render and
2386+ # _dispatch_normalize_grouped_render. Note: this dispatcher ALREADY
2387+ # had fig.suptitle(title) for explicit title (line above) — auto_title
2388+ # block runs only when title is None. group_by=None: each facet is its
2389+ # own panel, facet identity is in subplot titles via subplot_titles,
2390+ # not folded into the figure-level suptitle.
2391+ _auto_title_val = passthrough .get ('auto_title' , False )
2392+ if _auto_title_val and title is None :
2393+ try :
2394+ from .plots ._auto_title import (
2395+ parse_auto_title_parts , build_auto_title , resolve_auto_title
2396+ )
2397+ _at = resolve_auto_title (_auto_title_val )
2398+ if _at :
2399+ parts = parse_auto_title_parts (_at )
2400+ _y_str = (y_list if isinstance (y_list , str )
2401+ else (str (y_list [0 ]) if y_list else '' ))
2402+ _x_str = (x_list if isinstance (x_list , str )
2403+ else (str (x_list [0 ]) if x_list else '' ))
2404+ td = build_auto_title (
2405+ _x_str , _y_str ,
2406+ group_by = None , # facet_by is in subplot titles, not figure title
2407+ selection = selection ,
2408+ weights = None ,
2409+ parts = parts ,
2410+ )
2411+ _main = td .get ('main' , '' )
2412+ _sub = td .get ('sub' )
2413+ fig .suptitle (
2414+ f"{ _main } \n { _sub } " if _sub else _main ,
2415+ fontsize = get_style_value ("axes.titlesize" , 14 ),
2416+ )
2417+ plt .subplots_adjust (top = 0.92 )
2418+ except Exception :
2419+ _y_str = (y_list if isinstance (y_list , str )
2420+ else f"[{ ',' .join (map (str , y_list ))} ]" )
2421+ _x_str = (x_list if isinstance (x_list , str )
2422+ else str (x_list [0 ] if x_list else '' ))
2423+ fig .suptitle (f"{ _y_str } vs { _x_str } " ,
2424+ fontsize = get_style_value ("axes.titlesize" , 14 ))
2425+ plt .subplots_adjust (top = 0.92 )
2426+
22972427 # --- 8. Stats dict ----------------------------------------------------
22982428 stats_dict : Dict [str , Any ] = {
22992429 'normalize_mode' : normalize if isinstance (normalize , str ) else 'callable' ,
@@ -3866,7 +3996,12 @@ def profile(
38663996 'title' , 'xlabel' , 'ylabel' ,
38673997 'weights' , 'nan_policy' ,
38683998 'group_by' , 'facet_by' , 'facet_by_bins' , 'facet_by_quantiles' ,
3869- 'auto_title' , 'same' , 'return_data' , 'min_entries' ,
3999+ # Phase 13.34.DF FIX1 BUG-010: 'auto_title' REMOVED from _consumed.
4000+ # The 3 normalize dispatchers (_dispatch_normalize_render,
4001+ # _grouped_render, _faceted_render) now read auto_title from
4002+ # **passthrough to handle the figure-level suptitle, matching
4003+ # the Phase 13.32 FIX1 BUG-002 pattern in _dispatch_faceted_render.
4004+ 'same' , 'return_data' , 'min_entries' ,
38704005 'stats' , 'stat_fields' , 'top_k' , 'group_by_bins' ,
38714006 'group_by_quantiles' , 'sort_groups' , 'ax' , 'save' ,
38724007 'quantiles' , 'quantile_mode' , 'quantile_style' ,
0 commit comments