diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 179baa5..0be5fb0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -35,7 +35,7 @@ jobs:
python -m pip install --upgrade pip codecov
pip install --upgrade -e .
pip install -r requirements-test.txt
- - name: Test with nose
- run: nosetests
+ - name: Test
+ run: pytest
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bf0196d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,362 @@
+# matplotlib-scalebar
+
+
+
+
+Provides a new artist for [matplotlib](https://matplotlib.org) to display a scale bar, aka micron bar.
+It is particularly useful when displaying calibrated images plotted using
+`plt.imshow(...)`.
+
+
+
+The artist supports customization either directly from the **ScaleBar** object or from the matplotlibrc.
+
+## Installation
+
+Easiest way to install using `pip`:
+
+```bash
+pip install matplotlib-scalebar
+```
+
+For development installation from the git repository:
+
+```bash
+git clone git@github.com:ppinard/matplotlib-scalebar.git
+pip install -e matplotlib-scalebar
+```
+
+## Getting started
+
+There are many ways to customize the scale bar.
+Examples and explanations of the arguments of the **ScaleBar** class are given [below](#scalebar-arguments), but here is a quick start guide.
+
+The constructor arguments *dx* and *units* specify the pixel dimension.
+For example `ScaleBar(0.2, 'um')` indicates that each pixel is equal to 0.2 micrometer.
+By default, the scale bar uses SI units of length (e.g. m, cm, um, km, etc.).
+See examples below for other system of units.
+
+In this example, we load a sample image from the matplotlib library, create a subplot, plot image, create scale bar and add scale bar as an "artist" of the subplot.
+
+```python
+import numpy as np
+import matplotlib.pyplot as plt
+import matplotlib.cbook as cbook
+from matplotlib_scalebar.scalebar import ScaleBar
+
+# Load image
+with cbook.get_sample_data("s1045.ima.gz") as dfile:
+ im = np.frombuffer(dfile.read(), np.uint16).reshape((256, 256))
+
+# Create subplot
+fig, ax = plt.subplots()
+ax.axis("off")
+
+# Plot image
+ax.imshow(im, cmap="gray")
+
+# Create scale bar
+scalebar = ScaleBar(0.08, "cm", length_fraction=0.25)
+ax.add_artist(scalebar)
+
+# Show
+plt.show()
+```
+
+
+
+## ScaleBar arguments
+
+Here are arguments of the **ScaleBar** class constructor and examples how to use them.
+
+```python
+scalebar = ScaleBar(
+ dx,
+ units="m",
+ dimension="si-length",
+ label=None,
+ length_fraction=None,
+ height_fraction=None,
+ width_fraction=None,
+ location=None,
+ pad=None,
+ border_pad=None,
+ sep=None,
+ frameon=None,
+ color=None,
+ box_color=None,
+ box_alpha=None,
+ scale_loc=None,
+ label_loc=None,
+ font_properties=None,
+ label_formatter=None,
+ scale_formatter=None,
+ fixed_value=None,
+ fixed_units=None,
+ animated=False,
+ rotation=None,
+ )
+```
+
+Each argument can also be changed afterwards using their respective property.
+
+```python
+scalebar.dx = 2.0
+```
+
+The following schematic illustrates the nomenclature used in the definition of the arguments.
+
+
+
+### dx (required)
+
+Size of one pixel in *units* specified by the next argument.
+
+Set *dx* to 1.0 if the axes image has already been calibrated by setting its *extent*.
+
+```python
+fig, ax = plt.subplots()
+ax.axis("off")
+
+ax.imshow(im, cmap="gray", extent=[0, 20.48, 0, 20.48])
+
+scalebar = ScaleBar(1, "cm", length_fraction=0.25)
+ax.add_artist(scalebar)
+```
+
+
+
+**Special notes for geospatial plots**:
+If you are plotting geospatial coordinates (such as scatterplots of the location of structures, [geopandas](http://geopandas.org) geodataframe plots, etc.), *dx* needs to be set differently depending on the coordinate system:
+
+* For UTM based coordinate system, where the X and Y are in meters, simply set `dx = 1`.
+* For WGS or NAD based coordinate system, where X and Y are in latitude (Y) and longitude (X), compute the distance between two points at the latitude (Y) you wish to have the scale represented and are also one full degree of longitude (X) apart, in meters. For example, `dx = great_circle_distance((X, Y), (X + 1, Y))`
+
+### units
+
+Units of *dx*.
+The units needs to be valid for the specified *dimension*.
+Default: `m`.
+
+### dimension
+
+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.
+* `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)
+* a `matplotlib_scalebar.dimension._Dimension` object
+
+```python
+fig, ax = plt.subplots()
+ax.axis("off")
+
+ax.imshow(im, cmap="gray")
+
+scalebar = ScaleBar(0.0315, "in", dimension="imperial-length", length_fraction=0.25)
+ax.add_artist(scalebar)
+```
+
+
+
+### label
+
+Optional label associated with the scale bar.
+Default: `None`, no label is shown.
+The position of the label with respect to the scale bar can be adjusted using *label_loc* argument.
+
+### length_fraction
+
+Desired length of the scale bar as a fraction of the subplot's width.
+Default: `None`, value from matplotlibrc or `0.2`.
+The actual length of the scale bar is automatically determined based on the specified pixel size (*dx* and *units*) and the contraint that the scale value can only take the following numbers: 1, 2, 5, 10, 15, 20, 25, 50, 75, 100, 125, 150, 200, 500 or 750.
+If you want a specific value, see [*fixed_value*](#fixed_value) and [*fixed_units*](#fixed_units).
+
+In the example below, the scale bar for a *length_fraction* of 0.25 and 0.5 is the same because the scale cannot have a value between 2 and 5 mm.
+
+
+
+### height_fraction
+
+**Deprecated**, use *width_fraction*.
+
+### width_fraction
+
+Width of the scale bar as a fraction of the subplot's height.
+Default: `None`, value from matplotlibrc or `0.01`.
+
+### location
+
+A location code, same as matplotlib's legend, either: `upper right`, `upper left`, `lower left`, `lower right`, `right`, `center left`, `center right`, `lower center`, `upper center` or `center`.
+Default: `None`, value from matplotlibrc or `upper right`.
+
+### loc
+
+Alias for *location*.
+
+### pad
+
+Padding inside the box, as a fraction of the font size.
+Default: `None`, value from matplotlibrc or `0.2`.
+
+### border_pad
+
+Padding outside the box, fraction of the font size.
+Default: `None`, value from matplotlibrc or `0.1`.
+
+### sep
+
+Separation in points between the scale bar and scale, and between the scale bar and label.
+Default: `None`, value from matplotlibrc or `5`.
+
+### frameon
+
+Whether to draw a box behind the scale bar, scale and label.
+Default: `None`, value from matplotlibrc or `True`.
+
+### color
+
+Color for the scale bar, scale and label.
+Default: `None`, value from matplotlibrc or `k` (black).
+
+### box_color
+
+Background color of the box.
+Default: `None`, value from matplotlibrc or `w` (white).
+
+### box_alpha
+
+Transparency of box.
+Default: `None`, value from matplotlibrc or `1.0` (opaque).
+
+### scale_loc
+
+Location of the scale with respect to the scale bar.
+Either `bottom`, `top`, `left`, `right`.
+Default: `None`, value from matplotlibrc or `bottom`.
+
+
+
+### label_loc
+
+Location of the label with respect to the scale bar.
+Either `bottom`, `top`, `left`, `right`.
+Default: `None`, value from matplotlibrc or `top`.
+
+### font_properties
+
+Font properties of the scale and label text, specified either as `dict` or `str`.
+See [`FontProperties`](https://matplotlib.org/api/font_manager_api.html#matplotlib.font_manager.FontProperties) for the arguments.
+Default: `None`, default font properties of matplotlib.
+
+### label_formatter
+
+**Deprecated**, use *scale_formatter*.
+
+### scale_formatter
+
+Custom function called to format the scale.
+Needs to take 2 arguments - the scale value and the unit.
+Default: `None` which results in
+
+```python
+scale_formatter = lambda value, unit: f"{value} {unit}"
+```
+
+### fixed_value
+
+Value for the scale.
+The length of the scale bar is calculated based on the specified pixel size *dx*.
+Default: `None`, the value is automatically determined based on *length_fraction*.
+
+### fixed_units
+
+Units of the *fixed_value*.
+Default: `None`, if *fixed value* is not `None`, the units of *dx* are used.
+
+### animated
+
+Animation state.
+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`.
+Note you might have to adjust *scale_loc* and *label_loc* to achieve desired layout.
+Default: `None`, value from matplotlibrc or `horizontal`.
+
+```python
+fig, ax = plt.subplots()
+ax.axis("off")
+
+ax.imshow(im, cmap="gray")
+
+scalebar = ScaleBar(
+ 0.08,
+ "cm",
+ length_fraction=0.25,
+ rotation="vertical",
+ scale_loc="right",
+ border_pad=1,
+ pad=0.5,
+)
+ax.add_artist(scalebar)
+```
+
+
+
+## Release notes
+
+### dev
+
+* Add rotation to display scale bar for the y-axis (#30)
+* New documentation (#32)
+* Deprecate argument *height_fraction*, replaced by *width_fraction* (#32)
+* Deprecate argument *label_formatter*, replaced by *scale_formatter* (#32)
+* Add alias *loc* for *location* (#32)
+
+### 0.6.2
+
+* Fix reciprocal unit (#29)
+
+### 0.6.1
+
+* Add notes about for geospatial plots (#20)
+
+### 0.6.0
+
+* Add angular units (#19)
+* Add blit support and fix documentation (#22)
+* Fix issue with getting the wrong preferred values for the scale bar (#23)
+* Package LICENSE file to distribution (#24)
+
+### 0.5.1
+
+* Remove leftover print statement (#18)
+
+### 0.5.0
+
+* Add pixel unit (#12)
+* Display micro symbol in text mode (#15)
+* Fix error in length of scale bar (#14), the bar was drawn with an edge around it which made it longer than the actual size.
+
+### 0.4.1
+
+* Fix deprecated usage of is_string_like (#11)
+
+### 0.4.0
+
+* Add possibility to specified a fixed value for the scale bar (#9))
+
+## Contributors
+
+@maweigert, @crosbyla, @joschkazj, @AKuederle, @habi, @huangziwei,@SirJohnFranklin, @alexandrejaguar, @parishcm, @wiai, @cosmicshear,@ericore, @seangrogan, @PhilipeRLeal, @din14970
+
+## License
+
+License under the BSD License, compatible with matplotlib.
+
+Copyright (c) 2015-2021 Philippe Pinard
diff --git a/README.rst b/README.rst
deleted file mode 100644
index a5eb97f..0000000
--- a/README.rst
+++ /dev/null
@@ -1,237 +0,0 @@
-matplotlib-scalebar
-===================
-
-.. image:: https://img.shields.io/github/workflow/status/ppinard/matplotlib-scalebar/CI
- :alt: GitHub Workflow Status
-
-.. image:: https://img.shields.io/pypi/v/matplotlib-scalebar
- :alt: PyPI
-
-Provides a new artist for matplotlib to display a scale bar, aka micron bar.
-It is particularly useful when displaying calibrated images plotted using
-plt.imshow(...).
-
-.. image:: https://raw.githubusercontent.com/ppinard/matplotlib-scalebar/master/doc/example1.png
-
-The artist supports customization either directly from the **ScaleBar** object or
-from the matplotlibrc.
-
-Installation
-------------
-
-Easiest way to install using ``pip``::
-
- $ pip install matplotlib-scalebar
-
-For development installation from the git repository::
-
- $ git clone git@github.com:ppinard/matplotlib-scalebar.git
- $ pip install -e matplotlib-scalebar
-
-How to use
-----------
-
-There are two modes of operation:
-
-1. Length, value and units of the scale bar are automatically
- determined based on the specified pixel size *dx* and
- *length_fraction*.
- The value will only take the following numbers:
- 1, 2, 5, 10, 15, 20, 25, 50, 75, 100, 125, 150, 200, 500 or 750.
-
-2. The desired value and units are specified by the user
- (*fixed_value* and *fixed_units*) and the length is calculated
- based on the specified pixel size *dx*.
-
-The constructor arguments *dx* and *units* specify the pixel dimension.
-For example ``scalebar = ScaleBar(0.2, 'um')`` indicates that each pixel is
-equal to 0.2 micrometer.
-If the the axes image has already been calibrated by setting its ``extent``,
-set *dx* to 1.0.
-
-**Special notes for geospatial plots**:
-If you are plotting geospatial coordinates (such as scatterplots of the location of structures, `geopandas `_ geodataframe plots, etc.), ``dx`` needs to be set differently depending on the coordinate system:
-
-* For UTM based coordinate system, where the X and Y are in meters, simply set ``dx = 1``.
-* For WGS or NAD based coordinate system, where X and Y are in latitude (Y) and longitude (X), compute the distance between two points at the latitude (Y) you wish to have the scale represented and are also one full degree of longitude (X) apart, in meters. For example ``dx = great_circle_distance((X, Y), (X + 1, Y))``
-
-The system of units (SI, imperial, etc.) is defined by the argument *dimension*.
-By default, the scale bar uses SI units of length (e.g. m, cm, um, km, etc.).
-See examples below for other system of units.
-
-Example
--------
-
-Here is an example how to add a scale bar::
-
- >>> import matplotlib.pyplot as plt
- >>> import matplotlib.cbook as cbook
- >>> from matplotlib_scalebar.scalebar import ScaleBar
- >>> plt.figure()
- >>> image = plt.imread(cbook.get_sample_data('grace_hopper.png'))
- >>> plt.imshow(image)
- >>> scalebar = ScaleBar(0.2) # 1 pixel = 0.2 meter
- >>> plt.gca().add_artist(scalebar)
- >>> plt.show()
-
-The scale bar also works with reciprocal units,::
-
- >>> from matplotlib_scalebar.scalebar import SI_LENGTH_RECIPROCAL
- >>> scalebar = ScaleBar(0.2, '1/cm', SI_LENGTH_RECIPROCAL) # 1 pixel = 0.2 1/cm
-
-imperial units::
-
- >>> from matplotlib_scalebar.scalebar import IMPERIAL_LENGTH
- >>> scalebar = ScaleBar(0.2, 'ft', IMPERIAL_LENGTH) # 1 pixel = 0.2 feet
-
-.. image:: https://raw.githubusercontent.com/ppinard/matplotlib-scalebar/master/doc/example2.png
-
-and system defined by the **Dimension** class.
-
-ScaleBar arguments
-------------------
-
-Here are parameters of the **ScaleBar** class constructor.
-
-* ``dx``: Size of one pixel in *units* specified by the next argument (required).
- Set ``dx`` to 1.0 if the axes image has already been calibrated by
- setting its ``extent``.
-* ``units``: units of *dx* (default: ``m``)
-* ``dimension``: dimension of *dx* and *units*.
- It can either be equal
-
- * ``si-length``: scale bar showing km, m, cm, etc.
- * ``imperial-length``: scale bar showing in, ft, yd, mi, 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).
- * a ``matplotlib_scalebar.dimension._Dimension`` object
-
-* ``label``: optional label associated with the scale bar
- (default: ``None``, no label is shown)
-* ``length_fraction``: length of the scale bar as a fraction of the
- axes's width (default: ``rcParams['scalebar.lenght_fraction']`` or ``0.2``)
-* ``height_fraction``: height of the scale bar as a fraction of the
- axes's height (default: ``rcParams['scalebar.height_fraction']`` or ``0.01``)
-* ``location``: a location code (same as legend)
- (default: ``rcParams['scalebar.location']`` or ``upper right``)
-* ``pad``: fraction of the font size
- (default: ``rcParams['scalebar.pad']`` or ``0.2``)
-* ``border_pad``: fraction of the font size
- (default: ``rcParams['scalebar.border_pad']`` or ``0.1``)
-* ``sep``: separation between scale bar and label in points
- (default: ``rcParams['scalebar.sep']`` or ``5``)
-* ``frameon``: if ``True``, will draw a box around the scale bar and label
- (default: ``rcParams['scalebar.frameon']`` or ``True``)
-* ``color``: color for the scale bar and label
- (default: ``rcParams['scalebar.color']`` or ``k``)
-* ``box_color``: color of the box (if *frameon*)
- (default: ``rcParams['scalebar.box_color']`` or ``w``)
-* ``box_alpha``: transparency of box
- (default: ``rcParams['scalebar.box_alpha']`` or ``1.0``)
-* ``scale_loc``: either ``bottom``, ``top``, ``left``, ``right``
- (default: ``rcParams['scalebar.scale_loc']`` or ``bottom``)
-* ``label_loc``: either ``bottom``, ``top``, ``left``, ``right``
- (default: ``rcParams['scalebar.label_loc']`` or ``top``)
-* ``font_properties``: font properties of the label text, specified either as
- dict or `fontconfig `_ pattern (XML).
-* ``label_formatter``: custom function called to format the scalebar text.
- Needs to take 2 arguments - the scale value and the unit.
- (default: ``None`` which results in `` ``)
-* ``fixed_value``: value for the scale bar. If ``None``, the value is
- automatically determined based on *length_fraction*.
-* ``fixed_units``: units of the *fixed_value*. If ``None`` and
- *fixed_value* is not ``None``, the units of *dx* are used.
-* ``animated``: animation state (default: ``False``)
-
-matplotlibrc parameters
------------------------
-
-Here are parameters that can be customized in the matplotlibrc file.
-
-* ``scalebar.length_fraction``: length of the scale bar as a fraction of the
- axes's width (default: ``0.2``)
-* ``scalebar.height_fraction``: height of the scale bar as a fraction of the
- axes's height (default: ``0.01``)
-* ``scalebar.location``: a location code (same as legend)
- (default: ``upper right``)
-* ``scalebar.pad``: fraction of the font size (default: ``0.2``)
-* ``scalebar.border_pad``: fraction of the font size (default: ``0.1``)
-* ``scalebar.sep``: separation between scale bar and label in points
- (default: ``5``)
-* ``scalebar.frameon``: if True, will draw a box around the scale bar
- and label (default: ``True``)
-* ``scalebar.color``: color for the scale bar and label (default: ``k``)
-* ``scalebar.box_color``: color of the box (if *frameon*) (default: ``w``)
-* ``scalebar.box_alpha``: transparency of box (default: ``1.0``)
-* ``scale_loc``: either ``bottom``, ``top``, ``left``, ``right`` (default: ``bottom``)
-* ``label_loc``: either ``bottom``, ``top``, ``left``, ``right`` (default: ``top``)
-
-Release notes
--------------
-0.6.2
-^^^^^
-
-* Fix reciprocal unit (`PR#29 `_)
-
-0.6.1
-^^^^^
-
-* Add notes about for geospatial plots (`#20 `_)
-
-0.6.0
-^^^^^
-
-* Add angular units (`#19 `_)
-* Add blit support and fix documentation (`PR#22 `_)
-* Fix issue with getting the wrong preferred values for the scale bar. (`PR#23 `_)
-* Package LICENSE file to distribution. (`PR#24 `_)
-
-0.5.1
-^^^^^
-
-* Remove leftover print statement (`#18 `_)
-
-0.5.0
-^^^^^
-
-* Add pixel unit (`#12 `_)
-* Display micro symbol in text mode (`#15 `_)
-* Fix error in length of scale bar (`#14 `_). The bar was drawn with an edge around it which made it longer than the actual size.
-
-0.4.1
-^^^^^
-
-* Fix deprecated usage of is_string_like (`#11 `_)
-
-0.4.0
-^^^^^
-
-* Add possibility to specified a fixed value for the scale bar (`#9 `_)
-
-Contributors
-------------
-
-`@maweigert `_,
-`@crosbyla `_,
-`@joschkazj `_,
-`@AKuederle `_,
-`@habi `_,
-`@huangziwei `_,
-`@SirJohnFranklin `_,
-`@alexandrejaguar `_,
-`@parishcm `_
-`@wiai `_,
-`@cosmicshear `_,
-`@ericore `_,
-`@seangrogan `_,
-`@PhilipeRLeal `_,
-`@din14970 `_
-
-License
--------
-
-License under the BSD License, compatible with matplotlib.
-
-Copyright (c) 2015-2020 Philippe Pinard
-
diff --git a/doc/argument_dimension.png b/doc/argument_dimension.png
new file mode 100644
index 0000000..a0c9d94
Binary files /dev/null and b/doc/argument_dimension.png differ
diff --git a/doc/argument_dimension.py b/doc/argument_dimension.py
new file mode 100644
index 0000000..b4b3d2f
--- /dev/null
+++ b/doc/argument_dimension.py
@@ -0,0 +1,17 @@
+import numpy as np
+import matplotlib.pyplot as plt
+import matplotlib.cbook as cbook
+from matplotlib_scalebar.scalebar import ScaleBar
+
+with cbook.get_sample_data("s1045.ima.gz") as dfile:
+ im = np.frombuffer(dfile.read(), np.uint16).reshape((256, 256))
+
+fig, ax = plt.subplots()
+ax.axis("off")
+
+ax.imshow(im, cmap="gray")
+
+scalebar = ScaleBar(0.0315, "in", dimension="imperial-length", length_fraction=0.25)
+ax.add_artist(scalebar)
+
+fig.savefig("argument_dimension.png", dpi=60, bbox_inches="tight")
diff --git a/doc/argument_dx.png b/doc/argument_dx.png
new file mode 100644
index 0000000..25d089e
Binary files /dev/null and b/doc/argument_dx.png differ
diff --git a/doc/argument_dx.py b/doc/argument_dx.py
new file mode 100644
index 0000000..543f4e0
--- /dev/null
+++ b/doc/argument_dx.py
@@ -0,0 +1,17 @@
+import numpy as np
+import matplotlib.pyplot as plt
+import matplotlib.cbook as cbook
+from matplotlib_scalebar.scalebar import ScaleBar
+
+with cbook.get_sample_data("s1045.ima.gz") as dfile:
+ im = np.frombuffer(dfile.read(), np.uint16).reshape((256, 256))
+
+fig, ax = plt.subplots()
+ax.axis("off")
+
+ax.imshow(im, cmap="gray", extent=[0, 20.48, 0, 20.48])
+
+scalebar = ScaleBar(1, "cm", length_fraction=0.25)
+ax.add_artist(scalebar)
+
+fig.savefig("argument_dx.png", dpi=60, bbox_inches="tight")
diff --git a/doc/argument_length_fraction.png b/doc/argument_length_fraction.png
new file mode 100644
index 0000000..3536573
Binary files /dev/null and b/doc/argument_length_fraction.png differ
diff --git a/doc/argument_length_fraction.py b/doc/argument_length_fraction.py
new file mode 100644
index 0000000..840baa2
--- /dev/null
+++ b/doc/argument_length_fraction.py
@@ -0,0 +1,43 @@
+import matplotlib.pyplot as plt
+from matplotlib_scalebar.scalebar import ScaleBar
+
+fig, ax = plt.subplots(figsize=(3, 2.8))
+ax.axis("off")
+
+scalebar = ScaleBar(
+ 1,
+ "cm",
+ length_fraction=0.25,
+ width_fraction=0.05,
+ label="length_fraction=0.25",
+ location="upper center",
+ box_color="0.8",
+ pad=0.5,
+)
+ax.add_artist(scalebar)
+
+scalebar = ScaleBar(
+ 1,
+ "cm",
+ length_fraction=0.5,
+ width_fraction=0.05,
+ label="length_fraction=0.5",
+ location="center",
+ box_color="0.8",
+ pad=0.5,
+)
+ax.add_artist(scalebar)
+
+scalebar = ScaleBar(
+ 1,
+ "cm",
+ length_fraction=0.75,
+ width_fraction=0.05,
+ label="length_fraction=0.75",
+ location="lower center",
+ box_color="0.8",
+ pad=0.5,
+)
+ax.add_artist(scalebar)
+
+fig.savefig("argument_length_fraction.png", dpi=100, bbox_inches="tight")
diff --git a/doc/argument_pad.png b/doc/argument_pad.png
new file mode 100644
index 0000000..2f6f6c9
Binary files /dev/null and b/doc/argument_pad.png differ
diff --git a/doc/argument_pad.py b/doc/argument_pad.py
new file mode 100644
index 0000000..aadd543
--- /dev/null
+++ b/doc/argument_pad.py
@@ -0,0 +1,19 @@
+import numpy as np
+import matplotlib.pyplot as plt
+import matplotlib.cbook as cbook
+from matplotlib_scalebar.scalebar import ScaleBar
+
+with cbook.get_sample_data("s1045.ima.gz") as dfile:
+ im = np.frombuffer(dfile.read(), np.uint16).reshape((256, 256))
+
+fig, ax = plt.subplots()
+ax.axis("off")
+
+ax.imshow(im, cmap="gray")
+
+scalebar = ScaleBar(
+ 0.08, "cm", pad=1.0, border_pad=1.0, sep=10.0, frameon=True, length_fraction=0.25
+)
+ax.add_artist(scalebar)
+
+fig.savefig("argument_pad.png", dpi=60, bbox_inches="tight")
diff --git a/doc/argument_rotation.png b/doc/argument_rotation.png
new file mode 100644
index 0000000..000b565
Binary files /dev/null and b/doc/argument_rotation.png differ
diff --git a/doc/argument_rotation.py b/doc/argument_rotation.py
new file mode 100644
index 0000000..5f2b244
--- /dev/null
+++ b/doc/argument_rotation.py
@@ -0,0 +1,25 @@
+import numpy as np
+import matplotlib.pyplot as plt
+import matplotlib.cbook as cbook
+from matplotlib_scalebar.scalebar import ScaleBar
+
+with cbook.get_sample_data("s1045.ima.gz") as dfile:
+ im = np.frombuffer(dfile.read(), np.uint16).reshape((256, 256))
+
+fig, ax = plt.subplots()
+ax.axis("off")
+
+ax.imshow(im, cmap="gray")
+
+scalebar = ScaleBar(
+ 0.08,
+ "cm",
+ length_fraction=0.25,
+ rotation="vertical",
+ scale_loc="right",
+ border_pad=1,
+ pad=0.5,
+)
+ax.add_artist(scalebar)
+
+fig.savefig("argument_rotation.png", dpi=60, bbox_inches="tight")
diff --git a/doc/argument_scale_loc.png b/doc/argument_scale_loc.png
new file mode 100644
index 0000000..0e2f72a
Binary files /dev/null and b/doc/argument_scale_loc.png differ
diff --git a/doc/argument_scale_loc.py b/doc/argument_scale_loc.py
new file mode 100644
index 0000000..151a227
--- /dev/null
+++ b/doc/argument_scale_loc.py
@@ -0,0 +1,46 @@
+import matplotlib.pyplot as plt
+from matplotlib_scalebar.scalebar import ScaleBar
+
+fig, ax = plt.subplots(figsize=(3, 2.8))
+ax.axis("off")
+
+scalebar = ScaleBar(
+ 1,
+ "cm",
+ length_fraction=0.75,
+ width_fraction=0.05,
+ scale_loc="bottom",
+ label="scale_loc=bottom",
+ location="upper center",
+ box_color="0.8",
+ pad=0.5,
+)
+ax.add_artist(scalebar)
+
+scalebar = ScaleBar(
+ 1,
+ "cm",
+ length_fraction=0.75,
+ width_fraction=0.05,
+ scale_loc="right",
+ label="scale_loc=right",
+ location="center",
+ box_color="0.8",
+ pad=0.5,
+)
+ax.add_artist(scalebar)
+
+scalebar = ScaleBar(
+ 1,
+ "cm",
+ length_fraction=0.75,
+ width_fraction=0.05,
+ scale_loc="top",
+ label="scale_loc=top",
+ location="lower center",
+ box_color="0.8",
+ pad=0.5,
+)
+ax.add_artist(scalebar)
+
+fig.savefig("argument_scale_loc.png", dpi=100, bbox_inches="tight")
diff --git a/doc/getting_started.png b/doc/getting_started.png
new file mode 100644
index 0000000..25d089e
Binary files /dev/null and b/doc/getting_started.png differ
diff --git a/doc/getting_started.py b/doc/getting_started.py
new file mode 100644
index 0000000..0b5591d
--- /dev/null
+++ b/doc/getting_started.py
@@ -0,0 +1,21 @@
+import numpy as np
+import matplotlib.pyplot as plt
+import matplotlib.cbook as cbook
+from matplotlib_scalebar.scalebar import ScaleBar
+
+# Load image
+with cbook.get_sample_data("s1045.ima.gz") as dfile:
+ im = np.frombuffer(dfile.read(), np.uint16).reshape((256, 256))
+
+# Create subplot
+fig, ax = plt.subplots()
+ax.axis("off")
+
+# Plot image
+ax.imshow(im, cmap="gray")
+
+# Create scale bar
+scalebar = ScaleBar(0.08, "cm", length_fraction=0.25)
+ax.add_artist(scalebar)
+
+fig.savefig("getting_started.png", dpi=60, bbox_inches="tight")
diff --git a/doc/nomenclature.png b/doc/nomenclature.png
new file mode 100644
index 0000000..dfcad80
Binary files /dev/null and b/doc/nomenclature.png differ
diff --git a/doc/nomenclature.py b/doc/nomenclature.py
new file mode 100644
index 0000000..081e9ca
--- /dev/null
+++ b/doc/nomenclature.py
@@ -0,0 +1,142 @@
+import matplotlib.pyplot as plt
+from matplotlib.patches import FancyArrowPatch
+from matplotlib_scalebar.scalebar import ScaleBar
+
+
+fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(16, 4))
+
+for ax in [ax1, ax2, ax3]:
+ ax.xaxis.set_visible(False)
+ ax.yaxis.set_visible(False)
+
+ scalebar = ScaleBar(
+ 1,
+ "cm",
+ width_fraction=0.05,
+ location="center left",
+ label="label",
+ box_color="0.8",
+ pad=5,
+ border_pad=2,
+ font_properties={"size": "xx-large"},
+ fixed_value=2,
+ fixed_units="mm",
+ )
+ ax.add_artist(scalebar)
+
+# Names
+ax1.annotate(
+ "label",
+ (0.4, 0.6),
+ (0.65, 0.65),
+ arrowprops={"arrowstyle": "->", "connectionstyle": "arc3,rad=0.1", "lw": 3},
+ fontsize=20,
+ zorder=7,
+)
+ax1.annotate(
+ "scale bar",
+ (0.42, 0.5),
+ (0.65, 0.5),
+ arrowprops={"arrowstyle": "->", "connectionstyle": "arc3,rad=0.0", "lw": 3},
+ fontsize=20,
+ zorder=7,
+)
+ax1.annotate(
+ "scale",
+ (0.4, 0.42),
+ (0.65, 0.35),
+ arrowprops={"arrowstyle": "->", "connectionstyle": "arc3,rad=-0.1", "lw": 3},
+ fontsize=20,
+ zorder=7,
+)
+ax1.annotate(
+ "box",
+ (0.3, 0.75),
+ (0.4, 0.9),
+ arrowprops={"arrowstyle": "->", "connectionstyle": "arc3,rad=0.1", "lw": 3},
+ fontsize=20,
+ zorder=7,
+)
+
+# Fractions
+patch = FancyArrowPatch(
+ (0.19, 0.3), (0.405, 0.3), arrowstyle="|-|,widthA=6,widthB=6", zorder=7, lw=2
+)
+ax2.add_patch(patch)
+ax2.annotate(
+ "length",
+ (0.3, 0.25),
+ (0.5, 0.05),
+ arrowprops={"arrowstyle": "->", "connectionstyle": "arc3,rad=-0.3", "lw": 3},
+ fontsize=20,
+ zorder=7,
+)
+
+patch = FancyArrowPatch(
+ (0.45, 0.46), (0.45, 0.54), arrowstyle="|-|,widthA=6,widthB=6", zorder=7, lw=2
+)
+ax2.add_patch(patch)
+ax2.annotate(
+ "width",
+ (0.5, 0.5),
+ (0.65, 0.5),
+ arrowprops={"arrowstyle": "->", "connectionstyle": "arc3", "lw": 3},
+ fontsize=20,
+ zorder=7,
+)
+
+# Pad
+patch = FancyArrowPatch(
+ (0.45, 0.44), (0.45, 0.485), arrowstyle="|-|,widthA=6,widthB=6", zorder=7, lw=2
+)
+ax3.add_patch(patch)
+patch = FancyArrowPatch(
+ (0.45, 0.52), (0.45, 0.565), arrowstyle="|-|,widthA=6,widthB=6", zorder=7, lw=2
+)
+ax3.add_patch(patch)
+ax3.annotate(
+ "sep",
+ (0.47, 0.465),
+ (0.65, 0.5),
+ arrowprops={"arrowstyle": "->", "connectionstyle": "arc3,rad=-0.1", "lw": 3},
+ fontsize=20,
+ zorder=7,
+)
+ax3.annotate(
+ "sep",
+ (0.47, 0.545),
+ (0.65, 0.5),
+ arrowprops={"arrowstyle": "->", "connectionstyle": "arc3,rad=0.1", "lw": 3},
+ fontsize=20,
+ zorder=7,
+)
+
+patch = FancyArrowPatch(
+ (0.0, 0.1), (0.065, 0.1), arrowstyle="|-|,widthA=6,widthB=6", zorder=7, lw=2
+)
+ax3.add_patch(patch)
+ax3.annotate(
+ "border pad",
+ (0.08, 0.1),
+ (0.2, 0.05),
+ arrowprops={"arrowstyle": "->", "connectionstyle": "arc3", "lw": 3},
+ fontsize=20,
+ zorder=7,
+)
+
+patch = FancyArrowPatch(
+ (0.065, 0.9), (0.19, 0.9), arrowstyle="|-|,widthA=6,widthB=6", zorder=7, lw=2
+)
+ax3.add_patch(patch)
+ax3.annotate(
+ "pad",
+ (0.22, 0.9),
+ (0.35, 0.9),
+ arrowprops={"arrowstyle": "->", "connectionstyle": "arc3", "lw": 3},
+ fontsize=20,
+ zorder=7,
+)
+
+plt.tight_layout(pad=2)
+
+fig.savefig("nomenclature.png", dpi=60)
diff --git a/doc/splashscreen.png b/doc/splashscreen.png
new file mode 100644
index 0000000..a857902
Binary files /dev/null and b/doc/splashscreen.png differ
diff --git a/doc/splashscreen.py b/doc/splashscreen.py
new file mode 100644
index 0000000..694f56a
--- /dev/null
+++ b/doc/splashscreen.py
@@ -0,0 +1,26 @@
+import matplotlib.pyplot as plt
+from matplotlib_scalebar.scalebar import ScaleBar
+import requests
+from PIL import Image
+from io import BytesIO
+
+r = requests.get(
+ "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/Misc_pollen.jpg/315px-Misc_pollen.jpg"
+)
+im = Image.open(BytesIO(r.content))
+
+fig = plt.figure(figsize=(4, 4 / 1.3125))
+ax = fig.add_axes([0.0, 0.0, 1.0, 1.0])
+
+ax.imshow(im, "gray")
+
+# According to Wikipedia, "the bean shaped grain in the bottom left corner is about 50 μm long."
+scalebar = ScaleBar(
+ 50 / 37, "um", location="lower right", width_fraction=0.02, border_pad=1, pad=0.5
+)
+ax.add_artist(scalebar)
+
+ax.xaxis.set_visible(False)
+ax.yaxis.set_visible(False)
+
+fig.savefig("splashscreen.png")
diff --git a/matplotlib_scalebar/scalebar.py b/matplotlib_scalebar/scalebar.py
index ed1d3ce..801646d 100644
--- a/matplotlib_scalebar/scalebar.py
+++ b/matplotlib_scalebar/scalebar.py
@@ -38,6 +38,7 @@
# Standard library modules.
import bisect
+import warnings
# Third party modules.
import matplotlib
@@ -73,17 +74,24 @@
# Globals and constants variables.
# Setup of extra parameters in the matplotlic rc
-validate_scale_loc = ValidateInStrings(
- "scale_loc", ["bottom", "top", "right", "left"], ignorecase=True
+_VALID_SCALE_LOCATIONS = ["bottom", "top", "right", "left"]
+_validate_scale_loc = ValidateInStrings(
+ "scale_loc", _VALID_SCALE_LOCATIONS, ignorecase=True
)
-validate_label_loc = ValidateInStrings(
- "label_loc", ["bottom", "top", "right", "left"], ignorecase=True
+
+_VALID_LABEL_LOCATIONS = ["bottom", "top", "right", "left"]
+_validate_label_loc = ValidateInStrings(
+ "label_loc", _VALID_LABEL_LOCATIONS, ignorecase=True
)
+_VALID_ROTATIONS = ["horizontal", "vertical"]
+_validate_rotation = ValidateInStrings("rotation", _VALID_ROTATIONS, ignorecase=True)
+
defaultParams.update(
{
"scalebar.length_fraction": [0.2, validate_float],
- "scalebar.height_fraction": [0.01, validate_float],
+ "scalebar.height_fraction": [0.01, validate_float], # deprecated
+ "scalebar.width_fraction": [0.01, validate_float],
"scalebar.location": ["upper right", validate_legend_loc],
"scalebar.pad": [0.2, validate_float],
"scalebar.border_pad": [0.1, validate_float],
@@ -92,8 +100,9 @@
"scalebar.color": ["k", validate_color],
"scalebar.box_color": ["w", validate_color],
"scalebar.box_alpha": [1.0, validate_float],
- "scalebar.scale_loc": ["bottom", validate_scale_loc],
- "scalebar.label_loc": ["top", validate_label_loc],
+ "scalebar.scale_loc": ["bottom", _validate_scale_loc],
+ "scalebar.label_loc": ["top", _validate_label_loc],
+ "scalebar.rotation": ["horizontal", _validate_rotation],
}
)
@@ -147,7 +156,9 @@ def __init__(
label=None,
length_fraction=None,
height_fraction=None,
+ width_fraction=None,
location=None,
+ loc=None,
pad=None,
border_pad=None,
sep=None,
@@ -159,9 +170,11 @@ def __init__(
label_loc=None,
font_properties=None,
label_formatter=None,
+ scale_formatter=None,
fixed_value=None,
fixed_units=None,
animated=False,
+ rotation=None,
):
"""
Creates a new scale bar.
@@ -204,13 +217,16 @@ def __init__(
This argument is ignored if a *fixed_value* is specified.
:type length_fraction: :class:`float`
- :arg height_fraction: height of the scale bar as a fraction of the
- axes's height (default: rcParams['scalebar.height_fraction'] or ``0.01``)
- :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 location: a location code (same as legend)
(default: rcParams['scalebar.location'] or ``upper right``)
:type location: :class:`str`
+
+ :arg loc: alias for location
+ :type loc: :class:`str`
:arg pad: fraction of the font size
(default: rcParams['scalebar.pad'] or ``0.2``)
@@ -254,10 +270,10 @@ def __init__(
:type font_properties: :class:`matplotlib.font_manager.FontProperties`,
:class:`str` or :class:`dict`
- :arg label_formatter: function used to format the label. Needs to take
+ :arg scale_formatter: function used to format the label. Needs to take
the value (float) and the unit (str) as input and return the label
string.
- :type label_formatter: :class:`func`
+ :type scale_formatter: :class:`func`
:arg fixed_value: value for the scale bar. If ``None``, the value is
automatically determined based on *length_fraction*.
@@ -269,16 +285,40 @@ def __init__(
:arg animated: animation state (default: ``False``)
:type animated: :class`bool`
+
+ :arg rotation: either ``horizontal`` or ``vertical``
+ (default: rcParams['scalebar.rotation'] or ``horizontal``)
+ :type rotation: :class:`str`
"""
Artist.__init__(self)
+ # Deprecation
+ if height_fraction is not None:
+ warnings.warn(
+ "The height_fraction argument was deprecated. Use width_fraction instead.",
+ DeprecationWarning,
+ )
+ width_fraction = width_fraction or height_fraction
+
+ if label_formatter is not None:
+ warnings.warn(
+ "The label_formatter argument was deprecated. Use scale_formatter instead.",
+ DeprecationWarning,
+ )
+ scale_formatter = scale_formatter or label_formatter
+
+ if loc is not None and self._convert_location(loc) != self._convert_location(
+ location
+ ):
+ raise ValueError("loc and location are specified and not equal")
+
self.dx = dx
self.dimension = dimension # Should be initialize before units
self.units = units
self.label = label
self.length_fraction = length_fraction
- self.height_fraction = height_fraction
- self.location = location
+ self.width_fraction = width_fraction
+ self.location = location or loc
self.pad = pad
self.border_pad = border_pad
self.sep = sep
@@ -288,24 +328,12 @@ def __init__(
self.box_alpha = box_alpha
self.scale_loc = scale_loc
self.label_loc = label_loc
- self.label_formatter = label_formatter
-
- if font_properties is None:
- font_properties = FontProperties()
- elif isinstance(font_properties, dict):
- font_properties = FontProperties(**font_properties)
- elif isinstance(font_properties, str):
- font_properties = FontProperties(font_properties)
- else:
- raise TypeError(
- "Unsupported type for `font_properties`. Pass "
- "either a dict or a font config pattern as string."
- )
+ self.scale_formatter = scale_formatter
self.font_properties = font_properties
-
self.fixed_value = fixed_value
self.fixed_units = fixed_units
self.set_animated(animated)
+ self.rotation = rotation
def _calculate_best_length(self, length_px):
dx = self.dx
@@ -336,9 +364,21 @@ def draw(self, renderer, *args, **kwargs):
if self.dx == 0:
return
- # Get parameters
- from matplotlib import rcParams # late import
+ # Late import
+ from matplotlib import rcParams
+ # Deprecation
+ if rcParams.get("scalebar.height_fraction") is not None:
+ warnings.warn(
+ "The scalebar.height_fraction parameter in matplotlibrc is deprecated. "
+ "Use scalebar.width_fraction instead.",
+ DeprecationWarning,
+ )
+ rcParams.setdefault(
+ "scalebar.width_fraction", rcParams["scalebar.height_fraction"]
+ )
+
+ # Get parameters
def _get_value(attr, default):
value = getattr(self, attr)
if value is None:
@@ -346,10 +386,10 @@ def _get_value(attr, default):
return value
length_fraction = _get_value("length_fraction", 0.2)
- height_fraction = _get_value("height_fraction", 0.01)
+ width_fraction = _get_value("width_fraction", 0.01)
location = _get_value("location", "upper right")
if isinstance(location, str):
- location = self._LOCATIONS[location]
+ location = self._LOCATIONS[location.lower()]
pad = _get_value("pad", 0.2)
border_pad = _get_value("border_pad", 0.1)
sep = _get_value("sep", 5)
@@ -357,23 +397,26 @@ def _get_value(attr, default):
color = _get_value("color", "k")
box_color = _get_value("box_color", "w")
box_alpha = _get_value("box_alpha", 1.0)
- scale_loc = _get_value("scale_loc", "bottom")
- label_loc = _get_value("label_loc", "top")
+ scale_loc = _get_value("scale_loc", "bottom").lower()
+ label_loc = _get_value("label_loc", "top").lower()
font_properties = self.font_properties
fixed_value = self.fixed_value
fixed_units = self.fixed_units or self.units
+ rotation = _get_value("rotation", "horizontal").lower()
+ label = self.label
- if font_properties is None:
- textprops = {"color": color}
- else:
- textprops = {"color": color, "fontproperties": font_properties}
+ # Create text properties
+ textprops = {"color": color, "rotation": rotation}
+ if font_properties is not None:
+ textprops["fontproperties"] = font_properties
+ # Calculate value, units and length
ax = self.axes
xlim = ax.get_xlim()
ylim = ax.get_ylim()
- label = self.label
+ if rotation == "vertical":
+ xlim, ylim = ylim, xlim
- # Calculate value, units and length
# Mode 1: Auto
if self.fixed_value is None:
length_px = abs(xlim[1] - xlim[0]) * length_fraction
@@ -385,54 +428,68 @@ def _get_value(attr, default):
units = fixed_units
length_px = self._calculate_exact_length(value, units)
- scale_label = self.label_formatter(value, self.dimension.to_latex(units))
+ scale_text = self.scale_formatter(value, self.dimension.to_latex(units))
- size_vertical = abs(ylim[1] - ylim[0]) * height_fraction
+ width_px = abs(ylim[1] - ylim[0]) * width_fraction
- # Create size bar
- sizebar = AuxTransformBox(ax.transData)
- sizebar.add_artist(
- Rectangle(
+ # Create scale bar
+ if rotation == "horizontal":
+ scale_rect = Rectangle(
(0, 0),
length_px,
- size_vertical,
+ width_px,
fill=True,
facecolor=color,
edgecolor="none",
)
- )
+ else:
+ scale_rect = Rectangle(
+ (0, 0),
+ width_px,
+ length_px,
+ fill=True,
+ facecolor=color,
+ edgecolor="none",
+ )
+
+ scale_bar_box = AuxTransformBox(ax.transData)
+ scale_bar_box.add_artist(scale_rect)
- txtscale = TextArea(scale_label, minimumdescent=False, textprops=textprops)
+ scale_text_box = TextArea(scale_text, minimumdescent=False, textprops=textprops)
if scale_loc in ["bottom", "right"]:
- children = [sizebar, txtscale]
+ children = [scale_bar_box, scale_text_box]
else:
- children = [txtscale, sizebar]
+ children = [scale_text_box, scale_bar_box]
+
if scale_loc in ["bottom", "top"]:
Packer = VPacker
else:
Packer = HPacker
- boxsizebar = Packer(children=children, align="center", pad=0, sep=sep)
- # Create text area
+ scale_box = Packer(children=children, align="center", pad=0, sep=sep)
+
+ # Create label
if label:
- txtlabel = TextArea(label, minimumdescent=False, textprops=textprops)
+ label_box = TextArea(label, minimumdescent=False, textprops=textprops)
else:
- txtlabel = None
+ label_box = None
# Create final offset box
- if txtlabel:
+ if label_box:
if label_loc in ["bottom", "right"]:
- children = [boxsizebar, txtlabel]
+ children = [scale_box, label_box]
else:
- children = [txtlabel, boxsizebar]
+ children = [label_box, scale_box]
+
if label_loc in ["bottom", "top"]:
Packer = VPacker
else:
Packer = HPacker
+
child = Packer(children=children, align="center", pad=0, sep=sep)
else:
- child = boxsizebar
+ child = scale_box
box = AnchoredOffsetbox(
loc=location, pad=pad, borderpad=border_pad, child=child, frameon=frameon
@@ -460,7 +517,10 @@ def set_dimension(self, dimension):
dimension = _DIMENSION_LOOKUP[dimension]()
if not isinstance(dimension, _Dimension):
- raise ValueError("Unknown dimension: %s" % dimension)
+ raise ValueError(
+ f"Unknown dimension: {dimension}. "
+ f"Known dimensions: {', '.join(_DIMENSION_LOOKUP)}"
+ )
self._dimension = dimension
@@ -471,7 +531,7 @@ def get_units(self):
def set_units(self, units):
if not self.dimension.is_valid_units(units):
- raise ValueError("Invalid unit with dimension")
+ raise ValueError(f"Invalid unit ({units}) with dimension")
self._units = units
units = property(get_units, set_units)
@@ -496,30 +556,57 @@ def set_length_fraction(self, fraction):
length_fraction = property(get_length_fraction, set_length_fraction)
- def get_height_fraction(self):
- return self._height_fraction
+ def get_width_fraction(self):
+ return self._width_fraction
- def set_height_fraction(self, 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("Height fraction must be between [0.0, 1.0]")
- self._height_fraction = fraction
+ raise ValueError("Width fraction must be between [0.0, 1.0]")
+ self._width_fraction = fraction
+
+ 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.",
+ 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.",
+ DeprecationWarning,
+ )
+ self.width_fraction = fraction
height_fraction = property(get_height_fraction, set_height_fraction)
+ @classmethod
+ def _convert_location(cls, loc):
+ if isinstance(loc, str):
+ if loc not in cls._LOCATIONS:
+ raise ValueError(
+ f"Unknown location: {loc}. "
+ f"Valid locations: {', '.join(cls._LOCATIONS)}"
+ )
+ loc = cls._LOCATIONS[loc]
+ return loc
+
def get_location(self):
return self._location
def set_location(self, loc):
- if isinstance(loc, str):
- if loc not in self._LOCATIONS:
- raise ValueError("Unknown location code: %s" % loc)
- loc = self._LOCATIONS[loc]
- self._location = loc
+ self._location = self._convert_location(loc)
location = property(get_location, set_location)
+ get_loc = get_location
+ set_loc = set_location
+ loc = location
+
def get_pad(self):
return self._pad
@@ -584,8 +671,11 @@ def get_scale_loc(self):
return self._scale_loc
def set_scale_loc(self, loc):
- if loc is not None and loc not in ["bottom", "top", "right", "left"]:
- raise ValueError("Unknown location: %s" % loc)
+ if loc is not None and loc not in _VALID_SCALE_LOCATIONS:
+ raise ValueError(
+ f"Unknown location: {loc}. "
+ f"Valid locations: {', '.join(_VALID_SCALE_LOCATIONS)}"
+ )
self._scale_loc = loc
scale_loc = property(get_scale_loc, set_scale_loc)
@@ -594,8 +684,12 @@ def get_label_loc(self):
return self._label_loc
def set_label_loc(self, loc):
- if loc is not None and loc not in ["bottom", "top", "right", "left"]:
- raise ValueError("Unknown location: %s" % loc)
+ if loc is not None and loc not in _VALID_LABEL_LOCATIONS:
+ raise ValueError(
+ f"Unknown location: {loc}. "
+ f"Valid locations: {', '.join(_VALID_LABEL_LOCATIONS)}"
+ )
+
self._label_loc = loc
label_loc = property(get_label_loc, set_label_loc)
@@ -604,17 +698,44 @@ def get_font_properties(self):
return self._font_properties
def set_font_properties(self, props):
+ if props is None:
+ props = FontProperties()
+ elif isinstance(props, dict):
+ props = FontProperties(**props)
+ elif isinstance(props, str):
+ props = FontProperties(props)
+ else:
+ raise ValueError(
+ "Unsupported `font_properties`. "
+ "Pass either a dict or a font config pattern as string."
+ )
self._font_properties = props
font_properties = property(get_font_properties, set_font_properties)
- def get_label_formatter(self):
- if self._label_formatter is None:
+ def get_scale_formatter(self):
+ if self._scale_formatter is None:
return self.dimension.create_label
- return self._label_formatter
+ return self._scale_formatter
+
+ def set_scale_formatter(self, scale_formatter):
+ self._scale_formatter = scale_formatter
+
+ scale_formatter = property(get_scale_formatter, set_scale_formatter)
- def set_label_formatter(self, label_formatter):
- self._label_formatter = label_formatter
+ def get_label_formatter(self):
+ warnings.warn(
+ "The get_label_formatter method is deprecated. Use get_scale_formatter instead.",
+ DeprecationWarning,
+ )
+ return self.scale_formatter
+
+ def set_label_formatter(self, scale_formatter):
+ warnings.warn(
+ "The set_label_formatter method is deprecated. Use set_scale_formatter instead.",
+ DeprecationWarning,
+ )
+ self.scale_formatter = scale_formatter
label_formatter = property(get_label_formatter, set_label_formatter)
@@ -633,3 +754,16 @@ def set_fixed_units(self, units):
self._fixed_units = units
fixed_units = property(get_fixed_units, set_fixed_units)
+
+ def get_rotation(self):
+ return self._rotation
+
+ def set_rotation(self, rotation):
+ if rotation is not None and rotation not in _VALID_ROTATIONS:
+ raise ValueError(
+ f"Unknown rotation: {rotation}. "
+ f"Valid locations: {', '.join(_VALID_ROTATIONS)}"
+ )
+ self._rotation = rotation
+
+ rotation = property(get_rotation, set_rotation)
diff --git a/matplotlib_scalebar/test_dimension.py b/matplotlib_scalebar/test_dimension.py
index d2caa88..b4f5951 100644
--- a/matplotlib_scalebar/test_dimension.py
+++ b/matplotlib_scalebar/test_dimension.py
@@ -2,10 +2,9 @@
""" """
# Standard library modules.
-import unittest
-import logging
# Third party modules.
+import pytest
# Local modules.
from matplotlib_scalebar.dimension import (
@@ -19,157 +18,56 @@
# Globals and constants variables.
-class TestSILengthDimension(unittest.TestCase):
- def setUp(self):
- unittest.TestCase.setUp(self)
-
- self.dim = SILengthDimension()
-
- def tearDown(self):
- unittest.TestCase.tearDown(self)
-
- def testcalculate_preferred_km(self):
- value, units = self.dim.calculate_preferred(2000, "m")
- self.assertAlmostEqual(2.0, value, 2)
- self.assertEqual("km", units)
-
- def testcalculate_preferred_m(self):
- value, units = self.dim.calculate_preferred(200, "m")
- self.assertAlmostEqual(200.0, value, 2)
- self.assertEqual("m", units)
-
- def testcalculate_preferred_cm(self):
- value, units = self.dim.calculate_preferred(0.02, "m")
- self.assertAlmostEqual(2.0, value, 2)
- self.assertEqual("cm", units)
-
- def testcalculate_preferred_cm2(self):
- value, units = self.dim.calculate_preferred(0.01, "m")
- self.assertAlmostEqual(1.0, value, 2)
- self.assertEqual("cm", units)
-
- def testcalculate_preferred_mm1(self):
- value, units = self.dim.calculate_preferred(0.002, "m")
- self.assertAlmostEqual(2.0, value, 2)
- self.assertEqual("mm", units)
-
- def testcalculate_preferred_mm2(self):
- value, units = self.dim.calculate_preferred(0.001, "m")
- self.assertAlmostEqual(1.0, value, 2)
- self.assertEqual("mm", units)
-
- def testcalculate_preferred_mm3(self):
- value, units = self.dim.calculate_preferred(0.009, "m")
- self.assertAlmostEqual(9.0, value, 2)
- self.assertEqual("mm", units)
-
- def testcalculate_preferred_nm(self):
- value, units = self.dim.calculate_preferred(2e-7, "m")
- self.assertAlmostEqual(200.0, value, 2)
- self.assertEqual("nm", units)
-
- def testto_latex_cm(self):
- self.assertEqual("cm", self.dim.to_latex("cm"))
-
- def testto_latex_um(self):
- self.assertEqual(_LATEX_MU + "m", self.dim.to_latex(u"\u00b5m"))
-
- def testconvert(self):
- value = self.dim.convert(2, "cm", "um")
- self.assertAlmostEqual(2e4, value, 6)
-
- value = self.dim.convert(2, "um", "cm")
- self.assertAlmostEqual(2e-4, value, 6)
-
-
-class TestImperialLengthDimension(unittest.TestCase):
- def setUp(self):
- unittest.TestCase.setUp(self)
-
- self.dim = ImperialLengthDimension()
-
- def tearDown(self):
- unittest.TestCase.tearDown(self)
-
- def testcalculate_preferred_ft(self):
- value, units = self.dim.calculate_preferred(18, "in")
- self.assertAlmostEqual(1.5, value, 2)
- self.assertEqual("ft", units)
-
- def testcalculate_preferred_yd(self):
- value, units = self.dim.calculate_preferred(120, "in")
- self.assertAlmostEqual(3.33, value, 2)
- self.assertEqual("yd", units)
-
- def testcalculate_preferred_mi(self):
- value, units = self.dim.calculate_preferred(10000, "ft")
- self.assertAlmostEqual(1.8939, value, 2)
- self.assertEqual("mi", units)
-
-
-class TestSILengthReciprocalDimension(unittest.TestCase):
- def setUp(self):
- unittest.TestCase.setUp(self)
-
- self.dim = SILengthReciprocalDimension()
-
- def tearDown(self):
- unittest.TestCase.tearDown(self)
-
- def testcalculate_preferred_cm(self):
- value, units = self.dim.calculate_preferred(0.02, "1/m")
- self.assertAlmostEqual(20.0, value, 2)
- self.assertEqual("1/km", units)
-
- def testcalculate_preferred_mm1(self):
- value, units = self.dim.calculate_preferred(0.002, "1/m")
- self.assertAlmostEqual(2.0, value, 2)
- self.assertEqual("1/km", units)
-
- def testto_latex_cm(self):
- self.assertEqual("cm$^{-1}$", self.dim.to_latex("1/cm"))
-
- def testto_latex_um(self):
- self.assertEqual(_LATEX_MU + "m$^{-1}$", self.dim.to_latex(u"1/\u00b5m"))
-
-
-class TestPixelLengthDimension(unittest.TestCase):
- def setUp(self):
- unittest.TestCase.setUp(self)
-
- self.dim = PixelLengthDimension()
-
- def tearDown(self):
- unittest.TestCase.tearDown(self)
-
- def testcalculate_preferred_kpx(self):
- value, units = self.dim.calculate_preferred(2000, "px")
- self.assertAlmostEqual(2.0, value, 2)
- self.assertEqual("kpx", units)
-
- def testcalculate_preferred_px(self):
- value, units = self.dim.calculate_preferred(200, "px")
- self.assertAlmostEqual(200.0, value, 2)
- self.assertEqual("px", units)
-
- def testcalculate_preferred_subpx(self):
- value, units = self.dim.calculate_preferred(0.02, "px")
- self.assertEqual("px", units)
- self.assertAlmostEqual(0.02, value, 2)
-
- def testcalculate_preferred_subpx2(self):
- value, units = self.dim.calculate_preferred(0.001, "px")
- self.assertAlmostEqual(0.001, value, 3)
- self.assertEqual("px", units)
-
- def testconvert(self):
- value = self.dim.convert(2, "kpx", "px")
- self.assertAlmostEqual(2000, value, 6)
-
- value = self.dim.convert(2, "px", "kpx")
- self.assertAlmostEqual(2e-3, value, 6)
-
-
-if __name__ == "__main__": # pragma: no cover
- logging.getLogger().setLevel(logging.DEBUG)
- unittest.main()
+@pytest.mark.parametrize(
+ "dim,value,units,expected_value,expected_units",
+ [
+ (SILengthDimension(), 2000, "m", 2.0, "km"),
+ (SILengthDimension(), 200, "m", 200, "m"),
+ (SILengthDimension(), 0.02, "m", 2.0, "cm"),
+ (SILengthDimension(), 0.01, "m", 1.0, "cm"),
+ (SILengthDimension(), 0.002, "m", 2, "mm"),
+ (SILengthDimension(), 0.001, "m", 1, "mm"),
+ (SILengthDimension(), 0.009, "m", 9, "mm"),
+ (SILengthDimension(), 2e-7, "m", 200, "nm"),
+ (ImperialLengthDimension(), 18, "in", 1.5, "ft"),
+ (ImperialLengthDimension(), 120, "in", 3.333, "yd"),
+ (ImperialLengthDimension(), 10000, "ft", 1.8939, "mi"),
+ (SILengthReciprocalDimension(), 0.02, "1/m", 20.0, "1/km"),
+ (SILengthReciprocalDimension(), 0.002, "1/m", 2.0, "1/km"),
+ (PixelLengthDimension(), 2000, "px", 2.0, "kpx"),
+ (PixelLengthDimension(), 200, "px", 200.0, "px"),
+ (PixelLengthDimension(), 0.02, "px", 0.02, "px"),
+ (PixelLengthDimension(), 0.001, "px", 0.001, "px"),
+ ],
+)
+def test_calculate_preferred(dim, value, units, expected_value, expected_units):
+ value, units = dim.calculate_preferred(value, units)
+ assert value == pytest.approx(expected_value, abs=1e-3)
+ assert units == expected_units
+
+
+@pytest.mark.parametrize(
+ "dim,units,expected",
+ [
+ (SILengthDimension(), "cm", "cm"),
+ (SILengthDimension(), u"\u00b5m", _LATEX_MU + "m"),
+ (SILengthReciprocalDimension(), "1/cm", "cm$^{-1}$"),
+ (SILengthReciprocalDimension(), u"1/\u00b5m", _LATEX_MU + "m$^{-1}$"),
+ ],
+)
+def test_to_latex(dim, units, expected):
+ assert dim.to_latex(units) == expected
+
+
+@pytest.mark.parametrize(
+ "dim,value,units,newunits,expected_value",
+ [
+ (SILengthDimension(), 2, "cm", "um", 2e4),
+ (SILengthDimension(), 2, "um", "cm", 2e-4),
+ (PixelLengthDimension(), 2, "kpx", "px", 2000),
+ (PixelLengthDimension(), 2, "px", "kpx", 2e-3),
+ ],
+)
+def test_convert(dim, value, units, newunits, expected_value):
+ value = dim.convert(value, units, newunits)
+ assert value == pytest.approx(expected_value, abs=1e-6)
diff --git a/matplotlib_scalebar/test_scalebar.py b/matplotlib_scalebar/test_scalebar.py
index 5027ddc..2642cda 100644
--- a/matplotlib_scalebar/test_scalebar.py
+++ b/matplotlib_scalebar/test_scalebar.py
@@ -9,18 +9,11 @@
matplotlib.use("agg")
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import cleanup
+from matplotlib.font_manager import FontProperties
import numpy as np
-from nose.tools import (
- assert_equal,
- assert_almost_equal,
- assert_is_none,
- assert_true,
- assert_false,
- assert_raises,
- raises,
-)
+import pytest
# Local modules.
from matplotlib_scalebar.scalebar import ScaleBar
@@ -28,9 +21,11 @@
# Globals and constants variables.
-def create_figure():
+@pytest.fixture
+@cleanup
+def scalebar():
fig = plt.figure()
- ax = fig.add_subplot("111")
+ ax = fig.add_subplot(111)
data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
ax.imshow(data)
@@ -38,238 +33,249 @@ def create_figure():
scalebar = ScaleBar(0.5)
ax.add_artist(scalebar)
- return fig, ax, scalebar
+ yield scalebar
+ plt.draw()
-@cleanup
-def test_scalebar_draw():
- fig = plt.figure()
- ax = fig.add_subplot("111")
- data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
- ax.imshow(data)
+def test_scalebar_dx_m(scalebar):
+ assert scalebar.get_dx() == pytest.approx(0.5, abs=1e-2)
+ assert scalebar.dx == pytest.approx(0.5, abs=1e-2)
- scalebar = ScaleBar(0.5, fixed_value=5.0)
- ax.add_artist(scalebar)
-
- plt.draw()
+ scalebar.set_dx(0.2)
+ assert scalebar.get_dx() == pytest.approx(0.2, abs=1e-2)
+ assert scalebar.dx == pytest.approx(0.2, abs=1e-2)
+ scalebar.dx = 0.1
+ assert scalebar.get_dx() == pytest.approx(0.1, abs=1e-2)
+ assert scalebar.dx == pytest.approx(0.1, abs=1e-2)
-@cleanup
-def test_scalebar_draw_fixed():
- create_figure()
- plt.draw()
+def test_scalebar_length_fraction(scalebar):
+ assert scalebar.get_length_fraction() is None
+ assert scalebar.length_fraction is None
-@cleanup
-def test_scalebar_dx_m():
- _fig, _ax, scalebar = create_figure()
+ scalebar.set_length_fraction(0.2)
+ assert scalebar.get_length_fraction() == pytest.approx(0.2, abs=1e-2)
+ assert scalebar.length_fraction == pytest.approx(0.2, abs=1e-2)
- assert_almost_equal(0.5, scalebar.get_dx())
- assert_almost_equal(0.5, scalebar.dx)
+ scalebar.length_fraction = 0.1
+ assert scalebar.get_length_fraction() == pytest.approx(0.1, abs=1e-2)
+ assert scalebar.length_fraction == pytest.approx(0.1, abs=1e-2)
- scalebar.set_dx(0.2)
- assert_almost_equal(0.2, scalebar.get_dx())
- assert_almost_equal(0.2, scalebar.dx)
+ with pytest.raises(ValueError):
+ scalebar.set_length_fraction(0.0)
- scalebar.dx = 0.1
- assert_almost_equal(0.1, scalebar.get_dx())
- assert_almost_equal(0.1, scalebar.dx)
+ with pytest.raises(ValueError):
+ scalebar.set_length_fraction(1.1)
-@cleanup
-def test_scalebar_length_fraction():
- _fig, _ax, scalebar = create_figure()
+def test_scalebar_height_fraction(scalebar):
+ with pytest.deprecated_call():
+ assert scalebar.get_height_fraction() is None
- assert_is_none(scalebar.get_length_fraction())
- assert_is_none(scalebar.length_fraction)
+ with pytest.deprecated_call():
+ assert scalebar.height_fraction is None
- scalebar.set_length_fraction(0.2)
- assert_almost_equal(0.2, scalebar.get_length_fraction())
- assert_almost_equal(0.2, scalebar.length_fraction)
+ with pytest.deprecated_call():
+ scalebar.set_height_fraction(0.2)
- scalebar.length_fraction = 0.1
- assert_almost_equal(0.1, scalebar.get_length_fraction())
- assert_almost_equal(0.1, scalebar.length_fraction)
+ assert scalebar.get_height_fraction() == pytest.approx(0.2, abs=1e-2)
+ assert scalebar.height_fraction == pytest.approx(0.2, abs=1e-2)
- assert_raises(ValueError, scalebar.set_length_fraction, 0.0)
- assert_raises(ValueError, scalebar.set_length_fraction, 1.1)
+ with pytest.deprecated_call():
+ scalebar.height_fraction = 0.1
+ assert scalebar.get_height_fraction() == pytest.approx(0.1, abs=1e-2)
+ assert scalebar.height_fraction == pytest.approx(0.1, abs=1e-2)
-@cleanup
-def test_scalebar_height_fraction():
- _fig, _ax, scalebar = create_figure()
+ with pytest.raises(ValueError), pytest.deprecated_call():
+ scalebar.set_height_fraction(0.0)
- assert_is_none(scalebar.get_height_fraction())
- assert_is_none(scalebar.height_fraction)
+ with pytest.raises(ValueError), pytest.deprecated_call():
+ scalebar.set_height_fraction(1.1)
- scalebar.set_height_fraction(0.2)
- assert_almost_equal(0.2, scalebar.get_height_fraction())
- assert_almost_equal(0.2, scalebar.height_fraction)
- scalebar.height_fraction = 0.1
- assert_almost_equal(0.1, scalebar.get_height_fraction())
- assert_almost_equal(0.1, scalebar.height_fraction)
+def test_scalebar_location(scalebar):
+ assert scalebar.get_location() is None
+ assert scalebar.location is None
- assert_raises(ValueError, scalebar.set_height_fraction, 0.0)
- assert_raises(ValueError, scalebar.set_height_fraction, 1.1)
+ scalebar.set_location("upper right")
+ assert scalebar.get_location() == 1
+ assert scalebar.location == 1
+ scalebar.location = "lower left"
+ assert scalebar.get_location() == 3
+ assert scalebar.location == 3
-@cleanup
-def test_scalebar_location():
- _fig, _ax, scalebar = create_figure()
- assert_is_none(scalebar.get_location())
- assert_is_none(scalebar.location)
+def test_scalebar_loc(scalebar):
+ assert scalebar.get_loc() is None
+ assert scalebar.loc is None
scalebar.set_location("upper right")
- assert_equal(1, scalebar.get_location())
- assert_equal(1, scalebar.location)
+ assert scalebar.get_loc() == 1
+ assert scalebar.loc == 1
scalebar.location = "lower left"
- assert_equal(3, scalebar.get_location())
- assert_equal(3, scalebar.location)
+ assert scalebar.get_loc() == 3
+ assert scalebar.loc == 3
+ scalebar.set_loc("lower right")
+ assert scalebar.get_loc() == 4
+ assert scalebar.loc == 4
-@cleanup
-def test_scalebar_pad():
- _fig, _ax, scalebar = create_figure()
+ scalebar.location = "upper left"
+ assert scalebar.get_loc() == 2
+ assert scalebar.loc == 2
- assert_is_none(scalebar.get_pad())
- assert_is_none(scalebar.pad)
+ with pytest.raises(ValueError):
+ ScaleBar(1.0, loc="upper right", location="upper left")
- scalebar.set_pad(4)
- assert_almost_equal(4, scalebar.get_pad())
- assert_almost_equal(4, scalebar.pad)
+ with pytest.raises(ValueError):
+ ScaleBar(1.0, loc="upper right", location=2)
- scalebar.pad = 5
- assert_almost_equal(5, scalebar.get_pad())
- assert_almost_equal(5, scalebar.pad)
+def test_scalebar_pad(scalebar):
+ assert scalebar.get_pad() is None
+ assert scalebar.pad is None
+
+ scalebar.set_pad(4.0)
+ assert scalebar.get_pad() == pytest.approx(4.0, abs=1e-2)
+ assert scalebar.pad == pytest.approx(4.0, abs=1e-2)
+
+ scalebar.pad = 5.0
+ assert scalebar.get_pad() == pytest.approx(5.0, abs=1e-2)
+ assert scalebar.pad == pytest.approx(5.0, abs=1e-2)
-@cleanup
-def test_scalebar_border_pad():
- _fig, _ax, scalebar = create_figure()
- assert_is_none(scalebar.get_border_pad())
- assert_is_none(scalebar.border_pad)
+def test_scalebar_border_pad(scalebar):
+ assert scalebar.get_border_pad() is None
+ assert scalebar.border_pad is None
scalebar.set_border_pad(4)
- assert_almost_equal(4, scalebar.get_border_pad())
- assert_almost_equal(4, scalebar.border_pad)
+ assert scalebar.get_border_pad() == pytest.approx(4.0, abs=1e-2)
+ assert scalebar.border_pad == pytest.approx(4.0, abs=1e-2)
scalebar.border_pad = 5
- assert_almost_equal(5, scalebar.get_border_pad())
- assert_almost_equal(5, scalebar.border_pad)
+ assert scalebar.get_border_pad() == pytest.approx(5.0, abs=1e-2)
+ assert scalebar.border_pad == pytest.approx(5.0, abs=1e-2)
-@cleanup
-def test_scalebar_sep():
- _fig, _ax, scalebar = create_figure()
-
- assert_is_none(scalebar.get_sep())
- assert_is_none(scalebar.sep)
+def test_scalebar_sep(scalebar):
+ assert scalebar.get_sep() is None
+ assert scalebar.sep is None
scalebar.set_sep(4)
- assert_almost_equal(4, scalebar.get_sep())
- assert_almost_equal(4, scalebar.sep)
+ assert scalebar.get_sep() == pytest.approx(4.0, abs=1e-2)
+ assert scalebar.sep == pytest.approx(4.0, abs=1e-2)
scalebar.sep = 5
- assert_almost_equal(5, scalebar.get_sep())
- assert_almost_equal(5, scalebar.sep)
+ assert scalebar.get_sep() == pytest.approx(5.0, abs=1e-2)
+ assert scalebar.sep == pytest.approx(5.0, abs=1e-2)
-@cleanup
-def test_scalebar_frameon():
- _fig, _ax, scalebar = create_figure()
-
- assert_is_none(scalebar.get_frameon())
- assert_is_none(scalebar.frameon)
+def test_scalebar_frameon(scalebar):
+ assert scalebar.get_frameon() is None
+ assert scalebar.frameon is None
scalebar.set_frameon(True)
- assert_true(scalebar.get_frameon())
- assert_true(scalebar.frameon)
+ assert scalebar.get_frameon()
+ assert scalebar.frameon
scalebar.frameon = False
- assert_false(scalebar.get_frameon())
- assert_false(scalebar.frameon)
+ assert not scalebar.get_frameon()
+ assert not scalebar.frameon
-@cleanup
-def test_scalebar_font_properties():
- font_settings = dict(family="serif", size=9)
- scalebar = ScaleBar(0.5, font_properties=font_settings)
+def test_scalebar_font_properties(scalebar):
+ assert isinstance(scalebar.get_font_properties(), FontProperties)
+ assert isinstance(scalebar.font_properties, FontProperties)
- assert_equal(scalebar.font_properties.get_family(), ["serif"])
- assert_equal(scalebar.font_properties.get_size(), 9)
+ scalebar.set_font_properties(dict(family="serif", size=9))
+ assert scalebar.font_properties.get_family() == ["serif"]
+ assert scalebar.font_properties.get_size() == 9
+ scalebar.font_properties = dict(family="sans serif", size=12)
+ assert scalebar.font_properties.get_family() == ["sans serif"]
+ assert scalebar.font_properties.get_size() == 12
-@cleanup
-@raises(TypeError)
-def test_scalebar_font_properties_invalid_type():
- ScaleBar(0.5, font_properties=2.0)
+ with pytest.raises(ValueError):
+ scalebar.set_font_properties(2.0)
+ with pytest.raises(ValueError):
+ scalebar.font_properties = 2.0
-def test_matplotlibrc():
- matplotlib.rcParams["scalebar.box_color"] = "r"
+def test_matplotlibrc(scalebar):
+ matplotlib.rcParams["scalebar.box_color"] = "r"
-@cleanup
-def test_scalebar_fixed_value():
- _fig, _ax, scalebar = create_figure()
- assert_is_none(scalebar.get_fixed_value())
- assert_is_none(scalebar.fixed_value)
+def test_scalebar_fixed_value(scalebar):
+ assert scalebar.get_fixed_value() is None
+ assert scalebar.fixed_value is None
scalebar.set_fixed_value(0.2)
- assert_almost_equal(0.2, scalebar.get_fixed_value())
- assert_almost_equal(0.2, scalebar.fixed_value)
+ assert scalebar.get_fixed_value() == pytest.approx(0.2, abs=1e-2)
+ assert scalebar.fixed_value == pytest.approx(0.2, abs=1e-2)
scalebar.fixed_value = 0.1
- assert_almost_equal(0.1, scalebar.get_fixed_value())
- assert_almost_equal(0.1, scalebar.fixed_value)
+ assert scalebar.get_fixed_value() == pytest.approx(0.1, abs=1e-2)
+ assert scalebar.fixed_value == pytest.approx(0.1, abs=1e-2)
-@cleanup
-def test_scalebar_fixed_units():
- _fig, _ax, scalebar = create_figure()
-
- assert_is_none(scalebar.get_fixed_units())
- assert_is_none(scalebar.fixed_units)
+def test_scalebar_fixed_units(scalebar):
+ assert scalebar.get_fixed_units() is None
+ assert scalebar.fixed_units is None
scalebar.set_fixed_units("m")
- assert_equal("m", scalebar.get_fixed_units())
- assert_equal("m", scalebar.fixed_units)
+ assert scalebar.get_fixed_units() == "m"
+ assert scalebar.fixed_units == "m"
scalebar.fixed_units = "um"
- assert_equal("um", scalebar.get_fixed_units())
- assert_equal("um", scalebar.fixed_units)
+ assert scalebar.get_fixed_units() == "um"
+ assert scalebar.fixed_units == "um"
-@cleanup
-def test_custom_label_format():
- _fig, _ax, scalebar = create_figure()
+def test_scale_formatter(scalebar):
+ scalebar.dx = 1
+ scalebar.units = "m"
+ _length, value, units = scalebar._calculate_best_length(10)
+
+ assert scalebar.scale_formatter(value, units) == "5 m"
+
+ scalebar.scale_formatter = lambda *_: "test"
+ assert scalebar.scale_formatter(value, units) == "test"
+
+ scalebar.scale_formatter = lambda value, unit: "{} {}".format(unit, value)
+ assert scalebar.scale_formatter(value, units) == "m 5"
+
+
+def test_label_formatter(scalebar):
scalebar.dx = 1
scalebar.units = "m"
_length, value, units = scalebar._calculate_best_length(10)
- scale_label = scalebar.label_formatter(value, units)
- assert_equal(scale_label, "5 m")
+ with pytest.deprecated_call():
+ assert scalebar.label_formatter(value, units) == "5 m"
+
+ with pytest.deprecated_call():
+ scalebar.label_formatter = lambda *_: "test"
+ assert scalebar.label_formatter(value, units) == "test"
- scalebar.label_formatter = lambda value, unit: "test"
- scale_label = scalebar.label_formatter(value, units)
- assert_equal(scale_label, "test")
+ with pytest.deprecated_call():
+ scalebar.label_formatter = lambda value, unit: "{} {}".format(unit, value)
+ assert scalebar.label_formatter(value, units) == "m 5"
- scalebar.label_formatter = lambda value, unit: "{} {}".format(unit, value)
- scale_label = scalebar.label_formatter(value, units)
- assert_equal(scale_label, "m 5")
+@pytest.mark.parametrize("rotation", ["horizontal", "vertical"])
+def test_rotation(scalebar, rotation):
+ assert scalebar.get_rotation() is None
+ assert scalebar.rotation is None
-if __name__ == "__main__":
- import nose
- import sys
+ scalebar.set_rotation(rotation)
+ assert scalebar.get_rotation() == rotation
+ assert scalebar.rotation == rotation
- args = ["-s", "--with-doctest"]
- argv = sys.argv
- argv = argv[:1] + args + argv[1:]
- nose.runmodule(argv=argv, exit=False)
+ with pytest.raises(ValueError):
+ scalebar.set_rotation("h")
diff --git a/requirements-test.txt b/requirements-test.txt
index f3c7e8e..55b033e 100644
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -1 +1 @@
-nose
+pytest
\ No newline at end of file
diff --git a/setup.py b/setup.py
index e6affe4..450c0a4 100644
--- a/setup.py
+++ b/setup.py
@@ -13,7 +13,7 @@
BASEDIR = Path(__file__).parent.resolve()
# Get the long description from the relevant file
-with open(BASEDIR.joinpath("README.rst"), "r") as f:
+with open(BASEDIR.joinpath("README.md"), "r") as f:
long_description = f.read()
setup(
@@ -21,6 +21,7 @@
version=versioneer.get_version(),
description="Artist for matplotlib to display a scale bar",
long_description=long_description,
+ long_description_content_type="text/markdown",
author="Philippe Pinard",
author_email="philippe.pinard@gmail.com",
maintainer="Philippe Pinard",
@@ -40,6 +41,5 @@
package_data={},
install_requires=["matplotlib"],
zip_safe=True,
- test_suite="nose.collector",
cmdclass=versioneer.get_cmdclass(),
)