Skip to content

Commit

Permalink
Add a (skippable) check that the axes have equal aspect ratio.
Browse files Browse the repository at this point in the history
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.)
  • Loading branch information
anntzer committed Apr 8, 2024
1 parent 2c57173 commit b24f406
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 6 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,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 colorbar only applies to the horizontal
direction") or `vertical-only` ("the colorbar 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`.

Expand Down
17 changes: 14 additions & 3 deletions matplotlib_scalebar/scalebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,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)


Expand Down Expand Up @@ -298,8 +298,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
Expand Down Expand Up @@ -426,6 +429,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
Expand Down
34 changes: 32 additions & 2 deletions matplotlib_scalebar/test_scalebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

# Standard library modules.

import warnings

# Third party modules.
import matplotlib

Expand Down Expand Up @@ -33,7 +35,7 @@ def scalebar():

yield scalebar

plt.draw()
plt.close(fig)


def test_mpl_rcParams_update():
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit b24f406

Please sign in to comment.