From 9cf53033baecec9a0049d2505eea57da17460c95 Mon Sep 17 00:00:00 2001 From: Zezhong Date: Wed, 22 May 2024 18:12:43 +0200 Subject: [PATCH 01/17] support angstrom --- matplotlib_scalebar/dimension.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/matplotlib_scalebar/dimension.py b/matplotlib_scalebar/dimension.py index e561b95..ff173c0 100644 --- a/matplotlib_scalebar/dimension.py +++ b/matplotlib_scalebar/dimension.py @@ -25,6 +25,9 @@ "\u00b5": 1e-6, "u": 1e-6, "n": 1e-9, + "Å": 1e-10, + "A": 1e-10, + "angstrom": 1e-10, "p": 1e-12, "f": 1e-15, "a": 1e-18, @@ -111,7 +114,11 @@ def __init__(self): latexrepr = None if prefix == "\u00b5" or prefix == "u": latexrepr = _LATEX_MU + "m" - self.add_units(prefix + "m", factor, latexrepr) + if prefix == "Å" or prefix == "A" or prefix == "angstrom": + latexrepr = "Å" # Directly add "Å" without appending "m" + self.add_units(prefix, factor, latexrepr) + else: + self.add_units(prefix + "m", factor, latexrepr) class SILengthReciprocalDimension(_Dimension): @@ -121,7 +128,10 @@ def __init__(self): latexrepr = "{0}m$^{{-1}}$".format(prefix) if prefix == "\u00b5" or prefix == "u": latexrepr = _LATEX_MU + "m$^{-1}$" - self.add_units("1/{0}m".format(prefix), 1 / factor, latexrepr) + if prefix == "Å" or prefix == "A" or prefix == "angstrom": + self.add_units("1/Å", 1 / factor) + else: + self.add_units("1/{0}m".format(prefix), 1 / factor, latexrepr) class ImperialLengthDimension(_Dimension): From 5440f3db8064ee119be13b6b512b0487e0ad42a1 Mon Sep 17 00:00:00 2001 From: Zezhong Date: Thu, 30 May 2024 22:05:32 +0200 Subject: [PATCH 02/17] support 1/A --- matplotlib_scalebar/dimension.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/matplotlib_scalebar/dimension.py b/matplotlib_scalebar/dimension.py index ff173c0..1da349c 100644 --- a/matplotlib_scalebar/dimension.py +++ b/matplotlib_scalebar/dimension.py @@ -25,9 +25,7 @@ "\u00b5": 1e-6, "u": 1e-6, "n": 1e-9, - "Å": 1e-10, "A": 1e-10, - "angstrom": 1e-10, "p": 1e-12, "f": 1e-15, "a": 1e-18, @@ -129,7 +127,8 @@ def __init__(self): if prefix == "\u00b5" or prefix == "u": latexrepr = _LATEX_MU + "m$^{-1}$" if prefix == "Å" or prefix == "A" or prefix == "angstrom": - self.add_units("1/Å", 1 / factor) + latexrepr = "Å$^{-1}$" + self.add_units("1/A", 1 / factor, latexrepr) else: self.add_units("1/{0}m".format(prefix), 1 / factor, latexrepr) From 3405933eeafda392cd71c5447eb1f33ecca2ffdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20L=C3=A4hnemann?= Date: Sat, 4 Jan 2025 15:49:15 +0100 Subject: [PATCH 03/17] CI maintenance (#62) * Bump CI action versions * Add python 3.12 --- .github/workflows/ci.yml | 8 ++++---- pyproject.toml | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e654a7..b9990aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,15 +18,15 @@ jobs: matplotlib-version: [latest, dev] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Cache pip - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} @@ -47,4 +47,4 @@ jobs: run: hatch run test - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 diff --git a/pyproject.toml b/pyproject.toml index b60b357..f54b514 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering :: Visualization", ] dependencies = [ From deff20f17c6a6d770d870dc159276897e4d1dfaf Mon Sep 17 00:00:00 2001 From: John ZuHone Date: Sat, 4 Jan 2025 10:02:53 -0500 Subject: [PATCH 04/17] Add common astronomical lengths (#56) * Add astronomical length units * add astro-length to README * Fix bugs --- README.md | 1 + matplotlib_scalebar/dimension.py | 12 ++++++++++++ matplotlib_scalebar/scalebar.py | 5 +++++ 3 files changed, 18 insertions(+) diff --git a/README.md b/README.md index 3d2389d..ccbede1 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,7 @@ Dimension of *dx* and *units*. It can either be equal: * `si-length` (default): scale bar showing km, m, cm, etc. * `imperial-length`: scale bar showing in, ft, yd, mi, etc. +* `astro-length`: scale bar showing pc, kpc, Mpc, ly, AU, etc. * `si-length-reciprocal`: scale bar showing 1/m, 1/cm, etc. * `pixel-length`: scale bar showing px, kpx, Mpx, etc. * `angle`: scale bar showing °, ʹ (minute of arc) or ʹʹ (second of arc) diff --git a/matplotlib_scalebar/dimension.py b/matplotlib_scalebar/dimension.py index 1da349c..430bc7d 100644 --- a/matplotlib_scalebar/dimension.py +++ b/matplotlib_scalebar/dimension.py @@ -145,6 +145,18 @@ def __init__(self): self.add_units("lea", 15840) +class AstronomicalLengthDimension(_Dimension): + def __init__(self): + super().__init__("pc") + for prefix, factor in _PREFIXES_FACTORS.items(): + latexrepr = None + if prefix == "\u00b5" or prefix == "u": + latexrepr = _LATEX_MU + "pc" + self.add_units(prefix + "pc", factor, latexrepr) + self.add_units("ly", 0.30659485) + self.add_units("AU", 4.84813681e-06) + + class PixelLengthDimension(_Dimension): def __init__(self): super().__init__("px") diff --git a/matplotlib_scalebar/scalebar.py b/matplotlib_scalebar/scalebar.py index 92382b1..1164aac 100644 --- a/matplotlib_scalebar/scalebar.py +++ b/matplotlib_scalebar/scalebar.py @@ -33,6 +33,7 @@ "SI_LENGTH", "SI_LENGTH_RECIPROCAL", "IMPERIAL_LENGTH", + "ASTRO_LENGTH", "PIXEL_LENGTH", ] @@ -66,6 +67,7 @@ SILengthDimension, SILengthReciprocalDimension, ImperialLengthDimension, + AstronomicalLengthDimension, PixelLengthDimension, AngleDimension, ) @@ -126,6 +128,7 @@ def _validate_legend_loc(loc): SI_LENGTH = "si-length" SI_LENGTH_RECIPROCAL = "si-length-reciprocal" IMPERIAL_LENGTH = "imperial-length" +ASTRO_LENGTH = "astro-length" PIXEL_LENGTH = "pixel-length" ANGLE = "angle" @@ -133,6 +136,7 @@ def _validate_legend_loc(loc): SI_LENGTH: SILengthDimension, SI_LENGTH_RECIPROCAL: SILengthReciprocalDimension, IMPERIAL_LENGTH: ImperialLengthDimension, + ASTRO_LENGTH: AstronomicalLengthDimension, PIXEL_LENGTH: PixelLengthDimension, ANGLE: AngleDimension, } @@ -212,6 +216,7 @@ def __init__( * ``:const:`si-length```: scale bar showing km, m, cm, etc. * ``:const:`imperial-length```: scale bar showing in, ft, yd, mi, etc. * ``:const:`si-length-reciprocal```: scale bar showing 1/m, 1/cm, etc. + * ``:const:`astro-length```: scale bar showing pc, kpc, Mpc, ly, AU, etc. * ``:const:`pixel-length```: scale bar showing px, kpx, Mpx, etc. * ``:const:`angle```: scale bar showing \u00b0, \u2032 or \u2032\u2032. * a :class:`matplotlib_scalebar.dimension._Dimension` object From 5c4ccd3f6c83c8c5c06009b7365c2b645e1d7441 Mon Sep 17 00:00:00 2001 From: tetsu <66517224+360tetsu360@users.noreply.github.com> Date: Sun, 5 Jan 2025 00:03:48 +0900 Subject: [PATCH 05/17] Fix example_angular.py (#55) --- doc/example_angular.png | Bin 0 -> 32980 bytes doc/example_angular.py | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 doc/example_angular.png diff --git a/doc/example_angular.png b/doc/example_angular.png new file mode 100644 index 0000000000000000000000000000000000000000..a43c793fc658322ce658470e7c1bc8d255c30880 GIT binary patch literal 32980 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 Date: Sat, 4 Jan 2025 21:01:34 +0000 Subject: [PATCH 06/17] Update dev --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ccbede1..380ab18 100644 --- a/README.md +++ b/README.md @@ -380,6 +380,9 @@ ax.add_artist(scalebar) * Update tooling ([#53][i53]) * Add example gallery ([#50][i50]) * Add *bbox_anchor* and *bbox_transform* ([#40](i40)) +* Add common astronomical lengths ([#56][i56]) +* Fix example_angular.py ([#55][i55]) +* Support Python 3.12 ([#60][i60]) ### 0.8.1 @@ -465,12 +468,15 @@ ax.add_artist(scalebar) [@musicinmybrain](https://github.com/musicinmybrain), [@kolibril13](https://github.com/kolibril13), [@ilopata1](https://github.com/ilopata1) +[@jzuhone](https://github.com/jzuhone) +[@360tetsu360](https://github.com/360tetsu360) +[@jlaehne](https://github.com/jlaehne) ## License License under the BSD License, compatible with matplotlib. -Copyright (c) 2015-2023 Philippe Pinard +Copyright (c) 2015-2025 Philippe Pinard [i9]: https://github.com/ppinard/matplotlib-scalebar/issues/9 [i11]: https://github.com/ppinard/matplotlib-scalebar/issues/11 @@ -496,3 +502,6 @@ Copyright (c) 2015-2023 Philippe Pinard [i48]: https://github.com/ppinard/matplotlib-scalebar/pull/48 [i50]: https://github.com/ppinard/matplotlib-scalebar/pull/50 [i53]: https://github.com/ppinard/matplotlib-scalebar/pull/53 +[i55]: https://github.com/ppinard/matplotlib-scalebar/pull/55 +[i56]: https://github.com/ppinard/matplotlib-scalebar/pull/56 +[i62]: https://github.com/ppinard/matplotlib-scalebar/pull/62 \ No newline at end of file From 90a0ea70638fc6500483a859f592b6754da216d2 Mon Sep 17 00:00:00 2001 From: Philippe Pinard Date: Sat, 4 Jan 2025 21:06:09 +0000 Subject: [PATCH 07/17] Fix ruff --- matplotlib_scalebar/scalebar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matplotlib_scalebar/scalebar.py b/matplotlib_scalebar/scalebar.py index 1164aac..5ae7a51 100644 --- a/matplotlib_scalebar/scalebar.py +++ b/matplotlib_scalebar/scalebar.py @@ -216,7 +216,7 @@ def __init__( * ``:const:`si-length```: scale bar showing km, m, cm, etc. * ``:const:`imperial-length```: scale bar showing in, ft, yd, mi, etc. * ``:const:`si-length-reciprocal```: scale bar showing 1/m, 1/cm, etc. - * ``:const:`astro-length```: scale bar showing pc, kpc, Mpc, ly, AU, etc. + * ``:const:`astro-length```: scale bar showing pc, kpc ly, AU, etc. * ``:const:`pixel-length```: scale bar showing px, kpx, Mpx, etc. * ``:const:`angle```: scale bar showing \u00b0, \u2032 or \u2032\u2032. * a :class:`matplotlib_scalebar.dimension._Dimension` object From a3ff8ad55669fc5b2e9465e3179f16c4422967fb Mon Sep 17 00:00:00 2001 From: Philippe Pinard Date: Sat, 4 Jan 2025 21:24:09 +0000 Subject: [PATCH 08/17] Ignore uv.lock --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 19d3cc6..6086ddc 100644 --- a/.gitignore +++ b/.gitignore @@ -272,3 +272,5 @@ dmypy.json .pyre/ # End of https://www.gitignore.io/api/code,python,pycharm,eclipse + +uv.lock From 4fd4fcb405cdeca7b307067918df1c0770bed67c Mon Sep 17 00:00:00 2001 From: Philippe Pinard Date: Sat, 4 Jan 2025 21:33:43 +0000 Subject: [PATCH 09/17] Move tests --- {matplotlib_scalebar => tests}/test_dimension.py | 0 {matplotlib_scalebar => tests}/test_scalebar.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {matplotlib_scalebar => tests}/test_dimension.py (100%) rename {matplotlib_scalebar => tests}/test_scalebar.py (100%) diff --git a/matplotlib_scalebar/test_dimension.py b/tests/test_dimension.py similarity index 100% rename from matplotlib_scalebar/test_dimension.py rename to tests/test_dimension.py diff --git a/matplotlib_scalebar/test_scalebar.py b/tests/test_scalebar.py similarity index 100% rename from matplotlib_scalebar/test_scalebar.py rename to tests/test_scalebar.py From 3c4d472074a369adb19805625356bb589e2a40ca Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 5 Jan 2025 16:35:13 +0100 Subject: [PATCH 10/17] Add a (skippable) check that the axes have equal aspect ratio. (#61) * 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.) * Try to fix CI --------- Co-authored-by: Philippe Pinard --- README.md | 11 ++++++++++- matplotlib_scalebar/scalebar.py | 17 +++++++++++++--- tests/test_scalebar.py | 35 ++++++++++++++++++++++++++++++++- 3 files changed, 58 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..0075197 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 @@ -34,6 +36,8 @@ def scalebar(): yield scalebar plt.draw() + plt.close() + del fig def test_mpl_rcParams_update(): @@ -298,7 +302,9 @@ 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 +317,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 ad4e984cc7904ffd59944eb90ac46a120c93fd9b Mon Sep 17 00:00:00 2001 From: Philippe Pinard Date: Sun, 5 Jan 2025 15:39:59 +0000 Subject: [PATCH 11/17] Update README --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 062c5b6..1ec8150 100644 --- a/README.md +++ b/README.md @@ -391,7 +391,8 @@ ax.add_artist(scalebar) * Add *bbox_anchor* and *bbox_transform* ([#40](i40)) * Add common astronomical lengths ([#56][i56]) * Fix example_angular.py ([#55][i55]) -* Support Python 3.12 ([#60][i60]) +* Support Python 3.12 ([#61][i61]) +* Add a (skippable) check that the axes have equal aspect ratio ([#62][i62]) ### 0.8.1 @@ -513,4 +514,5 @@ Copyright (c) 2015-2025 Philippe Pinard [i53]: https://github.com/ppinard/matplotlib-scalebar/pull/53 [i55]: https://github.com/ppinard/matplotlib-scalebar/pull/55 [i56]: https://github.com/ppinard/matplotlib-scalebar/pull/56 +[i61]: https://github.com/ppinard/matplotlib-scalebar/pull/61 [i62]: https://github.com/ppinard/matplotlib-scalebar/pull/62 \ No newline at end of file From 677c8ec23ce7894cf6bd9b0709c2995ec9714b43 Mon Sep 17 00:00:00 2001 From: Philippe Pinard Date: Sun, 5 Jan 2025 16:47:39 +0000 Subject: [PATCH 12/17] Add tip about original resolution output --- README.md | 20 ++++++++++++++++++++ doc/original_resolution.png | Bin 0 -> 60126 bytes doc/original_resolution.py | 24 ++++++++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 doc/original_resolution.png create mode 100644 doc/original_resolution.py diff --git a/README.md b/README.md index 1ec8150..2625a35 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,26 @@ https://kolibril13.github.io/plywood-gallery-matplotlib-scalebar/ ![citurs](https://user-images.githubusercontent.com/44469195/202899151-483bad4b-bacf-4845-a7cd-ace7bb6417b1.png) +## Tips + +### Save image in original resolution + +The code snippet below shows how to save an image (stored in variable `arr`) in the same resolution as the original image without any horizontal and vertical axes. + +```python +dpi = 200 +fig = plt.figure(figsize=(arr.shape[1] / dpi, arr.shape[0] / dpi), frameon=False, dpi=dpi) + +ax = fig.add_axes([0.0, 0.0, 1.0, 1.0]) +ax.set_axis_off() + +ax.imshow(arr) + +scalebar = ScaleBar(100, "nm", length_fraction=0.25, location="lower right") +ax.add_artist(scalebar) + +fig.savefig("original_resolution.png", dpi=dpi) +``` ## ScaleBar arguments diff --git a/doc/original_resolution.png b/doc/original_resolution.png new file mode 100644 index 0000000000000000000000000000000000000000..26197eaeb0669301952652e1e230b76c54855f2d GIT binary patch literal 60126 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

