From 44ae174df69e3d7502674e34fc0c226c8a993716 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Mon, 23 Mar 2026 16:40:12 +1000 Subject: [PATCH 1/3] Add title_kw to legend and fix titlefontweight/titlefontcolor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The legend API now accepts a title_kw dict for passing arbitrary Text properties to the legend title (e.g. title_kw={'style': 'italic'}), consistent with the existing handle_kw parameter for handle styling. This change also fixes a pre-existing bug where titlefontweight and titlefontcolor were accepted by the API and silently stored but never actually applied to the legend title Text object — they now take effect as expected. --- ultraplot/axes/base.py | 6 ++++++ ultraplot/legend.py | 27 +++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/ultraplot/axes/base.py b/ultraplot/axes/base.py index 8ad5753d8..8c44c92be 100644 --- a/ultraplot/axes/base.py +++ b/ultraplot/axes/base.py @@ -633,6 +633,10 @@ titlefontsize, titlefontweight, titlefontcolor : optional The font size, weight, and color for the legend title. Font size is interpreted by `~ultraplot.utils.units`. The default size is `fontsize`. +title_kw : dict-like, optional + Additional properties passed to the legend title text object, e.g. + ``title_kw={'style': 'italic'}``. This can be used to set any + `~matplotlib.text.Text` property on the legend title. borderpad, borderaxespad, handlelength, handleheight, handletextpad, labelspacing, columnspacing : unit-spec, optional Various matplotlib `~matplotlib.axes.Axes.legend` spacing arguments. %(units.em)s @@ -1231,6 +1235,7 @@ def _add_legend( titlefontsize=None, titlefontweight=None, titlefontcolor=None, + title_kw=None, handle_kw=None, handler_map=None, span: Optional[Union[int, Tuple[int, int]]] = None, @@ -1263,6 +1268,7 @@ def _add_legend( titlefontsize=titlefontsize, titlefontweight=titlefontweight, titlefontcolor=titlefontcolor, + title_kw=title_kw, handle_kw=handle_kw, handler_map=handler_map, span=span, diff --git a/ultraplot/legend.py b/ultraplot/legend.py index c8c5c579d..f11fd61b7 100644 --- a/ultraplot/legend.py +++ b/ultraplot/legend.py @@ -1318,6 +1318,7 @@ class _LegendInputs: titlefontsize: float titlefontweight: Any titlefontcolor: Any + title_kw: Any handle_kw: Any handler_map: Any span: Optional[Union[int, Tuple[int, int]]] @@ -1794,6 +1795,7 @@ def _resolve_inputs( titlefontsize=None, titlefontweight=None, titlefontcolor=None, + title_kw=None, handle_kw=None, handler_map=None, span: Optional[Union[int, Tuple[int, int]]] = None, @@ -1844,6 +1846,7 @@ def _resolve_inputs( titlefontsize=titlefontsize, titlefontweight=titlefontweight, titlefontcolor=titlefontcolor, + title_kw=title_kw, handle_kw=handle_kw, handler_map=handler_map, span=span, @@ -1896,6 +1899,9 @@ def _resolve_style_kwargs( lax, fontcolor, fontweight, + titlefontweight, + titlefontcolor, + title_kw, handle_kw, kwargs, ): @@ -1908,10 +1914,16 @@ def _resolve_style_kwargs( kw_text["color"] = fontcolor if fontweight is not None: kw_text["weight"] = fontweight + kw_title = {} + if titlefontweight is not None: + kw_title["weight"] = titlefontweight + if titlefontcolor is not None: + kw_title["color"] = titlefontcolor + kw_title.update(title_kw or {}) kw_handle = _pop_props(kwargs, "line") kw_handle.setdefault("solid_capstyle", "butt") kw_handle.update(handle_kw or {}) - return kw_frame, kw_text, kw_handle, kwargs + return kw_frame, kw_text, kw_title, kw_handle, kwargs def _build_legends( self, @@ -1959,12 +1971,14 @@ def _build_legends( lax.add_artist(obj) return objs - def _apply_handle_styles(self, objs, *, kw_text, kw_handle): + def _apply_handle_styles(self, objs, *, kw_text, kw_title, kw_handle): """ Apply per-handle styling overrides to legend artists. """ for obj in objs: obj.set_clip_on(False) + if kw_title: + obj.get_title().update(kw_title) box = getattr(obj, "_legend_handle_box", None) for child in guides._iter_children(box): if isinstance(child, mtext.Text): @@ -2015,6 +2029,7 @@ def add( titlefontsize=None, titlefontweight=None, titlefontcolor=None, + title_kw=None, handle_kw=None, handler_map=None, span: Optional[Union[int, Tuple[int, int]]] = None, @@ -2050,6 +2065,7 @@ def add( titlefontsize=titlefontsize, titlefontweight=titlefontweight, titlefontcolor=titlefontcolor, + title_kw=title_kw, handle_kw=handle_kw, handler_map=handler_map, span=span, @@ -2062,10 +2078,13 @@ def add( lax, kwargs = self._resolve_axes_layout(inputs) - kw_frame, kw_text, kw_handle, kwargs = self._resolve_style_kwargs( + kw_frame, kw_text, kw_title, kw_handle, kwargs = self._resolve_style_kwargs( lax=lax, fontcolor=inputs.fontcolor, fontweight=inputs.fontweight, + titlefontweight=inputs.titlefontweight, + titlefontcolor=inputs.titlefontcolor, + title_kw=inputs.title_kw, handle_kw=inputs.handle_kw, kwargs=kwargs, ) @@ -2079,5 +2098,5 @@ def add( kwargs=kwargs, ) - self._apply_handle_styles(objs, kw_text=kw_text, kw_handle=kw_handle) + self._apply_handle_styles(objs, kw_text=kw_text, kw_title=kw_title, kw_handle=kw_handle) return self._finalize(objs, loc=inputs.loc, align=inputs.align) From 1caf687cb58a3fa9032d2abce2234b292158b559 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Mon, 23 Mar 2026 16:53:38 +1000 Subject: [PATCH 2/3] style: apply black formatting --- ultraplot/legend.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ultraplot/legend.py b/ultraplot/legend.py index f11fd61b7..db71338f3 100644 --- a/ultraplot/legend.py +++ b/ultraplot/legend.py @@ -2098,5 +2098,7 @@ def add( kwargs=kwargs, ) - self._apply_handle_styles(objs, kw_text=kw_text, kw_title=kw_title, kw_handle=kw_handle) + self._apply_handle_styles( + objs, kw_text=kw_text, kw_title=kw_title, kw_handle=kw_handle + ) return self._finalize(objs, loc=inputs.loc, align=inputs.align) From 0ada2aec2947b4c5c90eafd9a749cdf15f38e33d Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Mon, 23 Mar 2026 17:15:35 +1000 Subject: [PATCH 3/3] test: add tests for title_kw, titlefontweight, and titlefontcolor in legend --- ultraplot/tests/test_legend.py | 47 ++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/ultraplot/tests/test_legend.py b/ultraplot/tests/test_legend.py index a8ebb4455..8db7c8ac3 100644 --- a/ultraplot/tests/test_legend.py +++ b/ultraplot/tests/test_legend.py @@ -1437,3 +1437,50 @@ def legend(self, *args, **kwargs): leg = fig.legend(ref=proxy, loc="upper left", rows=1) assert leg is not None + + +def test_legend_title_kw(): + """ + Test that title_kw passes arbitrary Text properties to the legend title. + """ + fig, ax = uplt.subplots() + ax.plot([0, 1], label="data") + leg = ax.legend(title="My Legend", title_kw={"style": "italic", "color": "red"}) + title = leg.get_title() + assert title.get_style() == "italic" + assert title.get_color() == "red" + + +def test_legend_titlefontweight(): + """ + Test that titlefontweight is applied to the legend title text object. + """ + fig, ax = uplt.subplots() + ax.plot([0, 1], label="data") + leg = ax.legend(title="My Legend", titlefontweight="bold") + assert leg.get_title().get_fontweight() == "bold" + + +def test_legend_titlefontcolor(): + """ + Test that titlefontcolor is applied to the legend title text object. + """ + fig, ax = uplt.subplots() + ax.plot([0, 1], label="data") + leg = ax.legend(title="My Legend", titlefontcolor="blue") + assert leg.get_title().get_color() == "blue" + + +def test_legend_title_kw_overrides_titlefont(): + """ + Test that title_kw values override titlefontweight/titlefontcolor when both + are specified, since title_kw is merged last. + """ + fig, ax = uplt.subplots() + ax.plot([0, 1], label="data") + leg = ax.legend( + title="My Legend", + titlefontcolor="blue", + title_kw={"color": "green"}, + ) + assert leg.get_title().get_color() == "green"