From 23f2419c810cedb503eecb87257770979b210039 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 8 Apr 2024 20:15:30 +0200 Subject: [PATCH 1/2] Add a (skippable) check that the axes have equal aspect ratio. When the scalebar is not drawn on an aspect where imshow() has been called (e.g., a point cloud made with plot()), the axes aspect ratio is not automatically set to 1, in which case the scale bar will be wrong (or rather, it will only be correct in the direction (horizontal or vertical) in which it is drawn). To avoid mistakes, add a check for the aspect ratio, emitting a warning when appropriate. The check can be skipped by using new variants for `rotation`: it can now be set to "horizontal-only" ("the scalebar only applies to the horizontal direction") or "vertical-only". (This could also have been a separate kwarg, but something like `check_aspect=False` reads a bit awkwardly to me.) --- README.md | 11 ++++++++++- matplotlib_scalebar/scalebar.py | 17 ++++++++++++++--- tests/test_scalebar.py | 32 +++++++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 380ab18..062c5b6 100644 --- a/README.md +++ b/README.md @@ -312,7 +312,16 @@ Default: `False` ### rotation Whether to create a scale bar based on the x-axis (default) or y-axis. -*rotation* can either be `horizontal` or `vertical`. +*rotation* can either be `horizontal`, `vertical`, `horizontal-only`, or +`vertical-only`. + +By default, matplotlib_scalebar checks whether the axes have equal aspect ratio +(so that the scale bar applies both for the x and the y directions), and emits +a warning if this is not the case. This warning can be suppressed by setting +*rotation* to `horizontal-only` ("the scale bar only applies to the horizontal +direction") or `vertical-only` ("the scale bar only applies to the vertical +direction"). + Note you might have to adjust *scale_loc* and *label_loc* to achieve desired layout. Default: `None`, value from matplotlibrc or `horizontal`. diff --git a/matplotlib_scalebar/scalebar.py b/matplotlib_scalebar/scalebar.py index 5ae7a51..2b23e98 100644 --- a/matplotlib_scalebar/scalebar.py +++ b/matplotlib_scalebar/scalebar.py @@ -85,7 +85,7 @@ "label_loc", _VALID_LABEL_LOCATIONS, ignorecase=True ) -_VALID_ROTATIONS = ["horizontal", "vertical"] +_VALID_ROTATIONS = ["horizontal", "horizontal-only", "vertical", "vertical-only"] _validate_rotation = ValidateInStrings("rotation", _VALID_ROTATIONS, ignorecase=True) @@ -303,8 +303,11 @@ def __init__( :arg animated: animation state (default: ``False``) :type animated: :class`bool` - :arg rotation: either ``horizontal`` or ``vertical`` - (default: rcParams['scalebar.rotation'] or ``horizontal``) + :arg rotation: ``horizontal``, ``vertical``, ``horizontal-only``, or ``vertical-only`` + (default: rcParams['scalebar.rotation'] or ``horizontal``). + By default, ScaleBar checks that it is getting drawn on an axes + with equal aspect ratio and emits a warning if this is not the case. + The -only variants suppress that check. :type rotation: :class:`str` :arg bbox_to_anchor: box that is used to position the scalebar @@ -431,6 +434,14 @@ def _get_value(attr, default): fixed_value = self.fixed_value fixed_units = self.fixed_units or self.units rotation = _get_value("rotation", "horizontal").lower() + if rotation.endswith("-only"): + rotation = rotation[:-5] + else: # Check aspect ratio. + if self.axes.get_aspect() != 1: + warnings.warn( + f"Drawing scalebar on axes with unequal aspect ratio; " + f"either call ax.set_aspect(1) or suppress the warning with " + f"rotation='{rotation}-only'.") label = self.label # Create text properties diff --git a/tests/test_scalebar.py b/tests/test_scalebar.py index 0abb906..2afb2a7 100644 --- a/tests/test_scalebar.py +++ b/tests/test_scalebar.py @@ -2,6 +2,8 @@ # Standard library modules. +import warnings + # Third party modules. import matplotlib @@ -298,7 +300,8 @@ def test_label_formatter(scalebar): assert scalebar.label_formatter(value, units) == "m 5" -@pytest.mark.parametrize("rotation", ["horizontal", "vertical"]) +@pytest.mark.parametrize("rotation", [ + "horizontal", "vertical", "horizontal-only", "vertical-only"]) def test_rotation(scalebar, rotation): assert scalebar.get_rotation() is None assert scalebar.rotation is None @@ -311,6 +314,33 @@ def test_rotation(scalebar, rotation): scalebar.set_rotation("h") +def test_rotation_checks_aspect(): + fig, ax = plt.subplots() + sb = ScaleBar(0.5) + ax.add_artist(sb) + + with warnings.catch_warnings(): + warnings.simplefilter("error") + + for aspect in ["auto", 2]: + ax.set_aspect(aspect) # Warns if not using the -only variants. + for rotation in ["horizontal", "vertical"]: + sb.rotation = rotation + with pytest.warns(): + fig.canvas.draw() + sb.rotation = rotation + "-only" + fig.canvas.draw() + + ax.set_aspect("equal") # Never warn. + for rotation in ["horizontal", "vertical"]: + sb.rotation = rotation + fig.canvas.draw() + sb.rotation = rotation + "-only" + fig.canvas.draw() + + plt.close(fig) + + def test_bbox_to_anchor(scalebar): assert scalebar.get_bbox_to_anchor() is None assert scalebar.bbox_to_anchor is None From a30887f87ca354747f124d261796f1781141a639 Mon Sep 17 00:00:00 2001 From: Philippe Pinard Date: Sun, 5 Jan 2025 15:29:28 +0000 Subject: [PATCH 2/2] Try to fix CI --- tests/test_scalebar.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_scalebar.py b/tests/test_scalebar.py index 2afb2a7..0075197 100644 --- a/tests/test_scalebar.py +++ b/tests/test_scalebar.py @@ -36,6 +36,8 @@ def scalebar(): yield scalebar plt.draw() + plt.close() + del fig def test_mpl_rcParams_update(): @@ -300,8 +302,9 @@ def test_label_formatter(scalebar): assert scalebar.label_formatter(value, units) == "m 5" -@pytest.mark.parametrize("rotation", [ - "horizontal", "vertical", "horizontal-only", "vertical-only"]) +@pytest.mark.parametrize( + "rotation", ["horizontal", "vertical", "horizontal-only", "vertical-only"] +) def test_rotation(scalebar, rotation): assert scalebar.get_rotation() is None assert scalebar.rotation is None