DtTz>vx|lyh78L2Y83O8|N0N3f*# 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 Date: Sun, 5 Jan 2025 16:57:59 +0000 Subject: [PATCH 13/17] Fix issue #66 --- matplotlib_scalebar/scalebar.py | 12 ++++++++---- tests/test_scalebar.py | 9 +++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/matplotlib_scalebar/scalebar.py b/matplotlib_scalebar/scalebar.py index 2b23e98..61a0f6b 100644 --- a/matplotlib_scalebar/scalebar.py +++ b/matplotlib_scalebar/scalebar.py @@ -303,7 +303,8 @@ def __init__( :arg animated: animation state (default: ``False``) :type animated: :class`bool` - :arg rotation: ``horizontal``, ``vertical``, ``horizontal-only``, or ``vertical-only`` + :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. @@ -336,8 +337,10 @@ def __init__( ) scale_formatter = scale_formatter or label_formatter - if loc is not None and self._convert_location(loc) != self._convert_location( - location + if ( + loc is not None + and location is not None + and self._convert_location(loc) != self._convert_location(location) ): raise ValueError("loc and location are specified and not equal") @@ -441,7 +444,8 @@ def _get_value(attr, default): 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'.") + f"rotation='{rotation}-only'." + ) label = self.label # Create text properties diff --git a/tests/test_scalebar.py b/tests/test_scalebar.py index 0075197..7fb3f82 100644 --- a/tests/test_scalebar.py +++ b/tests/test_scalebar.py @@ -164,6 +164,15 @@ def test_scalebar_loc(scalebar): with pytest.raises(ValueError): ScaleBar(1.0, loc="upper right", location=2) + # Should not raise error + scalebar2 = ScaleBar(1.0, loc="upper center") + assert scalebar2.location == 9 + assert scalebar2.loc == 9 + + scalebar2 = ScaleBar(1.0, location="upper center") + assert scalebar2.location == 9 + assert scalebar2.loc == 9 + def test_scalebar_pad(scalebar): assert scalebar.get_pad() is None From 2060401dfab4e915435e9dbd029633aa969e059b Mon Sep 17 00:00:00 2001 From: Philippe Pinard Date: Mon, 6 Jan 2025 22:08:58 +0000 Subject: [PATCH 14/17] Add info after scale bar has been drawn (#67) --- README.md | 29 +++++++++++++++++++++++++++++ matplotlib_scalebar/scalebar.py | 23 +++++++++++++++++++++++ tests/test_scalebar.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) 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 From 42e38a4286d3441385c58a4f47e6b31a851dad98 Mon Sep 17 00:00:00 2001 From: Philippe Pinard Date: Fri, 17 Jan 2025 21:58:12 +0000 Subject: [PATCH 15/17] Fix isue #58 --- README.md | 3 +++ matplotlib_scalebar/dimension.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a6c6825..022ab62 100644 --- a/README.md +++ b/README.md @@ -442,6 +442,7 @@ ax.add_artist(scalebar) * Fix example_angular.py ([#55][i55]) * Support Python 3.12 ([#61][i61]) * Add a (skippable) check that the axes have equal aspect ratio ([#62][i62]) +* Use `\\mathregular` for LaTeX micro ([#58][i58]) ### 0.8.1 @@ -530,6 +531,7 @@ ax.add_artist(scalebar) [@jzuhone](https://github.com/jzuhone) [@360tetsu360](https://github.com/360tetsu360) [@jlaehne](https://github.com/jlaehne) +[@Alessandro-Zunino](https://github.com/Alessandro-Zunino) ## License @@ -563,5 +565,6 @@ Copyright (c) 2015-2025 Philippe Pinard [i53]: https://github.com/ppinard/matplotlib-scalebar/pull/53 [i55]: https://github.com/ppinard/matplotlib-scalebar/pull/55 [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 \ No newline at end of file diff --git a/matplotlib_scalebar/dimension.py b/matplotlib_scalebar/dimension.py index 430bc7d..3ced69a 100644 --- a/matplotlib_scalebar/dimension.py +++ b/matplotlib_scalebar/dimension.py @@ -32,7 +32,7 @@ "z": 1e-21, "y": 1e-24, } -_LATEX_MU = "$\\mathrm{\\mu}$" +_LATEX_MU = "$\\mathregular{\\mu}$" class _Dimension(object): From b2570efddde7769cf7c4da9d686521245162adc9 Mon Sep 17 00:00:00 2001 From: Philippe Pinard Date: Fri, 17 Jan 2025 22:05:55 +0000 Subject: [PATCH 16/17] Update version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 022ab62..67bff55 100644 --- a/README.md +++ b/README.md @@ -433,7 +433,7 @@ ax.add_artist(scalebar) ## Release notes -### Dev +### 0.9.0 * Update tooling ([#53][i53]) * Add example gallery ([#50][i50]) From 0d51385a0eeed4f9d8234ea8c05e2012670e513d Mon Sep 17 00:00:00 2001 From: Zezhong Zhang Date: Thu, 20 Feb 2025 20:10:59 +0800 Subject: [PATCH 17/17] [fix] host angstrom in AtomicLengthDimension class and add tests --- matplotlib_scalebar/dimension.py | 37 +++++++++++++++++++++----------- matplotlib_scalebar/scalebar.py | 35 ++++++++++++++++++++++++++++++ tests/test_dimension.py | 14 ++++++++++++ 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/matplotlib_scalebar/dimension.py b/matplotlib_scalebar/dimension.py index 3ced69a..b213a54 100644 --- a/matplotlib_scalebar/dimension.py +++ b/matplotlib_scalebar/dimension.py @@ -25,7 +25,6 @@ "\u00b5": 1e-6, "u": 1e-6, "n": 1e-9, - "A": 1e-10, "p": 1e-12, "f": 1e-15, "a": 1e-18, @@ -82,7 +81,6 @@ def calculate_preferred(self, value, units): if index: newunits, factor = units_factor[index - 1] return base_value / factor, newunits - else: return value, units @@ -112,11 +110,7 @@ def __init__(self): latexrepr = None if prefix == "\u00b5" or prefix == "u": latexrepr = _LATEX_MU + "m" - if prefix == "Å" or prefix == "A" or prefix == "angstrom": - latexrepr = "Å" # Directly add "Å" without appending "m" - self.add_units(prefix, factor, latexrepr) - else: - self.add_units(prefix + "m", factor, latexrepr) + self.add_units(prefix + "m", factor, latexrepr) class SILengthReciprocalDimension(_Dimension): @@ -126,11 +120,7 @@ def __init__(self): latexrepr = "{0}m$^{{-1}}$".format(prefix) if prefix == "\u00b5" or prefix == "u": latexrepr = _LATEX_MU + "m$^{-1}$" - if prefix == "Å" or prefix == "A" or prefix == "angstrom": - latexrepr = "Å$^{-1}$" - self.add_units("1/A", 1 / factor, latexrepr) - else: - self.add_units("1/{0}m".format(prefix), 1 / factor, latexrepr) + self.add_units("1/{0}m".format(prefix), 1 / factor, latexrepr) class ImperialLengthDimension(_Dimension): @@ -175,3 +165,26 @@ def __init__(self): def create_label(self, value, latexrepr): # Overriden to remove space between value and units. return "{}{}".format(value, latexrepr) + + +class AtomicLengthDimension(SILengthDimension): + """Dimension for atomic-scale lengths using Angstrom as the base unit.""" + def __init__(self): + super().__init__() + latexrepr = "$\\mathrm{\\AA}$" + factor = 1e-10 + + self.add_units("angstrom", factor, latexrepr) # Full name + self.add_units("A", factor, latexrepr) + self.add_units("Å", factor, latexrepr) + +class AtomicLengthReciprocalDimension(SILengthReciprocalDimension): + """Dimension for reciprocal atomic-scale lengths using inverse Angstrom as the base unit.""" + def __init__(self): + super().__init__() + latexrepr = "$\\mathrm{\\AA}^{-1}$" + factor = 1e10 + + self.add_units("1/angstrom", factor, latexrepr) # Full name + self.add_units("1/A", factor, latexrepr) + self.add_units("1/Å", factor, latexrepr) \ No newline at end of file diff --git a/matplotlib_scalebar/scalebar.py b/matplotlib_scalebar/scalebar.py index efd5a5d..1409380 100644 --- a/matplotlib_scalebar/scalebar.py +++ b/matplotlib_scalebar/scalebar.py @@ -35,6 +35,8 @@ "IMPERIAL_LENGTH", "ASTRO_LENGTH", "PIXEL_LENGTH", + "ATOMIC_LENGTH", + "ATOMIC_LENGTH_RECIPROCAL", ] # Standard library modules. @@ -71,6 +73,8 @@ AstronomicalLengthDimension, PixelLengthDimension, AngleDimension, + AtomicLengthDimension, + AtomicLengthReciprocalDimension, ) # Globals and constants variables. @@ -132,6 +136,8 @@ def _validate_legend_loc(loc): ASTRO_LENGTH = "astro-length" PIXEL_LENGTH = "pixel-length" ANGLE = "angle" +ATOMIC_LENGTH = "atomic-length" +ATOMIC_LENGTH_RECIPROCAL = "atomic-length-reciprocal" _DIMENSION_LOOKUP = { SI_LENGTH: SILengthDimension, @@ -140,6 +146,8 @@ def _validate_legend_loc(loc): ASTRO_LENGTH: AstronomicalLengthDimension, PIXEL_LENGTH: PixelLengthDimension, ANGLE: AngleDimension, + ATOMIC_LENGTH: AtomicLengthDimension, + ATOMIC_LENGTH_RECIPROCAL: AtomicLengthReciprocalDimension, } @@ -229,6 +237,8 @@ def __init__( * ``:const:`astro-length```: scale bar showing pc, kpc ly, AU, etc. * ``:const:`pixel-length```: scale bar showing px, kpx, Mpx, etc. * ``:const:`angle```: scale bar showing \u00b0, \u2032 or \u2032\u2032. + * ``:const:`atomic-length```: scale bar showing \AA, nm, \u00B5m, etc. + * ``:const:`atomic-length-reciprocal```: scale bar showing 1/\AA, 1/nm, 1/\u00B5m, etc. * a :class:`matplotlib_scalebar.dimension._Dimension` object :type dimension: :class:`str` or :class:`matplotlib_scalebar.dimension._Dimension` @@ -601,8 +611,33 @@ def get_units(self): return self._units def set_units(self, units): + """ + Set the units of the scale bar. + """ + old_units = self._units if hasattr(self, '_units') else None + old_dx = self._dx if hasattr(self, '_dx') else None + + # Auto-detect Angstrom units and switch to atomic dimension + if units in ["Å", "A", "angstrom"]: + self._dimension = _DIMENSION_LOOKUP[ATOMIC_LENGTH]() + elif units in ["1/Å", "1/A", "1/angstrom"]: + self._dimension = _DIMENSION_LOOKUP[ATOMIC_LENGTH_RECIPROCAL]() + elif hasattr(self, '_dimension') and isinstance(self._dimension, (AtomicLengthDimension, AtomicLengthReciprocalDimension)) and units not in ["Å", "A", "angstrom", "1/Å", "1/A", "1/angstrom"]: + # Switch back to SI if changing from Angstrom to non-Angstrom units + if '/' in units: + self._dimension = _DIMENSION_LOOKUP[SI_LENGTH_RECIPROCAL]() + else: + self._dimension = _DIMENSION_LOOKUP[SI_LENGTH]() + if not self.dimension.is_valid_units(units): raise ValueError(f"Invalid unit ({units}) with dimension") + + # Convert dx to the new units if we're changing units + if old_units is not None and old_dx is not None: + # All conversions are now handled by the dimension's convert method + # since factors are relative to meters + self._dx = self.dimension.convert(old_dx, old_units, units) + self._units = units units = property(get_units, set_units) diff --git a/tests/test_dimension.py b/tests/test_dimension.py index b5d9362..69b26b0 100644 --- a/tests/test_dimension.py +++ b/tests/test_dimension.py @@ -11,6 +11,8 @@ SILengthReciprocalDimension, ImperialLengthDimension, PixelLengthDimension, + AtomicLengthDimension, + AtomicLengthReciprocalDimension, _LATEX_MU, ) @@ -37,6 +39,11 @@ (PixelLengthDimension(), 200, "px", 200.0, "px"), (PixelLengthDimension(), 0.02, "px", 0.02, "px"), (PixelLengthDimension(), 0.001, "px", 0.001, "px"), + # Test Angstrom preferred units + (AtomicLengthDimension(), 0.1, "nm", 1, "Å"), + (AtomicLengthDimension(), 10, "Å", 1, "nm"), + (AtomicLengthReciprocalDimension(), 100, "1/nm", 10, "1/Å"), + (AtomicLengthReciprocalDimension(), 0.1, "1/Å", 1, "1/nm"), ], ) def test_calculate_preferred(dim, value, units, expected_value, expected_units): @@ -65,6 +72,13 @@ def test_to_latex(dim, units, expected): (SILengthDimension(), 2, "um", "cm", 2e-4), (PixelLengthDimension(), 2, "kpx", "px", 2000), (PixelLengthDimension(), 2, "px", "kpx", 2e-3), + # Test Angstrom conversions + (AtomicLengthDimension(), 1, "Å", "nm", 0.1), + (AtomicLengthDimension(), 1, "nm", "Å", 10), + (AtomicLengthDimension(), 1, "A", "angstrom", 1), + (AtomicLengthReciprocalDimension(), 1, "1/Å", "1/nm", 10), + (AtomicLengthReciprocalDimension(), 1, "1/nm", "1/Å", 0.1), + (AtomicLengthReciprocalDimension(), 1, "1/A", "1/angstrom", 1), ], ) def test_convert(dim, value, units, newunits, expected_value):