Skip to content

Commit a0a9296

Browse files
petercorkeclaude
andauthored
feat(base): set_window_title, set the title of a figure's window if it has one (#263)
## Summary Adds `spatialmath.base.set_window_title(fig, title)`, which sets the title of the window that contains a figure, if it has one. A figure has a window only if it is managed by pyplot and the backend provides one. On a headless backend (`Agg`), or for a figure made directly with `matplotlib.figure.Figure`, there is no window: `canvas.manager` is `None`, or the manager has no `set_window_title`. The function then does nothing and returns `False`, so callers do not need a `try`/`except` or a guard. ## Why The Robotics Toolbox for Python and the Machine Vision Toolbox for Python each set window titles, in several places, with different guards: RTB reached into `figure.canvas.manager` directly in some places, and MVTB has a helper on `plt.gcf()` with a bare `except:` as well as direct calls. Both already depend on `spatialmath.base.graphics` for `axes_logic` and `plotvol2`, so this is the natural home for one copy. ## Changes - `spatialmath/base/graphics.py`: `set_window_title`, and a stub that raises `NotImplementedError` without Matplotlib, as for the other plotting functions. - `spatialmath/base/__init__.py`: lazily exported with the other graphics names. - `tests/base/test_graphics.py`: a managed figure, a pyplot figure, a figure without a manager, a manager without the method. No change to any existing behaviour. ## Test plan - [x] `pytest tests` (384 passed, 3 skipped) Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent 8c4f022 commit a0a9296

3 files changed

Lines changed: 69 additions & 0 deletions

File tree

‎spatialmath/base/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"plot_cone": "graphics",
4343
"plot_cuboid": "graphics",
4444
"axes_logic": "graphics",
45+
"set_window_title": "graphics",
4546
"plotvol2": "graphics",
4647
"plotvol3": "graphics",
4748
"expand_dims": "graphics",
@@ -391,6 +392,7 @@ def __getattr__(name):
391392
"plot_cone",
392393
"plot_cuboid",
393394
"axes_logic",
395+
"set_window_title",
394396
"expand_dims",
395397
"isnotebook",
396398
# spatial.base.numeric

‎spatialmath/base/graphics.py‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1791,6 +1791,37 @@ def expand_dims(dim: ArrayLike = None, nd: int = 2) -> NDArray:
17911791
else:
17921792
raise ValueError("nd is 2 or 3")
17931793

1794+
def set_window_title(fig: plt.Figure, title: str) -> bool:
1795+
"""
1796+
Set the title of the window containing a figure, if it has one
1797+
1798+
:param fig: the figure
1799+
:type fig: Figure
1800+
:param title: the window title
1801+
:type title: str
1802+
:return: True if the title was set
1803+
:rtype: bool
1804+
1805+
A figure has a window only if it is managed by pyplot and the backend
1806+
provides one. A figure on a headless backend such as ``Agg``, or one
1807+
created directly with ``matplotlib.figure.Figure``, does not, and then
1808+
nothing is done and False is returned.
1809+
1810+
Example::
1811+
1812+
>>> from spatialmath.base import plotvol2, set_window_title
1813+
>>> ax = plotvol2(5)
1814+
>>> set_window_title(ax.figure, "my plot")
1815+
1816+
.. note:: The title of the window is not the title of the axes, see
1817+
:meth:`matplotlib.axes.Axes.set_title`.
1818+
"""
1819+
manager = getattr(fig.canvas, "manager", None)
1820+
if manager is not None and hasattr(manager, "set_window_title"):
1821+
manager.set_window_title(title)
1822+
return True
1823+
return False
1824+
17941825
def isnotebook() -> bool:
17951826
"""
17961827
Determine if code is being run from a Jupyter notebook
@@ -1857,3 +1888,6 @@ def plot_cone(*args, **kwargs) -> None:
18571888

18581889
def plot_cylinder(*args, **kwargs) -> None:
18591890
raise NotImplementedError("Matplotlib is not installed: pip install matplotlib")
1891+
1892+
def set_window_title(*args, **kwargs) -> None:
1893+
raise NotImplementedError("Matplotlib is not installed: pip install matplotlib")

‎tests/base/test_graphics.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,39 @@
99
# TODO check they actually create artists
1010

1111

12+
class TestWindowTitle(unittest.TestCase):
13+
def teardown_method(self, method):
14+
plt.close("all")
15+
16+
def test_sets_the_title_of_a_managed_figure(self):
17+
from types import SimpleNamespace
18+
from unittest import mock
19+
20+
manager = mock.Mock()
21+
fig = SimpleNamespace(canvas=SimpleNamespace(manager=manager))
22+
self.assertTrue(set_window_title(fig, "a title"))
23+
manager.set_window_title.assert_called_once_with("a title")
24+
25+
def test_pyplot_figure(self):
26+
# the Agg manager has no window, but does accept a title
27+
fig = plt.figure()
28+
self.assertIsInstance(set_window_title(fig, "a title"), bool)
29+
30+
def test_figure_without_a_manager(self):
31+
from matplotlib.figure import Figure
32+
33+
self.assertFalse(set_window_title(Figure(), "a title"))
34+
35+
def test_manager_without_the_method(self):
36+
from types import SimpleNamespace
37+
38+
class Manager:
39+
pass
40+
41+
fig = SimpleNamespace(canvas=SimpleNamespace(manager=Manager()))
42+
self.assertFalse(set_window_title(fig, "a title"))
43+
44+
1245
class TestGraphics(unittest.TestCase):
1346
def teardown_method(self, method):
1447
plt.close("all")

0 commit comments

Comments
 (0)