diff --git a/README.md b/README.md index 2625a35..a6c6825 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,35 @@ ax.add_artist(scalebar) fig.savefig("original_resolution.png", dpi=dpi) ``` +### Information about the drawn scale bar + +After the scale bar has been drawn, the `info` property returns the following dataclass. + +```python +@dataclasses.dataclass +class ScaleBarInfo: + length_px: int + value: float + units: str + scale_text: str + window_extent: matplotlib.transforms.Bbox +``` + +Note that the `info` property returns a `ValueError` exception if the scale bar has not been drawn. + +```python +fig, ax = plt.subplots() + +scalebar = ScaleBar(0.08, "cm", length_fraction=0.25) +ax.add_artist(scalebar) + +print(scalebar.info) # raises a ValueError exception + +fig.canvas.draw() + +print(scalebar.info) # works +``` + ## ScaleBar arguments Here are arguments of the **ScaleBar** class constructor and examples how to use them. diff --git a/matplotlib_scalebar/scalebar.py b/matplotlib_scalebar/scalebar.py index 61a0f6b..efd5a5d 100644 --- a/matplotlib_scalebar/scalebar.py +++ b/matplotlib_scalebar/scalebar.py @@ -40,6 +40,7 @@ # Standard library modules. import bisect import warnings +import dataclasses # Third party modules. import matplotlib @@ -142,6 +143,15 @@ def _validate_legend_loc(loc): } +@dataclasses.dataclass +class ScaleBarInfo: + length_px: int + value: float + units: str + scale_text: str + window_extent: matplotlib.transforms.Bbox + + class ScaleBar(Artist): zorder = 6 @@ -368,6 +378,7 @@ def __init__( self.rotation = rotation self.bbox_to_anchor = bbox_to_anchor self.bbox_transform = bbox_transform + self._info = None def _calculate_best_length(self, length_px): dx = self.dx @@ -393,6 +404,8 @@ def _calculate_exact_length(self, value, units): return newvalue / self.dx def draw(self, renderer, *args, **kwargs): + self._info = None + if not self.get_visible(): return if self.dx == 0: @@ -555,6 +568,10 @@ def _get_value(attr, default): box.patch.set_alpha(box_alpha) box.draw(renderer) + self._info = ScaleBarInfo( + length_px, value, units, scale_text, box.get_window_extent(renderer) + ) + def get_dx(self): return self._dx @@ -841,3 +858,9 @@ def set_bbox_transform(self, bbox_transform): self._bbox_transform = bbox_transform bbox_transform = property(get_bbox_transform, set_bbox_transform) + + @property + def info(self): + if self._info is None: + raise ValueError("Scale bar has not been drawn. Call figure.canvas.draw()") + return self._info diff --git a/tests/test_scalebar.py b/tests/test_scalebar.py index 7fb3f82..1093296 100644 --- a/tests/test_scalebar.py +++ b/tests/test_scalebar.py @@ -369,3 +369,32 @@ def test_bbox_transform(scalebar): scalebar.bbox_transform = scalebar.axes.transAxes assert scalebar.get_bbox_transform() == scalebar.axes.transAxes assert scalebar.bbox_transform == scalebar.axes.transAxes + + +def test_info(): + fig = plt.figure() + ax = fig.add_subplot(111) + + data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + ax.imshow(data) + + scalebar = ScaleBar(0.5) + ax.add_artist(scalebar) + + with pytest.raises(ValueError): + scalebar.info + + plt.draw() + + info = scalebar.info + assert info.length_px == pytest.approx(0.4, 1e-4) + assert info.value == pytest.approx(2, 1e-4) + assert info.units == "dm" + assert info.scale_text == "2 dm" + assert info.window_extent.x0 == pytest.approx(456.5755555555555, 1e-4) + assert info.window_extent.y0 == pytest.approx(390.81511111111104, 1e-4) + assert info.window_extent.x1 == pytest.approx(511.4111111111111, 1e-4) + assert info.window_extent.y1 == pytest.approx(421.01111111111106, 1e-4) + + plt.close() + del fig