Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ scalebar = ScaleBar(
dimension="si-length",
label=None,
length_fraction=None,
thickness=None,
height_fraction=None,
width_fraction=None,
location=None,
Expand Down Expand Up @@ -253,13 +254,20 @@ In the example below, the scale bar for a *length_fraction* of 0.25 and 0.5 is t

![length fraction](doc/argument_length_fraction.png)

### thickness

Width and unit of the scale bar (valid units: "saxis", i.e. relative to the short axis size;
"laxis", i.e. relative to the long axis size; "font", i.e. relative to the font size).
Default: `None`, value from matplotlibrc or `(0.01, "saxis")`.

### height_fraction

**Deprecated**, use *width_fraction*.
**Deprecated**, use *thickness* or *width_fraction*.

### width_fraction

Width of the scale bar as a fraction of the subplot's height.
*thickness* is a more general way to set this parameter.
Default: `None`, value from matplotlibrc or `0.01`.

### location
Expand Down Expand Up @@ -567,4 +575,4 @@ Copyright (c) 2015-2025 Philippe Pinard
[i56]: https://github.com/ppinard/matplotlib-scalebar/pull/56
[i58]: https://github.com/ppinard/matplotlib-scalebar/issues/58
[i61]: https://github.com/ppinard/matplotlib-scalebar/pull/61
[i62]: https://github.com/ppinard/matplotlib-scalebar/pull/62
[i62]: https://github.com/ppinard/matplotlib-scalebar/pull/62
86 changes: 70 additions & 16 deletions matplotlib_scalebar/scalebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

The following parameters are available for customization in the matplotlibrc:
- scalebar.length_fraction
- scalebar.height_fraction
- scalebar.thickness
- scalebar.location
- scalebar.pad
- scalebar.border_pad
Expand All @@ -39,8 +39,9 @@

# Standard library modules.
import bisect
import warnings
import dataclasses
import numbers
import warnings

# Third party modules.
import matplotlib
Expand All @@ -61,6 +62,7 @@
AnchoredOffsetbox,
)
from matplotlib.patches import Rectangle
from matplotlib.transforms import IdentityTransform, blended_transform_factory

# Local modules.
from matplotlib_scalebar.dimension import (
Expand Down Expand Up @@ -96,10 +98,19 @@ def _validate_legend_loc(loc):
return loc


def _validate_dim(dim):
if (len(dim) == 2
and isinstance(dim[0], numbers.Real)
and dim[1] in ["saxis", "laxis", "font"]):
return dim
else:
raise ValueError("Not a valid dimension")


defaultParams.update(
{
"scalebar.length_fraction": [0.2, validate_float],
"scalebar.width_fraction": [0.01, validate_float],
"scalebar.thickness": [(0.01, "saxis"), _validate_dim],
"scalebar.location": ["upper right", _validate_legend_loc],
"scalebar.pad": [0.2, validate_float],
"scalebar.border_pad": [0.1, validate_float],
Expand Down Expand Up @@ -177,6 +188,7 @@ def __init__(
dimension="si-length",
label=None,
length_fraction=None,
thickness=None,
height_fraction=None,
width_fraction=None,
location=None,
Expand Down Expand Up @@ -242,9 +254,15 @@ def __init__(
This argument is ignored if a *fixed_value* is specified.
:type length_fraction: :class:`float`

:arg width_fraction: width of the scale bar as a fraction of the
axes's height (default: rcParams['scalebar.width_fraction'] or ``0.01``)
:type width_fraction: :class:`float`
:arg thickness: thickness of the scale bar, as a ``(value, unit)`` pair.
Valid units are
* "laxis": value is relative to the size of the parent axes in the
"long" direction.
* "saxis": value is relative to the size of the parent axes in the
"short" direction.
* "font": value is relative to the label fontsize.
(default: rcParams['scalebar.thickness'] or ``(0.01, "saxis")``)
:type thickness: ``tuple[float, str]``

:arg location: a location code (same as legend)
(default: rcParams['scalebar.location'] or ``upper right``)
Expand Down Expand Up @@ -347,6 +365,12 @@ def __init__(
)
scale_formatter = scale_formatter or label_formatter

if width_fraction is not None:
if thickness is not None:
warnings.warn("Ignoring 'width_fraction', as 'thickness' is also set")
else:
thickness = (width_fraction, "saxis")

if (
loc is not None
and location is not None
Expand All @@ -359,7 +383,7 @@ def __init__(
self.units = units
self.label = label
self.length_fraction = length_fraction
self.width_fraction = width_fraction
self.thickness = thickness
self.location = location or loc
self.pad = pad
self.border_pad = border_pad
Expand Down Expand Up @@ -433,7 +457,7 @@ def _get_value(attr, default):
return value

length_fraction = _get_value("length_fraction", 0.2)
width_fraction = _get_value("width_fraction", 0.01)
thickness_value, thickness_unit = _get_value("thickness", (0.01, "saxis"))
location = _get_value("location", "upper right")
if isinstance(location, str):
location = self._LOCATIONS[location.lower()]
Expand Down Expand Up @@ -486,29 +510,44 @@ def _get_value(attr, default):

scale_text = self.scale_formatter(value, self.dimension.to_latex(units))

width_px = abs(ylim[1] - ylim[0]) * width_fraction
if thickness_unit == "saxis":
thickness = thickness_value
transform = (ax.get_xaxis_transform()
if rotation == "horizontal" else
ax.get_yaxis_transform())
elif thickness_unit == "laxis":
thickness = thickness_value
transform = (ax.get_yaxis_transform()
if rotation == "horizontal" else
ax.get_xaxis_transform())
elif thickness_unit == "font":
thickness = (font_properties.get_size() / 72 * thickness_value)
transform = (
blended_transform_factory(ax.transData, ax.figure.dpi_scale_trans)
if rotation == "horizontal" else
blended_transform_factory(ax.figure.dpi_scale_trans, ax.transData))

# Create scale bar
if rotation == "horizontal":
scale_rect = Rectangle(
(0, 0),
length_px,
width_px,
thickness,
fill=True,
facecolor=color,
edgecolor="none",
)
else:
scale_rect = Rectangle(
(0, 0),
width_px,
thickness,
length_px,
fill=True,
facecolor=color,
edgecolor="none",
)

scale_bar_box = AuxTransformBox(ax.transData)
scale_bar_box = AuxTransformBox(transform)
scale_bar_box.add_artist(scale_rect)

# Create scale text
Expand Down Expand Up @@ -627,30 +666,45 @@ def set_length_fraction(self, fraction):

length_fraction = property(get_length_fraction, set_length_fraction)

def get_thickness(self):
return self._thickness

def set_thickness(self, thickness):
if thickness is not None:
_validate_dim(thickness)
self._thickness = thickness

thickness = property(get_thickness, set_thickness)

def get_width_fraction(self):
return self._width_fraction
if self._thickness is None:
return None
elif self._thickness[1] == "saxis":
return self._thickness[0]
else:
raise ValueError(f"thickness ({self._thickness}) is not a width fraction")

def set_width_fraction(self, fraction):
if fraction is not None:
fraction = float(fraction)
if fraction <= 0.0 or fraction > 1.0:
raise ValueError("Width fraction must be between [0.0, 1.0]")
self._width_fraction = fraction
self._thickness = (fraction, "saxis")

width_fraction = property(get_width_fraction, set_width_fraction)

def get_height_fraction(self):
warnings.warn(
"The get_height_fraction method is deprecated. "
"Use get_width_fraction instead.",
"Use get_thickness instead.",
DeprecationWarning,
)
return self.width_fraction

def set_height_fraction(self, fraction):
warnings.warn(
"The set_height_fraction method is deprecated. "
"Use set_width_fraction instead.",
"Use set_thickness instead.",
DeprecationWarning,
)
self.width_fraction = fraction
Expand Down
41 changes: 40 additions & 1 deletion tests/test_scalebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def test_mpl_rcParams_update():

params = {
"scalebar.length_fraction": 0.2,
"scalebar.width_fraction": 0.01,
"scalebar.thickness": (0.01, "saxis"),
"scalebar.location": "upper right",
"scalebar.pad": 0.2,
"scalebar.border_pad": 0.1,
Expand Down Expand Up @@ -125,6 +125,45 @@ def test_scalebar_height_fraction(scalebar):
scalebar.set_height_fraction(1.1)


def test_scalebar_thickness_width_fraction(scalebar):
assert scalebar.get_thickness() is None
assert scalebar.thickness is None
assert scalebar.get_width_fraction() is None
assert scalebar.width_fraction is None

scalebar.set_width_fraction(0.2)
assert scalebar.get_width_fraction() == pytest.approx(0.2, abs=1e-2)
assert scalebar.width_fraction == pytest.approx(0.2, abs=1e-2)
assert scalebar.get_thickness() == (0.2, "saxis")
assert scalebar.thickness == (0.2, "saxis")

scalebar.thickness = (0.4, "saxis")
assert scalebar.get_width_fraction() == pytest.approx(0.4, abs=1e-2)
assert scalebar.width_fraction == pytest.approx(0.4, abs=1e-2)
assert scalebar.get_thickness() == (0.4, "saxis")
assert scalebar.thickness == (0.4, "saxis")

scalebar.width_fraction = 0.1
assert scalebar.get_width_fraction() == pytest.approx(0.1, abs=1e-2)
assert scalebar.width_fraction == pytest.approx(0.1, abs=1e-2)
assert scalebar.get_thickness() == (0.1, "saxis")
assert scalebar.thickness == (0.1, "saxis")

scalebar.set_thickness((0.3, "font"))
with pytest.raises(ValueError):
scalebar.get_width_fraction()
with pytest.raises(ValueError):
scalebar.width_fraction
assert scalebar.get_thickness() == (0.3, "font")
assert scalebar.thickness == (0.3, "font")

with pytest.raises(ValueError):
scalebar.set_width_fraction(0.0)

with pytest.raises(ValueError):
scalebar.set_width_fraction(1.1)


def test_scalebar_location(scalebar):
assert scalebar.get_location() is None
assert scalebar.location is None
Expand Down