From 91f6d1635150cb752273378851e7e1079ea92fd3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Thu, 1 Jun 2023 16:53:53 +0200
Subject: [PATCH 001/238] added a Feature Histogram Widget

Co-Authored-By: Marcelo Zoccoler <26173597+zoccoler@users.noreply.github.com>
---
 src/napari_matplotlib/napari.yaml             |   7 +
 src/napari_matplotlib/scatter.py              | 123 +++++++++++++++++-
 src/napari_matplotlib/tests/test_histogram.py |  21 ++-
 3 files changed, 149 insertions(+), 2 deletions(-)

diff --git a/src/napari_matplotlib/napari.yaml b/src/napari_matplotlib/napari.yaml
index b736592b..71af0ca6 100644
--- a/src/napari_matplotlib/napari.yaml
+++ b/src/napari_matplotlib/napari.yaml
@@ -14,6 +14,10 @@ contributions:
       python_name: napari_matplotlib:FeaturesScatterWidget
       title: Make a scatter plot of layer features
 
+    - id: napari-matplotlib.features_histogram
+      python_name: napari_matplotlib:FeaturesHistogramWidget
+      title: Plot feature histograms
+
     - id: napari-matplotlib.slice
       python_name: napari_matplotlib:SliceWidget
       title: Plot a 1D slice
@@ -28,5 +32,8 @@ contributions:
     - command: napari-matplotlib.features_scatter
       display_name: FeaturesScatter
 
+    - command: napari-matplotlib.features_histogram
+      display_name: FeaturesHistogram
+
     - command: napari-matplotlib.slice
       display_name: 1D slice
diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py
index 3b0f918c..b3c1147b 100644
--- a/src/napari_matplotlib/scatter.py
+++ b/src/napari_matplotlib/scatter.py
@@ -9,7 +9,7 @@
 from .base import NapariMPLWidget
 from .util import Interval
 
-__all__ = ["ScatterWidget", "FeaturesScatterWidget"]
+__all__ = ["ScatterWidget", "FeaturesScatterWidget", "FeaturesHistogramWidget"]
 
 
 class ScatterBaseWidget(NapariMPLWidget):
@@ -222,3 +222,124 @@ def _on_update_layers(self) -> None:
         # reset the axis keys
         self._x_axis_key = None
         self._y_axis_key = None
+
+
+class FeaturesHistogramWidget(NapariMPLWidget):
+    n_layers_input = Interval(1, 1)
+    # All layers that have a .features attributes
+    input_layer_types = (
+        napari.layers.Labels,
+        napari.layers.Points,
+        napari.layers.Shapes,
+        napari.layers.Tracks,
+        napari.layers.Vectors,
+    )
+
+    def __init__(self, napari_viewer: napari.viewer.Viewer):
+        super().__init__(napari_viewer)
+        self.axes = self.canvas.figure.subplots()
+
+        self._key_selection_widget = magicgui(
+            self._set_axis_keys,
+            x_axis_key={"choices": self._get_valid_axis_keys},
+            call_button="plot",
+        )
+        self.layout().addWidget(self._key_selection_widget.native)
+
+        self.update_layers(None)
+
+    def clear(self) -> None:
+        """
+        Clear the axes.
+        """
+        self.axes.clear()
+
+        self.layout().addWidget(self._key_selection_widget.native)
+
+    @property
+    def x_axis_key(self) -> Optional[str]:
+        """Key to access x axis data from the FeaturesTable"""
+        return self._x_axis_key
+
+    @x_axis_key.setter
+    def x_axis_key(self, key: Optional[str]) -> None:
+        self._x_axis_key = key
+        self._draw()
+
+    def _set_axis_keys(self, x_axis_key: str) -> None:
+        """Set both axis keys and then redraw the plot"""
+        self._x_axis_key = x_axis_key
+        self._draw()
+
+    def _get_valid_axis_keys(
+        self, combo_widget: Optional[ComboBox] = None
+    ) -> List[str]:
+        """
+        Get the valid axis keys from the layer FeatureTable.
+
+        Returns
+        -------
+        axis_keys : List[str]
+            The valid axis keys in the FeatureTable. If the table is empty
+            or there isn't a table, returns an empty list.
+        """
+        if len(self.layers) == 0 or not (hasattr(self.layers[0], "features")):
+            return []
+        else:
+            return self.layers[0].features.keys()
+
+    def _get_data(self) -> Tuple[List[np.ndarray], str, str]:
+        """Get the plot data.
+
+        Returns
+        -------
+        data : List[np.ndarray]
+            List contains X and Y columns from the FeatureTable. Returns
+            an empty array if nothing to plot.
+        x_axis_name : str
+            The title to display on the x axis. Returns
+            an empty string if nothing to plot.
+        """
+        if not hasattr(self.layers[0], "features"):
+            # if the selected layer doesn't have a featuretable,
+            # skip draw
+            return [], ""
+
+        feature_table = self.layers[0].features
+
+        if (
+            (len(feature_table) == 0)
+            or (self.x_axis_key is None)
+        ):
+            return [], ""
+
+        data = feature_table[self.x_axis_key]
+        x_axis_name = self.x_axis_key.replace("_", " ")
+
+        return data, x_axis_name
+
+    def _on_update_layers(self) -> None:
+        """
+        This is called when the layer selection changes by
+        ``self.update_layers()``.
+        """
+        if hasattr(self, "_key_selection_widget"):
+            self._key_selection_widget.reset_choices()
+
+        # reset the axis keys
+        self._x_axis_key = None
+
+    def draw(self) -> None:
+        """Clear the axes and histogram the currently selected layer/slice."""
+
+        data, x_axis_name = self._get_data()
+
+        if len(data) == 0:
+            return
+
+        _, _, _ = self.axes.hist(data, bins=50, edgecolor='white',
+                                 linewidth=0.3)
+
+        # # set ax labels
+        self.axes.set_xlabel(x_axis_name)
+        self.axes.set_ylabel('Counts [#]')
diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py
index f497a1a9..54b3ad29 100644
--- a/src/napari_matplotlib/tests/test_histogram.py
+++ b/src/napari_matplotlib/tests/test_histogram.py
@@ -1,4 +1,4 @@
-from napari_matplotlib import HistogramWidget
+from napari_matplotlib import HistogramWidget, FeaturesHistogramWidget
 
 
 def test_example_q_widget(make_napari_viewer, image_data):
@@ -6,3 +6,22 @@ def test_example_q_widget(make_napari_viewer, image_data):
     viewer = make_napari_viewer()
     viewer.add_image(image_data[0], **image_data[1])
     HistogramWidget(viewer)
+
+def test_feature_histogram(make_napari_viewer):
+
+    import numpy as np
+
+    n_points = 1000
+    random_points = np.random.random((n_points,3))*10
+    feature1 = np.random.random(n_points)
+    feature2 = np.random.normal(size=n_points)
+
+    viewer = make_napari_viewer()
+    viewer.add_points(random_points, properties={'feature1': feature1, 'feature2': feature2}, face_color='feature1', size=1)
+
+    widget = FeaturesHistogramWidget(viewer)
+    viewer.window.add_dock_widget(widget)
+    widget._set_axis_keys('feature1')
+    widget._key_selection_widget()
+    widget._set_axis_keys('feature2')
+    widget._key_selection_widget()

From 230a303bda3edb39bdb2f19e0649b28df96a1e59 Mon Sep 17 00:00:00 2001
From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com>
Date: Mon, 26 Jun 2023 11:35:30 +0200
Subject: [PATCH 002/238] Update src/napari_matplotlib/scatter.py

Co-authored-by: David Stansby <dstansby@gmail.com>
---
 src/napari_matplotlib/scatter.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py
index b3c1147b..84fd4e74 100644
--- a/src/napari_matplotlib/scatter.py
+++ b/src/napari_matplotlib/scatter.py
@@ -340,6 +340,6 @@ def draw(self) -> None:
         _, _, _ = self.axes.hist(data, bins=50, edgecolor='white',
                                  linewidth=0.3)
 
-        # # set ax labels
+        # set ax labels
         self.axes.set_xlabel(x_axis_name)
         self.axes.set_ylabel('Counts [#]')

From e5cb943c7d457f4574a6ee13f569c86cea80d304 Mon Sep 17 00:00:00 2001
From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com>
Date: Mon, 26 Jun 2023 11:35:39 +0200
Subject: [PATCH 003/238] Update src/napari_matplotlib/scatter.py

Co-authored-by: David Stansby <dstansby@gmail.com>
---
 src/napari_matplotlib/scatter.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py
index 84fd4e74..80931c7d 100644
--- a/src/napari_matplotlib/scatter.py
+++ b/src/napari_matplotlib/scatter.py
@@ -337,7 +337,7 @@ def draw(self) -> None:
         if len(data) == 0:
             return
 
-        _, _, _ = self.axes.hist(data, bins=50, edgecolor='white',
+        self.axes.hist(data, bins=50, edgecolor='white',
                                  linewidth=0.3)
 
         # set ax labels

From 524fdbd5064c6c635bf4815cd5ff986ca4b328c0 Mon Sep 17 00:00:00 2001
From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com>
Date: Mon, 26 Jun 2023 11:35:53 +0200
Subject: [PATCH 004/238] Update src/napari_matplotlib/scatter.py

Co-authored-by: David Stansby <dstansby@gmail.com>
---
 src/napari_matplotlib/scatter.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py
index 80931c7d..210a8053 100644
--- a/src/napari_matplotlib/scatter.py
+++ b/src/napari_matplotlib/scatter.py
@@ -288,7 +288,7 @@ def _get_valid_axis_keys(
         else:
             return self.layers[0].features.keys()
 
-    def _get_data(self) -> Tuple[List[np.ndarray], str, str]:
+    def _get_data(self) -> Tuple[np.ndarray, str]:
         """Get the plot data.
 
         Returns

From f69761871a301a98d8fac475df6cf5a0bdf56c33 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Mon, 26 Jun 2023 11:44:40 +0200
Subject: [PATCH 005/238] moved new widget to `histogram.py`

---
 src/napari_matplotlib/histogram.py | 126 ++++++++++++++++++++++++++++-
 src/napari_matplotlib/scatter.py   | 121 ---------------------------
 2 files changed, 124 insertions(+), 123 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 1e273e70..6a07bdf5 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -1,13 +1,14 @@
-from typing import Optional
+from typing import Optional, List, Tuple
 
 import napari
 import numpy as np
 from qtpy.QtWidgets import QWidget
+from magicgui import magicgui, ComboBox
 
 from .base import NapariMPLWidget
 from .util import Interval
 
-__all__ = ["HistogramWidget"]
+__all__ = ["HistogramWidget", "FeaturesHistogramWidget"]
 
 _COLORS = {"r": "tab:red", "g": "tab:green", "b": "tab:blue"}
 
@@ -63,3 +64,124 @@ def draw(self) -> None:
             self.axes.hist(data.ravel(), bins=bins, label=layer.name)
 
         self.axes.legend()
+
+
+class FeaturesHistogramWidget(NapariMPLWidget):
+    n_layers_input = Interval(1, 1)
+    # All layers that have a .features attributes
+    input_layer_types = (
+        napari.layers.Labels,
+        napari.layers.Points,
+        napari.layers.Shapes,
+        napari.layers.Tracks,
+        napari.layers.Vectors,
+    )
+
+    def __init__(self, napari_viewer: napari.viewer.Viewer):
+        super().__init__(napari_viewer)
+        self.axes = self.canvas.figure.subplots()
+
+        self._key_selection_widget = magicgui(
+            self._set_axis_keys,
+            x_axis_key={"choices": self._get_valid_axis_keys},
+            call_button="plot",
+        )
+        self.layout().addWidget(self._key_selection_widget.native)
+
+        self.update_layers(None)
+
+    def clear(self) -> None:
+        """
+        Clear the axes.
+        """
+        self.axes.clear()
+
+        self.layout().addWidget(self._key_selection_widget.native)
+
+    @property
+    def x_axis_key(self) -> Optional[str]:
+        """Key to access x axis data from the FeaturesTable"""
+        return self._x_axis_key
+
+    @x_axis_key.setter
+    def x_axis_key(self, key: Optional[str]) -> None:
+        self._x_axis_key = key
+        self._draw()
+
+    def _set_axis_keys(self, x_axis_key: str) -> None:
+        """Set both axis keys and then redraw the plot"""
+        self._x_axis_key = x_axis_key
+        self._draw()
+
+    def _get_valid_axis_keys(
+        self, combo_widget: Optional[ComboBox] = None
+    ) -> List[str]:
+        """
+        Get the valid axis keys from the layer FeatureTable.
+
+        Returns
+        -------
+        axis_keys : List[str]
+            The valid axis keys in the FeatureTable. If the table is empty
+            or there isn't a table, returns an empty list.
+        """
+        if len(self.layers) == 0 or not (hasattr(self.layers[0], "features")):
+            return []
+        else:
+            return self.layers[0].features.keys()
+
+    def _get_data(self) -> Tuple[np.ndarray, str]:
+        """Get the plot data.
+
+        Returns
+        -------
+        data : List[np.ndarray]
+            List contains X and Y columns from the FeatureTable. Returns
+            an empty array if nothing to plot.
+        x_axis_name : str
+            The title to display on the x axis. Returns
+            an empty string if nothing to plot.
+        """
+        if not hasattr(self.layers[0], "features"):
+            # if the selected layer doesn't have a featuretable,
+            # skip draw
+            return [], ""
+
+        feature_table = self.layers[0].features
+
+        if (
+            (len(feature_table) == 0)
+            or (self.x_axis_key is None)
+        ):
+            return [], ""
+
+        data = feature_table[self.x_axis_key]
+        x_axis_name = self.x_axis_key.replace("_", " ")
+
+        return data, x_axis_name
+
+    def _on_update_layers(self) -> None:
+        """
+        This is called when the layer selection changes by
+        ``self.update_layers()``.
+        """
+        if hasattr(self, "_key_selection_widget"):
+            self._key_selection_widget.reset_choices()
+
+        # reset the axis keys
+        self._x_axis_key = None
+
+    def draw(self) -> None:
+        """Clear the axes and histogram the currently selected layer/slice."""
+
+        data, x_axis_name = self._get_data()
+
+        if len(data) == 0:
+            return
+
+        self.axes.hist(data, bins=50, edgecolor='white',
+                                 linewidth=0.3)
+
+        # set ax labels
+        self.axes.set_xlabel(x_axis_name)
+        self.axes.set_ylabel('Counts [#]')
\ No newline at end of file
diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py
index eb6e9d27..c3c40c16 100644
--- a/src/napari_matplotlib/scatter.py
+++ b/src/napari_matplotlib/scatter.py
@@ -248,124 +248,3 @@ def on_update_layers(self) -> None:
                 self._selectors[dim].removeItem(0)
             # Add keys for newly selected layer
             self._selectors[dim].addItems(self._get_valid_axis_keys())
-
-
-class FeaturesHistogramWidget(NapariMPLWidget):
-    n_layers_input = Interval(1, 1)
-    # All layers that have a .features attributes
-    input_layer_types = (
-        napari.layers.Labels,
-        napari.layers.Points,
-        napari.layers.Shapes,
-        napari.layers.Tracks,
-        napari.layers.Vectors,
-    )
-
-    def __init__(self, napari_viewer: napari.viewer.Viewer):
-        super().__init__(napari_viewer)
-        self.axes = self.canvas.figure.subplots()
-
-        self._key_selection_widget = magicgui(
-            self._set_axis_keys,
-            x_axis_key={"choices": self._get_valid_axis_keys},
-            call_button="plot",
-        )
-        self.layout().addWidget(self._key_selection_widget.native)
-
-        self.update_layers(None)
-
-    def clear(self) -> None:
-        """
-        Clear the axes.
-        """
-        self.axes.clear()
-
-        self.layout().addWidget(self._key_selection_widget.native)
-
-    @property
-    def x_axis_key(self) -> Optional[str]:
-        """Key to access x axis data from the FeaturesTable"""
-        return self._x_axis_key
-
-    @x_axis_key.setter
-    def x_axis_key(self, key: Optional[str]) -> None:
-        self._x_axis_key = key
-        self._draw()
-
-    def _set_axis_keys(self, x_axis_key: str) -> None:
-        """Set both axis keys and then redraw the plot"""
-        self._x_axis_key = x_axis_key
-        self._draw()
-
-    def _get_valid_axis_keys(
-        self, combo_widget: Optional[ComboBox] = None
-    ) -> List[str]:
-        """
-        Get the valid axis keys from the layer FeatureTable.
-
-        Returns
-        -------
-        axis_keys : List[str]
-            The valid axis keys in the FeatureTable. If the table is empty
-            or there isn't a table, returns an empty list.
-        """
-        if len(self.layers) == 0 or not (hasattr(self.layers[0], "features")):
-            return []
-        else:
-            return self.layers[0].features.keys()
-
-    def _get_data(self) -> Tuple[np.ndarray, str]:
-        """Get the plot data.
-
-        Returns
-        -------
-        data : List[np.ndarray]
-            List contains X and Y columns from the FeatureTable. Returns
-            an empty array if nothing to plot.
-        x_axis_name : str
-            The title to display on the x axis. Returns
-            an empty string if nothing to plot.
-        """
-        if not hasattr(self.layers[0], "features"):
-            # if the selected layer doesn't have a featuretable,
-            # skip draw
-            return [], ""
-
-        feature_table = self.layers[0].features
-
-        if (
-            (len(feature_table) == 0)
-            or (self.x_axis_key is None)
-        ):
-            return [], ""
-
-        data = feature_table[self.x_axis_key]
-        x_axis_name = self.x_axis_key.replace("_", " ")
-
-        return data, x_axis_name
-
-    def _on_update_layers(self) -> None:
-        """
-        This is called when the layer selection changes by
-        ``self.update_layers()``.
-        """
-        if hasattr(self, "_key_selection_widget"):
-            self._key_selection_widget.reset_choices()
-
-        # reset the axis keys
-        self._x_axis_key = None
-
-    def draw(self) -> None:
-        """Clear the axes and histogram the currently selected layer/slice."""
-
-        data, x_axis_name = self._get_data()
-
-        if len(data) == 0:
-            return
-
-        self.axes.hist(data, bins=50, edgecolor='white',
-                                 linewidth=0.3)
-
-        # set ax labels
-        self.axes.set_xlabel(x_axis_name)
-        self.axes.set_ylabel('Counts [#]')

From 98d84f604a2fdb8457b78591291ebff61ca486e8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Mon, 26 Jun 2023 13:12:19 +0200
Subject: [PATCH 006/238] put import at correct location

---
 src/napari_matplotlib/scatter.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py
index c3c40c16..622a71fb 100644
--- a/src/napari_matplotlib/scatter.py
+++ b/src/napari_matplotlib/scatter.py
@@ -8,7 +8,7 @@
 from .util import Interval
 
 __all__ = ["ScatterBaseWidget", "ScatterWidget",
-           "FeaturesScatterWidget", "FeaturesHistogramWidget"]
+           "FeaturesScatterWidget"]
 
 
 class ScatterBaseWidget(NapariMPLWidget):

From c3d1d018502a90bba9f594fb10fe327a7812e509 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Mon, 26 Jun 2023 13:12:58 +0200
Subject: [PATCH 007/238] introduced `FEATURES_LAYER_TYPES` variable to be used
 across widgets

---
 src/napari_matplotlib/features.py  | 9 +++++++++
 src/napari_matplotlib/histogram.py | 9 ++-------
 src/napari_matplotlib/scatter.py   | 9 ++-------
 3 files changed, 13 insertions(+), 14 deletions(-)
 create mode 100644 src/napari_matplotlib/features.py

diff --git a/src/napari_matplotlib/features.py b/src/napari_matplotlib/features.py
new file mode 100644
index 00000000..16a2efe1
--- /dev/null
+++ b/src/napari_matplotlib/features.py
@@ -0,0 +1,9 @@
+from napari.layers import Labels, Points, Shapes, Tracks, Vectors
+
+FEATURES_LAYER_TYPES = (
+    Labels,
+    Points,
+    Shapes,
+    Tracks,
+    Vectors,
+)
\ No newline at end of file
diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 6a07bdf5..1f346c5e 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -7,6 +7,7 @@
 
 from .base import NapariMPLWidget
 from .util import Interval
+from .features import FEATURES_LAYER_TYPES
 
 __all__ = ["HistogramWidget", "FeaturesHistogramWidget"]
 
@@ -69,13 +70,7 @@ def draw(self) -> None:
 class FeaturesHistogramWidget(NapariMPLWidget):
     n_layers_input = Interval(1, 1)
     # All layers that have a .features attributes
-    input_layer_types = (
-        napari.layers.Labels,
-        napari.layers.Points,
-        napari.layers.Shapes,
-        napari.layers.Tracks,
-        napari.layers.Vectors,
-    )
+    input_layer_types = FEATURES_LAYER_TYPES
 
     def __init__(self, napari_viewer: napari.viewer.Viewer):
         super().__init__(napari_viewer)
diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py
index 622a71fb..144c604b 100644
--- a/src/napari_matplotlib/scatter.py
+++ b/src/napari_matplotlib/scatter.py
@@ -6,6 +6,7 @@
 
 from .base import NapariMPLWidget
 from .util import Interval
+from .features import FEATURES_LAYER_TYPES
 
 __all__ = ["ScatterBaseWidget", "ScatterWidget",
            "FeaturesScatterWidget"]
@@ -107,13 +108,7 @@ class FeaturesScatterWidget(ScatterBaseWidget):
 
     n_layers_input = Interval(1, 1)
     # All layers that have a .features attributes
-    input_layer_types = (
-        napari.layers.Labels,
-        napari.layers.Points,
-        napari.layers.Shapes,
-        napari.layers.Tracks,
-        napari.layers.Vectors,
-    )
+    input_layer_types = FEATURES_LAYER_TYPES
 
     def __init__(
         self,

From e965cd32b2f927a1d5a6a559c327a4a4df907693 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Mon, 26 Jun 2023 13:18:30 +0200
Subject: [PATCH 008/238] used `SingleAxesWidget` in FeatureHistogram

---
 src/napari_matplotlib/histogram.py | 11 +----------
 1 file changed, 1 insertion(+), 10 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index c04c2b17..048bf6cf 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -60,14 +60,13 @@ def draw(self) -> None:
         self.axes.legend()
 
 
-class FeaturesHistogramWidget(NapariMPLWidget):
+class FeaturesHistogramWidget(SingleAxesWidget):
     n_layers_input = Interval(1, 1)
     # All layers that have a .features attributes
     input_layer_types = FEATURES_LAYER_TYPES
 
     def __init__(self, napari_viewer: napari.viewer.Viewer):
         super().__init__(napari_viewer)
-        self.axes = self.canvas.figure.subplots()
 
         self._key_selection_widget = magicgui(
             self._set_axis_keys,
@@ -78,14 +77,6 @@ def __init__(self, napari_viewer: napari.viewer.Viewer):
 
         self.update_layers(None)
 
-    def clear(self) -> None:
-        """
-        Clear the axes.
-        """
-        self.axes.clear()
-
-        self.layout().addWidget(self._key_selection_widget.native)
-
     @property
     def x_axis_key(self) -> Optional[str]:
         """Key to access x axis data from the FeaturesTable"""

From 6731648a9775f479d082ea6b4bfa0db59509182b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Mon, 26 Jun 2023 13:19:19 +0200
Subject: [PATCH 009/238] Added optional `parent` argument

added parent to init
---
 src/napari_matplotlib/histogram.py | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 048bf6cf..ac1d1bfd 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -65,11 +65,13 @@ class FeaturesHistogramWidget(SingleAxesWidget):
     # All layers that have a .features attributes
     input_layer_types = FEATURES_LAYER_TYPES
 
-    def __init__(self, napari_viewer: napari.viewer.Viewer):
-        super().__init__(napari_viewer)
+    def __init__(
+            self,
+            napari_viewer: napari.viewer.Viewer,
+            parent: Optional[QWidget] = None,):
+        super().__init__(napari_viewer, parent=parent)
 
         self._key_selection_widget = magicgui(
-            self._set_axis_keys,
             x_axis_key={"choices": self._get_valid_axis_keys},
             call_button="plot",
         )

From dcab365f898cb945207395af2f0e84afd9170dff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Mon, 26 Jun 2023 13:21:43 +0200
Subject: [PATCH 010/238] removed unused ComboBox

---
 src/napari_matplotlib/histogram.py | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index ac1d1bfd..213a08b3 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -3,7 +3,7 @@
 import napari
 import numpy as np
 from qtpy.QtWidgets import QWidget
-from magicgui import magicgui, ComboBox
+from magicgui import magicgui
 
 from .base import SingleAxesWidget
 from .util import Interval
@@ -94,9 +94,7 @@ def _set_axis_keys(self, x_axis_key: str) -> None:
         self._x_axis_key = x_axis_key
         self._draw()
 
-    def _get_valid_axis_keys(
-        self, combo_widget: Optional[ComboBox] = None
-    ) -> List[str]:
+    def _get_valid_axis_keys(self) -> List[str]:
         """
         Get the valid axis keys from the layer FeatureTable.
 

From d1207f0c49d5e6bfe60f16cdd34d5e76822fba9e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Mon, 26 Jun 2023 14:17:50 +0200
Subject: [PATCH 011/238] updated tests

---
 src/napari_matplotlib/tests/test_histogram.py | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py
index 847235f1..1b17045f 100644
--- a/src/napari_matplotlib/tests/test_histogram.py
+++ b/src/napari_matplotlib/tests/test_histogram.py
@@ -44,14 +44,27 @@ def test_feature_histogram(make_napari_viewer):
     feature2 = np.random.normal(size=n_points)
 
     viewer = make_napari_viewer()
-    viewer.add_points(random_points, properties={'feature1': feature1, 'feature2': feature2}, face_color='feature1', size=1)
+    viewer.add_points(random_points,
+                      properties={'feature1': feature1, 'feature2': feature2},
+                      name='points1')
+    viewer.add_points(random_points,
+                      properties={'feature1': feature1, 'feature2': feature2},
+                      name='points2')
 
     widget = FeaturesHistogramWidget(viewer)
     viewer.window.add_dock_widget(widget)
+
+    # Check whether changing the selected key changes the plot
     widget._set_axis_keys('feature1')
-    widget._key_selection_widget()
+    fig1 = deepcopy(widget.figure)
+
     widget._set_axis_keys('feature2')
-    widget._key_selection_widget()
+    assert_figures_not_equal(widget.figure, fig1)
+
+    #check whether selecting a different layer produces the same plot
+    viewer.layers.selection.clear()
+    viewer.layers.selection.add(viewer.layers[1])
+    assert_figures_equal(widget.figure, fig1)
 
 
 def test_change_layer(make_napari_viewer, brain_data, astronaut_data):

From 476e2f26a5ffd067e318ab36c5666b4b8f38b5ce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Mon, 26 Jun 2023 14:18:02 +0200
Subject: [PATCH 012/238] Used PyQt instead of magicgui

---
 src/napari_matplotlib/histogram.py | 30 +++++++++++++++---------------
 1 file changed, 15 insertions(+), 15 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 213a08b3..1af9a9e1 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -2,8 +2,7 @@
 
 import napari
 import numpy as np
-from qtpy.QtWidgets import QWidget
-from magicgui import magicgui
+from qtpy.QtWidgets import QComboBox, QLabel, QVBoxLayout, QWidget
 
 from .base import SingleAxesWidget
 from .util import Interval
@@ -71,13 +70,14 @@ def __init__(
             parent: Optional[QWidget] = None,):
         super().__init__(napari_viewer, parent=parent)
 
-        self._key_selection_widget = magicgui(
-            x_axis_key={"choices": self._get_valid_axis_keys},
-            call_button="plot",
-        )
-        self.layout().addWidget(self._key_selection_widget.native)
+        self.layout().addLayout(QVBoxLayout())
+        self._key_selection_widget = QComboBox()
+        self.layout().addWidget(QLabel("Key:"))
+        self.layout().addWidget(self._key_selection_widget)
 
-        self.update_layers(None)
+        self._key_selection_widget.currentTextChanged.connect(self._set_axis_keys)
+
+        self._update_layers(None)
 
     @property
     def x_axis_key(self) -> Optional[str]:
@@ -139,17 +139,17 @@ def _get_data(self) -> Tuple[np.ndarray, str]:
 
         return data, x_axis_name
 
-    def _on_update_layers(self) -> None:
+    def on_update_layers(self) -> None:
         """
-        This is called when the layer selection changes by
-        ``self.update_layers()``.
+        Called when the layer selection changes by ``self.update_layers()``.
         """
-        if hasattr(self, "_key_selection_widget"):
-            self._key_selection_widget.reset_choices()
-
         # reset the axis keys
         self._x_axis_key = None
 
+        # Clear combobox
+        self._key_selection_widget.clear()
+        self._key_selection_widget.addItems(self._get_valid_axis_keys())
+
     def draw(self) -> None:
         """Clear the axes and histogram the currently selected layer/slice."""
 
@@ -159,7 +159,7 @@ def draw(self) -> None:
             return
 
         self.axes.hist(data, bins=50, edgecolor='white',
-                                 linewidth=0.3)
+                       linewidth=0.3)
 
         # set ax labels
         self.axes.set_xlabel(x_axis_name)

From 47ccb37c55d4294a2deb6f7398e3327966cc1de8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Mon, 26 Jun 2023 14:18:44 +0200
Subject: [PATCH 013/238] codestyle

---
 src/napari_matplotlib/tests/test_histogram.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py
index 1b17045f..7c71ff2c 100644
--- a/src/napari_matplotlib/tests/test_histogram.py
+++ b/src/napari_matplotlib/tests/test_histogram.py
@@ -39,7 +39,7 @@ def test_feature_histogram(make_napari_viewer):
     import numpy as np
 
     n_points = 1000
-    random_points = np.random.random((n_points,3))*10
+    random_points = np.random.random((n_points, 3)) * 10
     feature1 = np.random.random(n_points)
     feature2 = np.random.normal(size=n_points)
 
@@ -61,7 +61,7 @@ def test_feature_histogram(make_napari_viewer):
     widget._set_axis_keys('feature2')
     assert_figures_not_equal(widget.figure, fig1)
 
-    #check whether selecting a different layer produces the same plot
+    # check whether selecting a different layer produces the same plot
     viewer.layers.selection.clear()
     viewer.layers.selection.add(viewer.layers[1])
     assert_figures_equal(widget.figure, fig1)

From a40faf2d5e8f7bf72737cf0efd8b1f28232b9261 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Mon, 26 Jun 2023 14:39:27 +0200
Subject: [PATCH 014/238] codestyle

---
 src/napari_matplotlib/histogram.py | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 1af9a9e1..f4bcc346 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -1,4 +1,5 @@
-from typing import Optional, List, Tuple
+from typing import Optional, List, Tuple, Any
+import numpy.typing as npt
 
 import napari
 import numpy as np
@@ -60,6 +61,9 @@ def draw(self) -> None:
 
 
 class FeaturesHistogramWidget(SingleAxesWidget):
+    """
+    Display a histogram of selected feature attached to selected layer.
+    """
     n_layers_input = Interval(1, 1)
     # All layers that have a .features attributes
     input_layer_types = FEATURES_LAYER_TYPES
@@ -109,7 +113,7 @@ def _get_valid_axis_keys(self) -> List[str]:
         else:
             return self.layers[0].features.keys()
 
-    def _get_data(self) -> Tuple[np.ndarray, str]:
+    def _get_data(self) -> Tuple[npt.NDArray[Any], str]:
         """Get the plot data.
 
         Returns
@@ -163,4 +167,4 @@ def draw(self) -> None:
 
         # set ax labels
         self.axes.set_xlabel(x_axis_name)
-        self.axes.set_ylabel('Counts [#]')
\ No newline at end of file
+        self.axes.set_ylabel('Counts [#]')

From 32a2a149e4b8c728651b4c17d2cc5005da0a7061 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Mon, 26 Jun 2023 13:47:09 +0100
Subject: [PATCH 015/238] Fix doc links

---
 README.md | 1 -
 setup.cfg | 2 +-
 tox.ini   | 1 -
 3 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 087b2864..855c4991 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,6 @@
 [![Python Version](https://img.shields.io/pypi/pyversions/napari-matplotlib.svg?color=green)](https://python.org)
 [![tests](https://github.com/matplotlib/napari-matplotlib/workflows/tests/badge.svg)](https://github.com/matplotlib/napari-matplotlib/actions)
 [![codecov](https://codecov.io/gh/matplotlib/napari-matplotlib/branch/main/graph/badge.svg)](https://codecov.io/gh/matplotlib/napari-matplotlib)
-[![readthedocs](https://readthedocs.org/projects/napari-matplotlib/badge/?version=latest)](https://napari-matplotlib.readthedocs.io/en/latest/)
 [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/matplotlib/pytest-mpl/master.svg)](https://results.pre-commit.ci/latest/github/matplotlib/pytest-mpl/master)
 [![napari hub](https://img.shields.io/endpoint?url=https://api.napari-hub.org/shields/napari-matplotlib)](https://napari-hub.org/plugins/napari-matplotlib)
 
diff --git a/setup.cfg b/setup.cfg
index 92f4c8e1..118d4917 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -20,7 +20,7 @@ classifiers =
     Topic :: Software Development :: Testing
 project_urls =
     Bug Tracker = https://github.com/matplotlib/napari-matplotlib/issues
-    Documentation = https://github.com/matplotlib/napari-matplotlib#README.md
+    Documentation = http://www.davidstansby.com/napari-matplotlib-docs/
     Source Code = https://github.com/matplotlib/napari-matplotlib
     User Support = https://github.com/matplotlib/napari-matplotlib/issues
 
diff --git a/tox.ini b/tox.ini
index 7f78e2d5..0b59007d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,4 +1,3 @@
-# For more information about tox, see https://tox.readthedocs.io/en/latest/
 [tox]
 envlist = py{38,39,310}
 isolated_build = true

From 89f18301a9ac8b4d4c756254f9aea1a97fea3a75 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Mon, 26 Jun 2023 15:58:22 +0100
Subject: [PATCH 016/238] Update deploy repo

---
 .github/workflows/docs.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 30a2c3eb..f1b2a1b5 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -52,4 +52,4 @@ jobs:
       with:
         folder: html
         ssh-key: ${{ secrets.DEPLOY_KEY }}
-        repository-name: dstansby/napari-matplotlib-docs
+        repository-name: napari-matplotlib/napari-matplotlib.github.io

From 3e469a9cf317d14592b225ad100f938e527d314d Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Mon, 26 Jun 2023 16:08:26 +0100
Subject: [PATCH 017/238] Misc dox fixes

---
 docs/_static/hist.png            | Bin 0 -> 1116521 bytes
 docs/changelog.rst               |   2 +-
 docs/guide/index.rst             |   8 --------
 docs/index.rst                   |  18 ++++++++----------
 docs/{guide => }/third_party.rst |   4 +---
 docs/{guide => }/user_guide.rst  |   0
 6 files changed, 10 insertions(+), 22 deletions(-)
 create mode 100644 docs/_static/hist.png
 delete mode 100644 docs/guide/index.rst
 rename docs/{guide => }/third_party.rst (94%)
 rename docs/{guide => }/user_guide.rst (100%)

diff --git a/docs/_static/hist.png b/docs/_static/hist.png
new file mode 100644
index 0000000000000000000000000000000000000000..95850b71542a69624923718c529fd4d7cd6a54d9
GIT binary patch
diff --git a/docs/changelog.rst b/docs/changelog.rst
index db3bc343..75adbe4e 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,7 +1,7 @@
 Changelog
 =========
 
-0.3.0
+1.0.0
 -----
 
 New features
diff --git a/docs/guide/index.rst b/docs/guide/index.rst
deleted file mode 100644
index 968ea0ec..00000000
--- a/docs/guide/index.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-Guide
-=====
-
-.. toctree::
-   :maxdepth: 1
-
-   user_guide
-   third_party
diff --git a/docs/index.rst b/docs/index.rst
index 5b747e04..b457fdde 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,23 +1,21 @@
 napari-matplotlib
 =================
-``napari-matplotlib`` is a `Napari <https://napari.org>`_ plugin for generating
+A `napari <https://napari.org>`_ plugin for generating
 `Matplotlib <https://matplotlib.org/>`_ plots from one or more ``napari`` Layers.
 
-Design
-------
-``napari-matplotlib`` contains a number of different ``napari`` Widgets. Each
-widget is designed to map one or more ``napari`` Layers on to a ``matplotlib`` plot.
+This plugin provides a number of different ``napari`` Widgets. Each
+widget is designed to map one or more ``napari`` Layers on to a ``Matplotlib`` plot.
 As an example, the `~.HistogramWidget` is used to
 map one or more Image layers on to a 1D histogram plot.
 
-.. toctree::
-   :maxdepth: 1
-
-   guide/index
+.. image:: _static/hist.png
+  :alt: Screenshot of the napari viewer showing the napari-matplotlib histogram widget on the right hand side of the screen.
 
 .. toctree::
    :maxdepth: 1
-   :caption: Reference
+   :hidden:
 
+   user_guide
+   third_party
    api
    changelog
diff --git a/docs/guide/third_party.rst b/docs/third_party.rst
similarity index 94%
rename from docs/guide/third_party.rst
rename to docs/third_party.rst
index d892ba8d..c09eb532 100644
--- a/docs/guide/third_party.rst
+++ b/docs/third_party.rst
@@ -1,8 +1,6 @@
 Third-party plugins
 ===================
-This page explains how ``napari-matplotlib`` can be used within third party plugins.
-
-``napari-matplotlib`` provides a ready-to-go widget with a Matplotlib toolbar and figure to third party plugin developers
+``napari-matplotlib`` provides a ready-to-go widget with a Matplotlib toolbar and figure to third party plugin developers.
 This widget is customised to match the theme of the main napari window.
 
 The widget can be found at `napari_matplotlib.base.NapariMPLWidget`.
diff --git a/docs/guide/user_guide.rst b/docs/user_guide.rst
similarity index 100%
rename from docs/guide/user_guide.rst
rename to docs/user_guide.rst

From 6268bea86934a2ebf728c3253402142018bb6f22 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Mon, 26 Jun 2023 16:09:38 +0100
Subject: [PATCH 018/238] Also run doc job on main

---
 .github/workflows/docs.yml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index f1b2a1b5..bc4de630 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -4,6 +4,9 @@ on:
   pull_request:
     branches:
       - main
+  push:
+    branches:
+      - main
 
 jobs:
   build-docs:

From 40f63d779cbfc6f12da3ca6a731ee6199057bd25 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Mon, 26 Jun 2023 16:23:57 +0100
Subject: [PATCH 019/238] Update doc link again

---
 setup.cfg | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.cfg b/setup.cfg
index 118d4917..b03f446c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -20,7 +20,7 @@ classifiers =
     Topic :: Software Development :: Testing
 project_urls =
     Bug Tracker = https://github.com/matplotlib/napari-matplotlib/issues
-    Documentation = http://www.davidstansby.com/napari-matplotlib-docs/
+    Documentation = https://napari-matplotlib.github.io
     Source Code = https://github.com/matplotlib/napari-matplotlib
     User Support = https://github.com/matplotlib/napari-matplotlib/issues
 

From 381014c31fb24fa13b601a7f6439357fad978e35 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Mon, 26 Jun 2023 16:41:38 +0100
Subject: [PATCH 020/238] Minor cleanups to tox file

---
 tox.ini | 2 --
 1 file changed, 2 deletions(-)

diff --git a/tox.ini b/tox.ini
index 0b59007d..298887e1 100644
--- a/tox.ini
+++ b/tox.ini
@@ -13,9 +13,7 @@ extras = testing
 allowlist_externals =
     cp
     ls
-    tree
 commands =
     cp -R {toxinidir}/src/napari_matplotlib/tests/baseline {envdir}/baseline
     ls {toxinidir}/src/napari_matplotlib/tests/baseline
-    python -c 'from skimage import data; data.brain()'
     python -m pytest --mpl --mpl-generate-summary=html --mpl-results-path={toxinidir}/reports -v --color=yes --cov=napari_matplotlib --cov-report=xml

From 9e6c5aff3c9059f95c2b41f2dc3b30b3d80e36fa Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Mon, 26 Jun 2023 16:55:10 +0100
Subject: [PATCH 021/238] Add gallery to TOC

---
 docs/index.rst      | 1 +
 examples/README.rst | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/docs/index.rst b/docs/index.rst
index b457fdde..0eb92b04 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -16,6 +16,7 @@ map one or more Image layers on to a 1D histogram plot.
    :hidden:
 
    user_guide
+   auto_examples/index
    third_party
    api
    changelog
diff --git a/examples/README.rst b/examples/README.rst
index bac945d5..ae12bff3 100644
--- a/examples/README.rst
+++ b/examples/README.rst
@@ -1,2 +1,2 @@
-Examples
-========
+Gallery
+=======

From ed658cdbfce9aafa2ea0517cbaaee704a47b71da Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 26 Jun 2023 18:47:13 +0000
Subject: [PATCH 022/238] [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
---
 src/napari_matplotlib/features.py             |  2 +-
 src/napari_matplotlib/histogram.py            | 29 +++++++++----------
 src/napari_matplotlib/scatter.py              |  5 ++--
 src/napari_matplotlib/tests/test_histogram.py | 23 ++++++++-------
 4 files changed, 30 insertions(+), 29 deletions(-)

diff --git a/src/napari_matplotlib/features.py b/src/napari_matplotlib/features.py
index 16a2efe1..34abf104 100644
--- a/src/napari_matplotlib/features.py
+++ b/src/napari_matplotlib/features.py
@@ -6,4 +6,4 @@
     Shapes,
     Tracks,
     Vectors,
-)
\ No newline at end of file
+)
diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index f4bcc346..01c2c6a4 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -1,13 +1,13 @@
-from typing import Optional, List, Tuple, Any
-import numpy.typing as npt
+from typing import Any, List, Optional, Tuple
 
 import napari
 import numpy as np
+import numpy.typing as npt
 from qtpy.QtWidgets import QComboBox, QLabel, QVBoxLayout, QWidget
 
 from .base import SingleAxesWidget
-from .util import Interval
 from .features import FEATURES_LAYER_TYPES
+from .util import Interval
 
 __all__ = ["HistogramWidget", "FeaturesHistogramWidget"]
 
@@ -64,14 +64,16 @@ class FeaturesHistogramWidget(SingleAxesWidget):
     """
     Display a histogram of selected feature attached to selected layer.
     """
+
     n_layers_input = Interval(1, 1)
     # All layers that have a .features attributes
     input_layer_types = FEATURES_LAYER_TYPES
 
     def __init__(
-            self,
-            napari_viewer: napari.viewer.Viewer,
-            parent: Optional[QWidget] = None,):
+        self,
+        napari_viewer: napari.viewer.Viewer,
+        parent: Optional[QWidget] = None,
+    ):
         super().__init__(napari_viewer, parent=parent)
 
         self.layout().addLayout(QVBoxLayout())
@@ -79,7 +81,9 @@ def __init__(
         self.layout().addWidget(QLabel("Key:"))
         self.layout().addWidget(self._key_selection_widget)
 
-        self._key_selection_widget.currentTextChanged.connect(self._set_axis_keys)
+        self._key_selection_widget.currentTextChanged.connect(
+            self._set_axis_keys
+        )
 
         self._update_layers(None)
 
@@ -132,10 +136,7 @@ def _get_data(self) -> Tuple[npt.NDArray[Any], str]:
 
         feature_table = self.layers[0].features
 
-        if (
-            (len(feature_table) == 0)
-            or (self.x_axis_key is None)
-        ):
+        if (len(feature_table) == 0) or (self.x_axis_key is None):
             return [], ""
 
         data = feature_table[self.x_axis_key]
@@ -156,15 +157,13 @@ def on_update_layers(self) -> None:
 
     def draw(self) -> None:
         """Clear the axes and histogram the currently selected layer/slice."""
-
         data, x_axis_name = self._get_data()
 
         if len(data) == 0:
             return
 
-        self.axes.hist(data, bins=50, edgecolor='white',
-                       linewidth=0.3)
+        self.axes.hist(data, bins=50, edgecolor="white", linewidth=0.3)
 
         # set ax labels
         self.axes.set_xlabel(x_axis_name)
-        self.axes.set_ylabel('Counts [#]')
+        self.axes.set_ylabel("Counts [#]")
diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py
index 08464bc4..8a9f55ae 100644
--- a/src/napari_matplotlib/scatter.py
+++ b/src/napari_matplotlib/scatter.py
@@ -5,11 +5,10 @@
 from qtpy.QtWidgets import QComboBox, QLabel, QVBoxLayout, QWidget
 
 from .base import SingleAxesWidget
-from .util import Interval
 from .features import FEATURES_LAYER_TYPES
+from .util import Interval
 
-__all__ = ["ScatterBaseWidget", "ScatterWidget",
-           "FeaturesScatterWidget"]
+__all__ = ["ScatterBaseWidget", "ScatterWidget", "FeaturesScatterWidget"]
 
 
 class ScatterBaseWidget(SingleAxesWidget):
diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py
index 7c71ff2c..793c1a35 100644
--- a/src/napari_matplotlib/tests/test_histogram.py
+++ b/src/napari_matplotlib/tests/test_histogram.py
@@ -2,7 +2,7 @@
 
 import pytest
 
-from napari_matplotlib import HistogramWidget, FeaturesHistogramWidget
+from napari_matplotlib import FeaturesHistogramWidget, HistogramWidget
 from napari_matplotlib.tests.helpers import (
     assert_figures_equal,
     assert_figures_not_equal,
@@ -35,7 +35,6 @@ def test_histogram_3D(make_napari_viewer, brain_data):
 
 
 def test_feature_histogram(make_napari_viewer):
-
     import numpy as np
 
     n_points = 1000
@@ -44,21 +43,25 @@ def test_feature_histogram(make_napari_viewer):
     feature2 = np.random.normal(size=n_points)
 
     viewer = make_napari_viewer()
-    viewer.add_points(random_points,
-                      properties={'feature1': feature1, 'feature2': feature2},
-                      name='points1')
-    viewer.add_points(random_points,
-                      properties={'feature1': feature1, 'feature2': feature2},
-                      name='points2')
+    viewer.add_points(
+        random_points,
+        properties={"feature1": feature1, "feature2": feature2},
+        name="points1",
+    )
+    viewer.add_points(
+        random_points,
+        properties={"feature1": feature1, "feature2": feature2},
+        name="points2",
+    )
 
     widget = FeaturesHistogramWidget(viewer)
     viewer.window.add_dock_widget(widget)
 
     # Check whether changing the selected key changes the plot
-    widget._set_axis_keys('feature1')
+    widget._set_axis_keys("feature1")
     fig1 = deepcopy(widget.figure)
 
-    widget._set_axis_keys('feature2')
+    widget._set_axis_keys("feature2")
     assert_figures_not_equal(widget.figure, fig1)
 
     # check whether selecting a different layer produces the same plot

From d4379489230d704509e6ab63f9736265eb95e257 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Mon, 26 Jun 2023 20:56:00 +0100
Subject: [PATCH 023/238] Update pre-commit hooks

---
 .pre-commit-config.yaml                                      | 4 ++--
 examples/features_scatter.py                                 | 3 ++-
 src/napari_matplotlib/tests/helpers.py                       | 2 +-
 src/napari_matplotlib/tests/scatter/test_scatter_features.py | 2 +-
 4 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 1a3e77f3..767aedf6 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -22,14 +22,14 @@ repos:
       - id: napari-plugin-checks
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.3.0
+    rev: v1.4.1
     hooks:
      - id: mypy
        additional_dependencies: [numpy, matplotlib]
 
   - repo: https://github.com/charliermarsh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.0.272'
+    rev: 'v0.0.275'
     hooks:
       - id: ruff
 
diff --git a/examples/features_scatter.py b/examples/features_scatter.py
index 5b4d407a..f4ea7f66 100644
--- a/examples/features_scatter.py
+++ b/examples/features_scatter.py
@@ -5,10 +5,11 @@
 
 import napari
 import numpy as np
+import numpy.typing as npt
 from skimage.measure import regionprops_table
 
 # make a test label image
-label_image = np.zeros((100, 100), dtype=np.uint16)
+label_image: npt.NDArray[np.uint16] = np.zeros((100, 100), dtype=np.uint16)
 
 label_image[10:20, 10:20] = 1
 label_image[50:70, 50:70] = 2
diff --git a/src/napari_matplotlib/tests/helpers.py b/src/napari_matplotlib/tests/helpers.py
index 15259f54..b00c0af4 100644
--- a/src/napari_matplotlib/tests/helpers.py
+++ b/src/napari_matplotlib/tests/helpers.py
@@ -13,7 +13,7 @@ def fig_to_array(fig: Figure) -> npt.NDArray[np.uint8]:
     with BytesIO() as io_buf:
         fig.savefig(io_buf, format="raw")
         io_buf.seek(0)
-        img_arr = np.reshape(
+        img_arr: npt.NDArray[np.uint8] = np.reshape(
             np.frombuffer(io_buf.getvalue(), dtype=np.uint8),
             newshape=(int(fig.bbox.bounds[3]), int(fig.bbox.bounds[2]), -1),
         )
diff --git a/src/napari_matplotlib/tests/scatter/test_scatter_features.py b/src/napari_matplotlib/tests/scatter/test_scatter_features.py
index c211a064..b5a396fd 100644
--- a/src/napari_matplotlib/tests/scatter/test_scatter_features.py
+++ b/src/napari_matplotlib/tests/scatter/test_scatter_features.py
@@ -36,7 +36,7 @@ def test_features_scatter_widget_2D(
 def make_labels_layer_with_features() -> (
     Tuple[npt.NDArray[np.uint16], Dict[str, Any]]
 ):
-    label_image = np.zeros((100, 100), dtype=np.uint16)
+    label_image: npt.NDArray[np.uint16] = np.zeros((100, 100), dtype=np.uint16)
     for label_value, start_index in enumerate([10, 30, 50], start=1):
         end_index = start_index + 10
         label_image[start_index:end_index, start_index:end_index] = label_value

From 669cdc26bb6091f1209242b367820a2a6280233a Mon Sep 17 00:00:00 2001
From: Sam Cunliffe <s.cunliffe@ucl.ac.uk>
Date: Thu, 8 Jun 2023 09:53:09 +0100
Subject: [PATCH 024/238] Test first.

A TDD test to check we can set the theme from a user-defined stylesheet.
For this, just use Solarized_Light2.
---
 src/napari_matplotlib/tests/test_theme.py | 38 ++++++++++++++++++++++-
 1 file changed, 37 insertions(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/tests/test_theme.py b/src/napari_matplotlib/tests/test_theme.py
index 7af61f17..fb1416fe 100644
--- a/src/napari_matplotlib/tests/test_theme.py
+++ b/src/napari_matplotlib/tests/test_theme.py
@@ -1,10 +1,15 @@
 from copy import deepcopy
 
+import shutil
+from pathlib import Path
+
+import matplotlib
 import napari
 import numpy as np
 import pytest
+from matplotlib.colors import to_rgba
 
-from napari_matplotlib import ScatterWidget
+from napari_matplotlib import HistogramWidget, ScatterWidget
 from napari_matplotlib.base import NapariMPLWidget
 
 
@@ -140,3 +145,34 @@ def test_custom_theme(make_napari_viewer, theme_path, brain_data):
     viewer.layers.selection.add(viewer.layers[1])
 
     return deepcopy(widget.figure)
+
+
+def find_mpl_stylesheet(name: str) -> Path:
+    """Find the built-in matplotlib stylesheet."""
+    return Path(matplotlib.__path__[0]) / f"mpl-data/stylelib/{name}.mplstyle"
+
+
+def test_stylesheet_in_cwd(tmpdir, make_napari_viewer, image_data):
+    """
+    Test that a stylesheet in the current directory is given precidence.
+
+    Do this by copying over a stylesheet from matplotlib's built in styles,
+    naming it correctly, and checking the colours are as expected.
+    """
+    with tmpdir.as_cwd():
+        # Copy Solarize_Light2 to current dir as if it was a user-overriden stylesheet.
+        shutil.copy(find_mpl_stylesheet("Solarize_Light2"), "./user.mplstyle")
+        viewer = make_napari_viewer()
+        viewer.add_image(image_data[0], **image_data[1])
+        widget = HistogramWidget(viewer)
+        ax = widget.figure.gca()
+
+        # The axes should have a light brownish grey background:
+        assert ax.get_facecolor() == to_rgba("#eee8d5")
+        assert ax.patch.get_facecolor() == to_rgba("#eee8d5")
+
+        # The figure background and axis gridlines are light yellow:
+        assert widget.figure.patch.get_facecolor() == to_rgba("#fdf6e3")
+        for gridline in ax.get_xgridlines() + ax.get_ygridlines():
+            assert gridline.get_visible() is True
+            assert gridline.get_color() == "#fdf6e3"

From 13d22686764bb2badc973dca6331eafd7a15c135 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Mon, 26 Jun 2023 20:42:24 +0100
Subject: [PATCH 025/238] Add support for custom style sheets

---
 docs/user_guide.rst                       | 11 ++++++
 src/napari_matplotlib/base.py             |  8 +++-
 src/napari_matplotlib/tests/test_theme.py | 48 +++++++++++++----------
 3 files changed, 46 insertions(+), 21 deletions(-)

diff --git a/docs/user_guide.rst b/docs/user_guide.rst
index 15b44f44..0872e540 100644
--- a/docs/user_guide.rst
+++ b/docs/user_guide.rst
@@ -36,3 +36,14 @@ To use these:
 1. Open the desired widget using the ``Plugins > napari-matplotlib`` menu in napari.
 2. Select a single layer that has a features table using the napari layers list in the bottom left-hand side of the window.
 3. Use the drop down menu(s) under the Matplotlib figure to select the feature(s) to plot.
+
+Customising plots
+-----------------
+`Matplotlib style sheets <https://matplotlib.org/stable/tutorials/introductory/customizing.html#defining-your-own-style>`__ can be used to customise
+the plots generated by ``napari-matplotlib``.
+To use a custom style sheet:
+
+1. Save it as ``napari-matplotlib.mplstyle``
+2. Put it in the Matplotlib configuration directory.
+   The location of this directory varies on different computers,
+   and can be found by calling :func:`matplotlib.get_configdir()`.
diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py
index 89c6d3de..792b5aff 100644
--- a/src/napari_matplotlib/base.py
+++ b/src/napari_matplotlib/base.py
@@ -2,6 +2,7 @@
 from pathlib import Path
 from typing import List, Optional, Tuple
 
+import matplotlib
 import matplotlib.style as mplstyle
 import napari
 from matplotlib.backends.backend_qtagg import (
@@ -16,6 +17,10 @@
 
 __all__ = ["BaseNapariMPLWidget", "NapariMPLWidget", "SingleAxesWidget"]
 
+_CUSTOM_STYLE_PATH = (
+    Path(matplotlib.get_configdir()) / "napari-matplotlib.mplstyle"
+)
+
 
 class BaseNapariMPLWidget(QWidget):
     """
@@ -46,7 +51,6 @@ def __init__(
         with mplstyle.context(self.mpl_style_sheet_path):
             self.canvas = FigureCanvas()
 
-        self.canvas.figure.patch.set_facecolor("none")
         self.canvas.figure.set_layout_engine("constrained")
         self.toolbar = NapariNavigationToolbar(
             self.canvas, parent=self
@@ -73,6 +77,8 @@ def mpl_style_sheet_path(self) -> Path:
         """
         if self._mpl_style_sheet_path is not None:
             return self._mpl_style_sheet_path
+        elif (_CUSTOM_STYLE_PATH).exists():
+            return _CUSTOM_STYLE_PATH
         elif self._napari_theme_has_light_bg():
             return Path(__file__).parent / "styles" / "light.mplstyle"
         else:
diff --git a/src/napari_matplotlib/tests/test_theme.py b/src/napari_matplotlib/tests/test_theme.py
index fb1416fe..49738abd 100644
--- a/src/napari_matplotlib/tests/test_theme.py
+++ b/src/napari_matplotlib/tests/test_theme.py
@@ -1,6 +1,5 @@
-from copy import deepcopy
-
 import shutil
+from copy import deepcopy
 from pathlib import Path
 
 import matplotlib
@@ -152,27 +151,36 @@ def find_mpl_stylesheet(name: str) -> Path:
     return Path(matplotlib.__path__[0]) / f"mpl-data/stylelib/{name}.mplstyle"
 
 
-def test_stylesheet_in_cwd(tmpdir, make_napari_viewer, image_data):
+def test_custom_stylesheet(make_napari_viewer, image_data):
     """
     Test that a stylesheet in the current directory is given precidence.
 
     Do this by copying over a stylesheet from matplotlib's built in styles,
     naming it correctly, and checking the colours are as expected.
     """
-    with tmpdir.as_cwd():
-        # Copy Solarize_Light2 to current dir as if it was a user-overriden stylesheet.
-        shutil.copy(find_mpl_stylesheet("Solarize_Light2"), "./user.mplstyle")
-        viewer = make_napari_viewer()
-        viewer.add_image(image_data[0], **image_data[1])
-        widget = HistogramWidget(viewer)
-        ax = widget.figure.gca()
-
-        # The axes should have a light brownish grey background:
-        assert ax.get_facecolor() == to_rgba("#eee8d5")
-        assert ax.patch.get_facecolor() == to_rgba("#eee8d5")
-
-        # The figure background and axis gridlines are light yellow:
-        assert widget.figure.patch.get_facecolor() == to_rgba("#fdf6e3")
-        for gridline in ax.get_xgridlines() + ax.get_ygridlines():
-            assert gridline.get_visible() is True
-            assert gridline.get_color() == "#fdf6e3"
+    # Copy Solarize_Light2 as if it was a user-overriden stylesheet.
+    style_sheet_path = (
+        Path(matplotlib.get_configdir()) / "napari-matplotlib.mplstyle"
+    )
+    if style_sheet_path.exists():
+        pytest.skip("Won't ovewrite existing custom style sheet.")
+    shutil.copy(
+        find_mpl_stylesheet("Solarize_Light2"),
+        style_sheet_path,
+    )
+
+    viewer = make_napari_viewer()
+    viewer.add_image(image_data[0], **image_data[1])
+    widget = HistogramWidget(viewer)
+    assert widget.mpl_style_sheet_path == style_sheet_path
+    ax = widget.figure.gca()
+
+    # The axes should have a light brownish grey background:
+    assert ax.get_facecolor() == to_rgba("#eee8d5")
+    assert ax.patch.get_facecolor() == to_rgba("#eee8d5")
+
+    # The figure background and axis gridlines are light yellow:
+    assert widget.figure.patch.get_facecolor() == to_rgba("#fdf6e3")
+    for gridline in ax.get_xgridlines() + ax.get_ygridlines():
+        assert gridline.get_visible() is True
+        assert gridline.get_color() == "#fdf6e3"

From 3e6bf2c2ff899145c11cf056cc28c64849c26090 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Mon, 26 Jun 2023 20:58:25 +0100
Subject: [PATCH 026/238] Delte style sheet after test

---
 src/napari_matplotlib/tests/test_theme.py | 34 +++++++++++++----------
 1 file changed, 19 insertions(+), 15 deletions(-)

diff --git a/src/napari_matplotlib/tests/test_theme.py b/src/napari_matplotlib/tests/test_theme.py
index 49738abd..a3642f8f 100644
--- a/src/napari_matplotlib/tests/test_theme.py
+++ b/src/napari_matplotlib/tests/test_theme.py
@@ -1,3 +1,4 @@
+import os
 import shutil
 from copy import deepcopy
 from pathlib import Path
@@ -169,18 +170,21 @@ def test_custom_stylesheet(make_napari_viewer, image_data):
         style_sheet_path,
     )
 
-    viewer = make_napari_viewer()
-    viewer.add_image(image_data[0], **image_data[1])
-    widget = HistogramWidget(viewer)
-    assert widget.mpl_style_sheet_path == style_sheet_path
-    ax = widget.figure.gca()
-
-    # The axes should have a light brownish grey background:
-    assert ax.get_facecolor() == to_rgba("#eee8d5")
-    assert ax.patch.get_facecolor() == to_rgba("#eee8d5")
-
-    # The figure background and axis gridlines are light yellow:
-    assert widget.figure.patch.get_facecolor() == to_rgba("#fdf6e3")
-    for gridline in ax.get_xgridlines() + ax.get_ygridlines():
-        assert gridline.get_visible() is True
-        assert gridline.get_color() == "#fdf6e3"
+    try:
+        viewer = make_napari_viewer()
+        viewer.add_image(image_data[0], **image_data[1])
+        widget = HistogramWidget(viewer)
+        assert widget.mpl_style_sheet_path == style_sheet_path
+        ax = widget.figure.gca()
+
+        # The axes should have a light brownish grey background:
+        assert ax.get_facecolor() == to_rgba("#eee8d5")
+        assert ax.patch.get_facecolor() == to_rgba("#eee8d5")
+
+        # The figure background and axis gridlines are light yellow:
+        assert widget.figure.patch.get_facecolor() == to_rgba("#fdf6e3")
+        for gridline in ax.get_xgridlines() + ax.get_ygridlines():
+            assert gridline.get_visible() is True
+            assert gridline.get_color() == "#fdf6e3"
+    finally:
+        os.remove(style_sheet_path)

From 4b9cb3d29ba96d4aeca997ae51bac17659f38350 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 30 Jun 2023 10:28:56 +0100
Subject: [PATCH 027/238] Add logo to docs

---
 docs/_static/logo.png | Bin 0 -> 52586 bytes
 docs/conf.py          |   1 +
 2 files changed, 1 insertion(+)
 create mode 100644 docs/_static/logo.png

diff --git a/docs/_static/logo.png b/docs/_static/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..92232b6cfd18be4c0e3f2487842f97f6f5ca7d23
GIT binary patch
diff --git a/docs/conf.py b/docs/conf.py
index 4351e610..2517a59c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -87,6 +87,7 @@
 # a list of builtin themes.
 #
 html_theme = "pydata_sphinx_theme"
+html_logo = "_static/logo.png"
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,

From d5c87652744875f137ab5a255876b9c670dcb4e1 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 30 Jun 2023 10:31:39 +0100
Subject: [PATCH 028/238] Update changelog

---
 docs/changelog.rst | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 75adbe4e..64771d30 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -15,6 +15,8 @@ Visual improvements
 ~~~~~~~~~~~~~~~~~~~
 - The background of ``napari-matplotlib`` figures and axes is now transparent, and the text and axis colour respects the ``napari`` theme.
 - The icons in the Matplotlib toolbar are now the same size as icons in the napari window.
+- Custom style sheets can now be set to customise plots. See the user guide
+  for more information.
 
 Changes
 ~~~~~~~

From 35f12d84d070f6682f0b2d434462b6a9bbffd4ca Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 30 Jun 2023 10:33:55 +0100
Subject: [PATCH 029/238] Only upload docs on release

---
 .github/workflows/docs.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index bc4de630..bde0bc1e 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -43,7 +43,7 @@ jobs:
     name: Upload docs to GitHub pages
     runs-on: ubuntu-latest
     needs: build-docs
-    if: github.event_name == 'push' && github.ref_name == 'main'
+    if: contains(github.ref, 'tags')
     steps:
     - uses: actions/checkout@v3
     - uses: actions/download-artifact@v3

From 4c96272a65792ab6e61fb7677e3d6692f61f61a5 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 30 Jun 2023 10:45:31 +0100
Subject: [PATCH 030/238] Use environment to deploy

---
 .github/workflows/test_and_deploy.yml | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml
index f488fa13..ee1c9ce4 100644
--- a/.github/workflows/test_and_deploy.yml
+++ b/.github/workflows/test_and_deploy.yml
@@ -73,21 +73,22 @@ jobs:
     needs: [test]
     runs-on: ubuntu-latest
     if: contains(github.ref, 'tags')
+    environment: pypi-deploy
     steps:
       - uses: actions/checkout@v2
       - name: Set up Python
         uses: actions/setup-python@v2
         with:
           python-version: "3.x"
-      - name: Install dependencies
+      - name: Install build
         run: |
           python -m pip install --upgrade pip
-          pip install -U setuptools setuptools_scm wheel twine build
-      - name: Build and publish
-        env:
-          TWINE_USERNAME: __token__
-          TWINE_PASSWORD: ${{ secrets.TWINE_API_KEY }}
+          python -m pip install --upgrade build
+
+      - name: Build package
         run: |
           git tag
           python -m build .
-          twine upload dist/*
+
+      - name: Publish package
+        uses: pypa/gh-action-pypi-publish@release/v1

From d0aee4a4473c0ac79f19f10bcec395c183b8d2a5 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 30 Jun 2023 12:21:17 +0100
Subject: [PATCH 031/238] Add write token

---
 .github/workflows/test_and_deploy.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml
index ee1c9ce4..df170bdf 100644
--- a/.github/workflows/test_and_deploy.yml
+++ b/.github/workflows/test_and_deploy.yml
@@ -74,6 +74,8 @@ jobs:
     runs-on: ubuntu-latest
     if: contains(github.ref, 'tags')
     environment: pypi-deploy
+    permissions:
+      id-token: write
     steps:
       - uses: actions/checkout@v2
       - name: Set up Python

From 9d6988e4bf3ca5982475381994275b92d567fce4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Fri, 30 Jun 2023 13:18:45 +0200
Subject: [PATCH 032/238] added check for `_key_selection_widget`

removed `reset_choices()` statement

Revert "removed `reset_choices()` statement"

This reverts commit ed8fc0acd3a53f446ba71313be5209b0b589caff.

removed `reset_choices()` from feature histogram

From bd0c84f3d21fb7b03dae3099c7fd0f8f32f25bac Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 30 Jun 2023 12:44:54 +0100
Subject: [PATCH 033/238] Run doc build on tag

---
 .github/workflows/docs.yml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index bde0bc1e..13ce3b85 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -1,12 +1,15 @@
 name: Build docs
 
 on:
+
   pull_request:
     branches:
       - main
   push:
     branches:
       - main
+    tags:
+      - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10
 
 jobs:
   build-docs:

From d2fa291c0549536562130eef467a875cf41dacf8 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 30 Jun 2023 12:45:12 +0100
Subject: [PATCH 034/238] Upload docs temp

---
 .github/workflows/docs.yml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 13ce3b85..74b6e55b 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -46,7 +46,6 @@ jobs:
     name: Upload docs to GitHub pages
     runs-on: ubuntu-latest
     needs: build-docs
-    if: contains(github.ref, 'tags')
     steps:
     - uses: actions/checkout@v3
     - uses: actions/download-artifact@v3

From 87d34f586ae59e596507914479ce8b431b890d50 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 30 Jun 2023 12:45:26 +0100
Subject: [PATCH 035/238] Put back doc deploy guard

---
 .github/workflows/docs.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 74b6e55b..13ce3b85 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -46,6 +46,7 @@ jobs:
     name: Upload docs to GitHub pages
     runs-on: ubuntu-latest
     needs: build-docs
+    if: contains(github.ref, 'tags')
     steps:
     - uses: actions/checkout@v3
     - uses: actions/download-artifact@v3

From a2b83caea0f07446d4252d821f8f2f3cc3a27895 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Fri, 30 Jun 2023 16:19:15 +0200
Subject: [PATCH 036/238] removed `reset_choices` from old widget

---
 src/napari_matplotlib/scatter.py | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py
index 8a9f55ae..a4148bd2 100644
--- a/src/napari_matplotlib/scatter.py
+++ b/src/napari_matplotlib/scatter.py
@@ -217,13 +217,6 @@ def on_update_layers(self) -> None:
         """
         Called when the layer selection changes by ``self.update_layers()``.
         """
-        if hasattr(self, "_key_selection_widget"):
-            self._key_selection_widget.reset_choices()
-
-        # reset the axis keys
-        self._x_axis_key = None
-        self._y_axis_key = None
-
         # Clear combobox
         for dim in ["x", "y"]:
             while self._selectors[dim].count() > 0:

From 2c95034c4d70e73a49399ba522b83406dd073a34 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Fri, 30 Jun 2023 16:43:19 +0200
Subject: [PATCH 037/238] added figure test

---
 .../tests/baseline/test_feature_histogram.png | Bin 0 -> 9681 bytes
 src/napari_matplotlib/tests/test_histogram.py |  27 ++++++++++++++++++
 2 files changed, 27 insertions(+)
 create mode 100644 src/napari_matplotlib/tests/baseline/test_feature_histogram.png

diff --git a/src/napari_matplotlib/tests/baseline/test_feature_histogram.png b/src/napari_matplotlib/tests/baseline/test_feature_histogram.png
new file mode 100644
index 0000000000000000000000000000000000000000..1892af4467a4aee2346385437d4fc0c45216eccd
GIT binary patch
diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py
index 793c1a35..00c8092f 100644
--- a/src/napari_matplotlib/tests/test_histogram.py
+++ b/src/napari_matplotlib/tests/test_histogram.py
@@ -69,6 +69,33 @@ def test_feature_histogram(make_napari_viewer):
     viewer.layers.selection.add(viewer.layers[1])
     assert_figures_equal(widget.figure, fig1)
 
+@pytest.mark.mpl_image_compare
+def test_feature_histogram2(make_napari_viewer):
+    import numpy as np    
+    np.random.seed(0)
+    n_points = 1000
+    random_points = np.random.random((n_points, 3)) * 10
+    feature1 = np.random.random(n_points)
+    feature2 = np.random.normal(size=n_points)
+
+    viewer = make_napari_viewer()
+    viewer.add_points(
+        random_points,
+        properties={"feature1": feature1, "feature2": feature2},
+        name="points1",
+    )
+    viewer.add_points(
+        random_points,
+        properties={"feature1": feature1, "feature2": feature2},
+        name="points2",
+    )
+
+    widget = FeaturesHistogramWidget(viewer)
+    viewer.window.add_dock_widget(widget)
+
+    fig = HistogramWidget(viewer).figure
+    return deepcopy(fig)
+
 
 def test_change_layer(make_napari_viewer, brain_data, astronaut_data):
     viewer = make_napari_viewer()

From 19404398c55fa0f20646dbba8ecec44c5146186e Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 3 Jul 2023 20:07:32 +0000
Subject: [PATCH 038/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/asottile/setup-cfg-fmt: v2.3.0 → v2.4.0](https://github.com/asottile/setup-cfg-fmt/compare/v2.3.0...v2.4.0)
- [github.com/charliermarsh/ruff-pre-commit: v0.0.275 → v0.0.276](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.275...v0.0.276)
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 767aedf6..71aa4ae5 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -7,7 +7,7 @@ repos:
       - id: trailing-whitespace
 
   - repo: https://github.com/asottile/setup-cfg-fmt
-    rev: v2.3.0
+    rev: v2.4.0
     hooks:
       - id: setup-cfg-fmt
 
@@ -29,7 +29,7 @@ repos:
 
   - repo: https://github.com/charliermarsh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.0.275'
+    rev: 'v0.0.276'
     hooks:
       - id: ruff
 

From 20300a419cdfa242287e71814b5ef39153181dde Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Tue, 4 Jul 2023 11:47:44 +0200
Subject: [PATCH 039/238] added baseline image for histogram creation

---
 baseline/test_feature_histogram2.png          | Bin 0 -> 12860 bytes
 src/napari_matplotlib/tests/test_histogram.py |   4 +++-
 2 files changed, 3 insertions(+), 1 deletion(-)
 create mode 100644 baseline/test_feature_histogram2.png

diff --git a/baseline/test_feature_histogram2.png b/baseline/test_feature_histogram2.png
new file mode 100644
index 0000000000000000000000000000000000000000..b7bb19e039d73adfd05f0d3860a5a1c4d0711f72
GIT binary patch
diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py
index 00c8092f..a478a2c2 100644
--- a/src/napari_matplotlib/tests/test_histogram.py
+++ b/src/napari_matplotlib/tests/test_histogram.py
@@ -69,6 +69,7 @@ def test_feature_histogram(make_napari_viewer):
     viewer.layers.selection.add(viewer.layers[1])
     assert_figures_equal(widget.figure, fig1)
 
+
 @pytest.mark.mpl_image_compare
 def test_feature_histogram2(make_napari_viewer):
     import numpy as np    
@@ -92,8 +93,9 @@ def test_feature_histogram2(make_napari_viewer):
 
     widget = FeaturesHistogramWidget(viewer)
     viewer.window.add_dock_widget(widget)
+    widget._set_axis_keys("feature1")
 
-    fig = HistogramWidget(viewer).figure
+    fig = FeaturesHistogramWidget(viewer).figure
     return deepcopy(fig)
 
 

From 4d8cf5eb9dbac715b9b5b7f573dbcb1a8bb73587 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 21 Jul 2023 09:15:51 +0100
Subject: [PATCH 040/238] Pin max verison of napari

---
 docs/changelog.rst | 7 +++++++
 setup.cfg          | 2 +-
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 64771d30..574cd02e 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,6 +1,13 @@
 Changelog
 =========
 
+1.0.1
+-----
+Bug fixes
+~~~~~~~~~
+- Pinned that maximum version of `napari` to 0.4.17, since `napari-matplotlib`
+  does not yet work with `napari 0.4.18`.
+
 1.0.0
 -----
 
diff --git a/setup.cfg b/setup.cfg
index b03f446c..dfd52347 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -28,7 +28,7 @@ project_urls =
 packages = find:
 install_requires =
     matplotlib
-    napari
+    napari<0.4.18
     numpy
     tinycss2
 python_requires = >=3.8

From d0a3e830fcd02bda0851e4228ddf5dbf289709f2 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 21 Jul 2023 09:26:48 +0100
Subject: [PATCH 041/238] Fix changelog

---
 docs/changelog.rst | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 574cd02e..a31a0d3a 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -5,8 +5,8 @@ Changelog
 -----
 Bug fixes
 ~~~~~~~~~
-- Pinned that maximum version of `napari` to 0.4.17, since `napari-matplotlib`
-  does not yet work with `napari 0.4.18`.
+- Pinned that maximum version of `napari` to 0.4.17, since ``napari-matplotlib``
+  does not yet work with ``napari`` 0.4.18.
 
 1.0.0
 -----

From 56a95736008ee3b2c3f3ef6dee66645458099432 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 24 Jul 2023 19:51:45 +0000
Subject: [PATCH 042/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/psf/black: 23.3.0 → 23.7.0](https://github.com/psf/black/compare/23.3.0...23.7.0)
- https://github.com/charliermarsh/ruff-pre-commit → https://github.com/astral-sh/ruff-pre-commit
- [github.com/astral-sh/ruff-pre-commit: v0.0.276 → v0.0.280](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.276...v0.0.280)
---
 .pre-commit-config.yaml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 71aa4ae5..caf38d16 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -12,7 +12,7 @@ repos:
       - id: setup-cfg-fmt
 
   - repo: https://github.com/psf/black
-    rev: 23.3.0
+    rev: 23.7.0
     hooks:
       - id: black
 
@@ -27,9 +27,9 @@ repos:
      - id: mypy
        additional_dependencies: [numpy, matplotlib]
 
-  - repo: https://github.com/charliermarsh/ruff-pre-commit
+  - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.0.276'
+    rev: 'v0.0.280'
     hooks:
       - id: ruff
 

From 38fbccd9c6da1d91484a87eba27dadf8fff0b82e Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 21 Jul 2023 09:37:29 +0100
Subject: [PATCH 043/238] Don't read full cube into mem for histogramming

---
 src/napari_matplotlib/histogram.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 39ad41a3..50c2bcc4 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -33,7 +33,6 @@ def draw(self) -> None:
         Clear the axes and histogram the currently selected layer/slice.
         """
         layer = self.layers[0]
-        bins = np.linspace(np.min(layer.data), np.max(layer.data), 100)
 
         if layer.data.ndim - layer.rgb == 3:
             # 3D data, can be single channel or RGB
@@ -42,6 +41,10 @@ def draw(self) -> None:
         else:
             data = layer.data
 
+        # Important to calculate bins after slicing 3D data, to avoid reading
+        # whole cube into memory.
+        bins = np.linspace(np.min(layer.data), np.max(layer.data), 100)
+
         if layer.rgb:
             # Histogram RGB channels independently
             for i, c in enumerate("rgb"):

From 1aa3efd13a57d0dc5ddd2b09af058bc27a125f9c Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 26 Jul 2023 09:16:41 +0100
Subject: [PATCH 044/238] Actually don't read all bins into memory...

---
 src/napari_matplotlib/histogram.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 50c2bcc4..3b0c4234 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -43,7 +43,7 @@ def draw(self) -> None:
 
         # Important to calculate bins after slicing 3D data, to avoid reading
         # whole cube into memory.
-        bins = np.linspace(np.min(layer.data), np.max(layer.data), 100)
+        bins = np.linspace(np.min(data), np.max(data), 100)
 
         if layer.rgb:
             # Histogram RGB channels independently

From 804934c7b5706d7d2fbd7845c3aab94584961561 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 26 Jul 2023 09:25:54 +0100
Subject: [PATCH 045/238] Read data into memory

---
 src/napari_matplotlib/histogram.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 3b0c4234..a6cec3cf 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -33,6 +33,7 @@ def draw(self) -> None:
         Clear the axes and histogram the currently selected layer/slice.
         """
         layer = self.layers[0]
+        print(layer.data.shape)
 
         if layer.data.ndim - layer.rgb == 3:
             # 3D data, can be single channel or RGB
@@ -40,10 +41,13 @@ def draw(self) -> None:
             self.axes.set_title(f"z={self.current_z}")
         else:
             data = layer.data
+        # Read data into memory if it's a dask array
+        data = np.asarray(data)
 
         # Important to calculate bins after slicing 3D data, to avoid reading
         # whole cube into memory.
         bins = np.linspace(np.min(data), np.max(data), 100)
+        print(bins)
 
         if layer.rgb:
             # Histogram RGB channels independently

From e84958d2b9d9a0c86f18a5b1e9281bd791dc65ce Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 26 Jul 2023 15:21:29 +0100
Subject: [PATCH 046/238] Remove print statements

---
 src/napari_matplotlib/histogram.py | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index a6cec3cf..ab098f38 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -33,7 +33,6 @@ def draw(self) -> None:
         Clear the axes and histogram the currently selected layer/slice.
         """
         layer = self.layers[0]
-        print(layer.data.shape)
 
         if layer.data.ndim - layer.rgb == 3:
             # 3D data, can be single channel or RGB
@@ -47,7 +46,6 @@ def draw(self) -> None:
         # Important to calculate bins after slicing 3D data, to avoid reading
         # whole cube into memory.
         bins = np.linspace(np.min(data), np.max(data), 100)
-        print(bins)
 
         if layer.rgb:
             # Histogram RGB channels independently

From 0523d2844a7e02a9702a3e31f21bd767e8ad61f9 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 26 Jul 2023 15:23:04 +0100
Subject: [PATCH 047/238] Add changelog

---
 docs/changelog.rst | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index a31a0d3a..e3b62dae 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,5 +1,16 @@
 Changelog
 =========
+1.0.2
+-----
+Bug fixes
+~~~~~~~~~
+- A full dataset is no longer read into memory when using ``HistogramWidget``.
+  Only the current slice is loaded.
+
+Changes
+~~~~~~~
+- Histogram bin limits are now caclualted from the slice being histogrammed, and
+  not the whole dataset. This is as a result of the above bug fix.
 
 1.0.1
 -----

From aa9eaf4e194b60a8a16cf1fbde07d005429e87bf Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 21 Jul 2023 09:34:38 +0100
Subject: [PATCH 048/238] Un-pin napari for testing

---
 setup.cfg | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.cfg b/setup.cfg
index dfd52347..b03f446c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -28,7 +28,7 @@ project_urls =
 packages = find:
 install_requires =
     matplotlib
-    napari<0.4.18
+    napari
     numpy
     tinycss2
 python_requires = >=3.8

From 84f4486f8f742e753c7e7c9938826b26b9fa3475 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 26 Jul 2023 15:57:34 +0100
Subject: [PATCH 049/238] Fix all the tests!

---
 setup.cfg                                     |   2 +-
 .../tests/baseline/test_custom_theme.png      | Bin 15662 -> 15625 bytes
 .../tests/baseline/test_slice_2D.png          | Bin 21290 -> 21061 bytes
 .../scatter/baseline/test_scatter_2D.png      | Bin 18537 -> 18548 bytes
 src/napari_matplotlib/tests/test_theme.py     |   6 ++++--
 5 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/setup.cfg b/setup.cfg
index b03f446c..f7ddd381 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -55,7 +55,7 @@ docs =
     sphinx-automodapi
     sphinx-gallery
 testing =
-    napari[pyqt6-experimental]
+    napari[pyqt6-experimental]>=0.4.18
     pooch
     pyqt6
     pytest
diff --git a/src/napari_matplotlib/tests/baseline/test_custom_theme.png b/src/napari_matplotlib/tests/baseline/test_custom_theme.png
index 65c43a49c60da77f7c6a5401f10aca4a95771f01..c385852288bb57b741b8d6373cdc4ae1a43f8f3f 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_slice_2D.png b/src/napari_matplotlib/tests/baseline/test_slice_2D.png
index 5b73091c4ef1c117a1ae5b45bad918bb734bbaa5..639f25b84b2bfc224f3344d75995265fa56aa79e 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/scatter/baseline/test_scatter_2D.png b/src/napari_matplotlib/tests/scatter/baseline/test_scatter_2D.png
index 3b55066636e11453c90846182b3df5aa049d161b..f96b5354f90467d86b7a531bccbcefaba47f7ecb 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/test_theme.py b/src/napari_matplotlib/tests/test_theme.py
index a3642f8f..2a340c89 100644
--- a/src/napari_matplotlib/tests/test_theme.py
+++ b/src/napari_matplotlib/tests/test_theme.py
@@ -37,9 +37,11 @@ def _mock_up_theme() -> None:
     https://napari.org/stable/gallery/new_theme.html
     """
     blue_theme = napari.utils.theme.get_theme("dark", False)
-    blue_theme.name = "blue"
+    blue_theme.label = "blue"
     blue_theme.background = "#4169e1"  # my favourite shade of blue
-    napari.utils.theme.register_theme("blue", blue_theme)
+    napari.utils.theme.register_theme(
+        "blue", blue_theme, source="napari-mpl-tests"
+    )
 
 
 def test_theme_background_check(make_napari_viewer):

From 22192b6aadb07cecec02667ce9bd1519fc361408 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 26 Jul 2023 16:03:30 +0100
Subject: [PATCH 050/238] Pin napari version to build docs

---
 setup.cfg | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.cfg b/setup.cfg
index f7ddd381..229b5777 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -47,7 +47,7 @@ napari.manifest =
 
 [options.extras_require]
 docs =
-    napari[all]
+    napari[all]==0.4.17
     numpydoc
     pydata-sphinx-theme
     qtgallery

From 915944988c27812c44b26ab3b0696b2c2200610b Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 26 Jul 2023 16:10:20 +0100
Subject: [PATCH 051/238] Add changelog entry

---
 docs/changelog.rst | 1 +
 1 file changed, 1 insertion(+)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index e3b62dae..103cd80e 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -6,6 +6,7 @@ Bug fixes
 ~~~~~~~~~
 - A full dataset is no longer read into memory when using ``HistogramWidget``.
   Only the current slice is loaded.
+- Fixed compatibility with napari 0.4.18.
 
 Changes
 ~~~~~~~

From 7b11356779d5450453e3a37174a7d4b538d93861 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 31 Jul 2023 20:37:13 +0000
Subject: [PATCH 052/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.280 → v0.0.281](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.280...v0.0.281)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index caf38d16..1fb3a9df 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -29,7 +29,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.0.280'
+    rev: 'v0.0.281'
     hooks:
       - id: ruff
 

From 523212962063ad4f7173c892126f6cb7c8d1062d Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 14 Aug 2023 20:22:12 +0000
Subject: [PATCH 053/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/pre-commit/mirrors-mypy: v1.4.1 → v1.5.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.4.1...v1.5.0)
- [github.com/astral-sh/ruff-pre-commit: v0.0.281 → v0.0.284](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.281...v0.0.284)
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 1fb3a9df..33aee57b 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -22,14 +22,14 @@ repos:
       - id: napari-plugin-checks
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.4.1
+    rev: v1.5.0
     hooks:
      - id: mypy
        additional_dependencies: [numpy, matplotlib]
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.0.281'
+    rev: 'v0.0.284'
     hooks:
       - id: ruff
 

From f1360063fdad4314156881eb32142d874f8920bc Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 21 Aug 2023 20:26:54 +0000
Subject: [PATCH 054/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/pre-commit/mirrors-mypy: v1.5.0 → v1.5.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.5.0...v1.5.1)
- [github.com/astral-sh/ruff-pre-commit: v0.0.284 → v0.0.285](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.284...v0.0.285)
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 33aee57b..4e322a0d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -22,14 +22,14 @@ repos:
       - id: napari-plugin-checks
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.5.0
+    rev: v1.5.1
     hooks:
      - id: mypy
        additional_dependencies: [numpy, matplotlib]
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.0.284'
+    rev: 'v0.0.285'
     hooks:
       - id: ruff
 

From 2119f45d6d1133b6d818334cdd1920d3ea55a77f Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 23 Aug 2023 14:34:57 +0100
Subject: [PATCH 055/238] Fix testing extra name

---
 setup.cfg | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.cfg b/setup.cfg
index 229b5777..e1fc9e73 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -55,7 +55,7 @@ docs =
     sphinx-automodapi
     sphinx-gallery
 testing =
-    napari[pyqt6-experimental]>=0.4.18
+    napari[pyqt6_experimental]>=0.4.18
     pooch
     pyqt6
     pytest

From 70248a71822c364ba526e45acd53337776b1a3d1 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 23 Aug 2023 14:41:57 +0100
Subject: [PATCH 056/238] Remove setup-cfg-format from pre-commit

---
 .pre-commit-config.yaml | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 4e322a0d..b78e43e1 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -6,11 +6,6 @@ repos:
       - id: end-of-file-fixer
       - id: trailing-whitespace
 
-  - repo: https://github.com/asottile/setup-cfg-fmt
-    rev: v2.4.0
-    hooks:
-      - id: setup-cfg-fmt
-
   - repo: https://github.com/psf/black
     rev: 23.7.0
     hooks:

From 726b66aca1ba56a690264501e75272258211dbad Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 23 Aug 2023 14:27:14 +0100
Subject: [PATCH 057/238] Explcitly check dimensions in slice tests

---
 src/napari_matplotlib/tests/conftest.py   | 4 +++-
 src/napari_matplotlib/tests/test_slice.py | 8 ++++++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/tests/conftest.py b/src/napari_matplotlib/tests/conftest.py
index 6b2a813f..4d07c706 100644
--- a/src/napari_matplotlib/tests/conftest.py
+++ b/src/napari_matplotlib/tests/conftest.py
@@ -1,7 +1,9 @@
 import os
 from pathlib import Path
+from typing import Any, Dict, Tuple
 
 import numpy as np
+import numpy.typing as npt
 import pytest
 from skimage import data
 
@@ -18,7 +20,7 @@ def image_data(request):
 
 
 @pytest.fixture
-def astronaut_data():
+def astronaut_data() -> Tuple[npt.NDArray[Any], Dict[Any, Any]]:
     return data.astronaut(), {"rgb": True}
 
 
diff --git a/src/napari_matplotlib/tests/test_slice.py b/src/napari_matplotlib/tests/test_slice.py
index 412e71c3..fa25c3d4 100644
--- a/src/napari_matplotlib/tests/test_slice.py
+++ b/src/napari_matplotlib/tests/test_slice.py
@@ -9,7 +9,11 @@
 def test_slice_3D(make_napari_viewer, brain_data):
     viewer = make_napari_viewer()
     viewer.theme = "light"
+
+    data = brain_data[0]
+    assert data.ndim == 3
     viewer.add_image(brain_data[0], **brain_data[1])
+
     axis = viewer.dims.last_used
     slice_no = brain_data[0].shape[0] - 1
     viewer.dims.set_current_step(axis, slice_no)
@@ -23,7 +27,11 @@ def test_slice_3D(make_napari_viewer, brain_data):
 def test_slice_2D(make_napari_viewer, astronaut_data):
     viewer = make_napari_viewer()
     viewer.theme = "light"
+
+    data = astronaut_data[0]
+    assert data.ndim == 2
     viewer.add_image(astronaut_data[0], **astronaut_data[1])
+
     fig = SliceWidget(viewer).figure
     # Need to return a copy, as original figure is too eagerley garbage
     # collected by the widget

From 8ac1cd2c68118123017135622f54bc443ea032b8 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 23 Aug 2023 14:47:42 +0100
Subject: [PATCH 058/238] Fix data dimensions

---
 src/napari_matplotlib/tests/test_slice.py | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/src/napari_matplotlib/tests/test_slice.py b/src/napari_matplotlib/tests/test_slice.py
index fa25c3d4..0111e185 100644
--- a/src/napari_matplotlib/tests/test_slice.py
+++ b/src/napari_matplotlib/tests/test_slice.py
@@ -11,11 +11,11 @@ def test_slice_3D(make_napari_viewer, brain_data):
     viewer.theme = "light"
 
     data = brain_data[0]
-    assert data.ndim == 3
-    viewer.add_image(brain_data[0], **brain_data[1])
+    assert data.ndim == 3, data.shape
+    viewer.add_image(data, **brain_data[1])
 
     axis = viewer.dims.last_used
-    slice_no = brain_data[0].shape[0] - 1
+    slice_no = data.shape[0] - 1
     viewer.dims.set_current_step(axis, slice_no)
     fig = SliceWidget(viewer).figure
     # Need to return a copy, as original figure is too eagerley garbage
@@ -28,9 +28,10 @@ def test_slice_2D(make_napari_viewer, astronaut_data):
     viewer = make_napari_viewer()
     viewer.theme = "light"
 
-    data = astronaut_data[0]
-    assert data.ndim == 2
-    viewer.add_image(astronaut_data[0], **astronaut_data[1])
+    # Take first RGB channel
+    data = astronaut_data[0][:, :, 0]
+    assert data.ndim == 2, data.shape
+    viewer.add_image(data, **astronaut_data[1])
 
     fig = SliceWidget(viewer).figure
     # Need to return a copy, as original figure is too eagerley garbage

From 2b026ec08cc2bf9bfa5acfb4305d816316bef1bd Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 23 Aug 2023 14:50:26 +0100
Subject: [PATCH 059/238] Update 3D slice test image

---
 .../tests/baseline/test_slice_3D.png          | Bin 14698 -> 14356 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)

diff --git a/src/napari_matplotlib/tests/baseline/test_slice_3D.png b/src/napari_matplotlib/tests/baseline/test_slice_3D.png
index 43c8c3b6c0b276f89860ef1b630fafdd219070b9..c30211dab12d3c276a1427990fc86c01b2dff46a 100644
GIT binary patch

From bc496fcb528239466379b0ffea2f067760c89247 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 23 Aug 2023 14:50:52 +0100
Subject: [PATCH 060/238] Fix adding 2D slice data

---
 src/napari_matplotlib/tests/test_slice.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/tests/test_slice.py b/src/napari_matplotlib/tests/test_slice.py
index 0111e185..32eb9ad4 100644
--- a/src/napari_matplotlib/tests/test_slice.py
+++ b/src/napari_matplotlib/tests/test_slice.py
@@ -31,7 +31,7 @@ def test_slice_2D(make_napari_viewer, astronaut_data):
     # Take first RGB channel
     data = astronaut_data[0][:, :, 0]
     assert data.ndim == 2, data.shape
-    viewer.add_image(data, **astronaut_data[1])
+    viewer.add_image(data)
 
     fig = SliceWidget(viewer).figure
     # Need to return a copy, as original figure is too eagerley garbage

From ed278ec3471dff3845a465c9329a9cfb669f8d79 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 23 Aug 2023 14:59:59 +0100
Subject: [PATCH 061/238] Fix slicing 2D images

---
 src/napari_matplotlib/slice.py                |  37 +++++++++++++-----
 .../tests/baseline/test_slice_2D.png          | Bin 21061 -> 33587 bytes
 2 files changed, 27 insertions(+), 10 deletions(-)

diff --git a/src/napari_matplotlib/slice.py b/src/napari_matplotlib/slice.py
index e3aa80b2..f0d01f3f 100644
--- a/src/napari_matplotlib/slice.py
+++ b/src/napari_matplotlib/slice.py
@@ -1,4 +1,4 @@
-from typing import Any, Dict, Optional, Tuple
+from typing import Any, Dict, List, Optional, Tuple
 
 import matplotlib.ticker as mticker
 import napari
@@ -12,7 +12,6 @@
 __all__ = ["SliceWidget"]
 
 _dims_sel = ["x", "y"]
-_dims = ["x", "y", "z"]
 
 
 class SliceWidget(SingleAxesWidget):
@@ -37,7 +36,7 @@ def __init__(
         self.dim_selector = QComboBox()
         button_layout.addWidget(QLabel("Slice axis:"))
         button_layout.addWidget(self.dim_selector)
-        self.dim_selector.addItems(_dims)
+        self.dim_selector.addItems(["x", "y", "z"])
 
         self.slice_selectors = {}
         for d in _dims_sel:
@@ -61,7 +60,7 @@ def _layer(self) -> napari.layers.Layer:
         return self.layers[0]
 
     @property
-    def current_dim(self) -> str:
+    def current_dim_name(self) -> str:
         """
         Currently selected slice dimension.
         """
@@ -74,12 +73,27 @@ def current_dim_index(self) -> int:
         """
         # Note the reversed list because in napari the z-axis is the first
         # numpy axis
-        return _dims[::-1].index(self.current_dim)
+        return self._dim_names[::-1].index(self.current_dim_name)
+
+    @property
+    def _dim_names(self) -> List[str]:
+        """
+        List of dimension names. This is a property as it varies depending on the
+        dimensionality of the currently selected data.
+        """
+        if self._layer.data.ndim == 2:
+            return ["x", "y"]
+        elif self._layer.data.ndim == 3:
+            return ["x", "y", "z"]
+        else:
+            raise RuntimeError("Don't know how to handle ndim != 2 or 3")
 
     @property
     def _selector_values(self) -> Dict[str, int]:
         """
         Values of the slice selectors.
+
+        Mapping from dimension name to value.
         """
         return {d: self.slice_selectors[d].value() for d in _dims_sel}
 
@@ -87,19 +101,22 @@ def _get_xy(self) -> Tuple[npt.NDArray[Any], npt.NDArray[Any]]:
         """
         Get data for plotting.
         """
-        x = np.arange(self._layer.data.shape[self.current_dim_index])
+        dim_index = self.current_dim_index
+        if self._layer.data.ndim == 2:
+            dim_index -= 1
+        x = np.arange(self._layer.data.shape[dim_index])
 
         vals = self._selector_values
         vals.update({"z": self.current_z})
 
         slices = []
-        for d in _dims:
-            if d == self.current_dim:
+        for dim_name in self._dim_names:
+            if dim_name == self.current_dim_name:
                 # Select all data along this axis
                 slices.append(slice(None))
             else:
                 # Select specific index
-                val = vals[d]
+                val = vals[dim_name]
                 slices.append(slice(val, val + 1))
 
         # Reverse since z is the first axis in napari
@@ -115,7 +132,7 @@ def draw(self) -> None:
         x, y = self._get_xy()
 
         self.axes.plot(x, y)
-        self.axes.set_xlabel(self.current_dim)
+        self.axes.set_xlabel(self.current_dim_name)
         self.axes.set_title(self._layer.name)
         # Make sure all ticks lie on integer values
         self.axes.xaxis.set_major_locator(
diff --git a/src/napari_matplotlib/tests/baseline/test_slice_2D.png b/src/napari_matplotlib/tests/baseline/test_slice_2D.png
index 639f25b84b2bfc224f3344d75995265fa56aa79e..ee3ce3b69a960ddfb07c347ef92c8c5f351d2472 100644
GIT binary patch

From f5f74f182591788011263bd8998162f03e174882 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 23 Aug 2023 15:00:59 +0100
Subject: [PATCH 062/238] Add changelog

---
 docs/changelog.rst | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 103cd80e..f7425bfe 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,5 +1,11 @@
 Changelog
 =========
+1.0.3
+-----
+Bug fixes
+~~~~~~~~~
+- Fixed creating 1D slices of 2D images.
+
 1.0.2
 -----
 Bug fixes

From 08ee5d097caa97e09805fe5faae1eb8b29e9042b Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 23 Aug 2023 15:05:22 +0100
Subject: [PATCH 063/238] Remove conftest typing

---
 src/napari_matplotlib/tests/conftest.py | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/napari_matplotlib/tests/conftest.py b/src/napari_matplotlib/tests/conftest.py
index 4d07c706..6b2a813f 100644
--- a/src/napari_matplotlib/tests/conftest.py
+++ b/src/napari_matplotlib/tests/conftest.py
@@ -1,9 +1,7 @@
 import os
 from pathlib import Path
-from typing import Any, Dict, Tuple
 
 import numpy as np
-import numpy.typing as npt
 import pytest
 from skimage import data
 
@@ -20,7 +18,7 @@ def image_data(request):
 
 
 @pytest.fixture
-def astronaut_data() -> Tuple[npt.NDArray[Any], Dict[Any, Any]]:
+def astronaut_data():
     return data.astronaut(), {"rgb": True}
 
 

From a9d41dbfd73311577e9aa6bed3c00c24f30981d6 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 23 Aug 2023 16:31:10 +0100
Subject: [PATCH 064/238] Fix slicing selection

---
 src/napari_matplotlib/slice.py            | 85 ++++++++++++-----------
 src/napari_matplotlib/tests/test_slice.py | 24 +++++++
 2 files changed, 70 insertions(+), 39 deletions(-)

diff --git a/src/napari_matplotlib/slice.py b/src/napari_matplotlib/slice.py
index f0d01f3f..43635a83 100644
--- a/src/napari_matplotlib/slice.py
+++ b/src/napari_matplotlib/slice.py
@@ -1,18 +1,23 @@
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any, List, Optional, Tuple
 
 import matplotlib.ticker as mticker
 import napari
 import numpy as np
 import numpy.typing as npt
-from qtpy.QtWidgets import QComboBox, QHBoxLayout, QLabel, QSpinBox, QWidget
+from qtpy.QtCore import Qt
+from qtpy.QtWidgets import (
+    QComboBox,
+    QLabel,
+    QSlider,
+    QVBoxLayout,
+    QWidget,
+)
 
 from .base import SingleAxesWidget
 from .util import Interval
 
 __all__ = ["SliceWidget"]
 
-_dims_sel = ["x", "y"]
-
 
 class SliceWidget(SingleAxesWidget):
     """
@@ -30,28 +35,44 @@ def __init__(
         # Setup figure/axes
         super().__init__(napari_viewer, parent=parent)
 
-        button_layout = QHBoxLayout()
-        self.layout().addLayout(button_layout)
-
         self.dim_selector = QComboBox()
+        self.dim_selector.addItems(["x", "y"])
+
+        self.slice_selector = QSlider(orientation=Qt.Orientation.Horizontal)
+
+        # Create widget layout
+        button_layout = QVBoxLayout()
         button_layout.addWidget(QLabel("Slice axis:"))
         button_layout.addWidget(self.dim_selector)
-        self.dim_selector.addItems(["x", "y", "z"])
-
-        self.slice_selectors = {}
-        for d in _dims_sel:
-            self.slice_selectors[d] = QSpinBox()
-            button_layout.addWidget(QLabel(f"{d}:"))
-            button_layout.addWidget(self.slice_selectors[d])
+        button_layout.addWidget(self.slice_selector)
+        self.layout().addLayout(button_layout)
 
         # Setup callbacks
-        # Re-draw when any of the combon/spin boxes are updated
+        # Re-draw when any of the combo/slider is updated
         self.dim_selector.currentTextChanged.connect(self._draw)
-        for d in _dims_sel:
-            self.slice_selectors[d].textChanged.connect(self._draw)
+        self.slice_selector.valueChanged.connect(self._draw)
 
         self._update_layers(None)
 
+    def on_update_layers(self) -> None:
+        """
+        Called when layer selection is updated.
+        """
+        if self.current_dim_name == "x":
+            max = self._layer.data.shape[-2]
+        elif self.current_dim_name == "y":
+            max = self._layer.data.shape[-1]
+        else:
+            raise RuntimeError("dim name must be x or y")
+        self.slice_selector.setRange(0, max)
+
+    @property
+    def _slice_width(self) -> int:
+        """
+        Width of the slice being plotted.
+        """
+        return self._layer.data.shape[self.current_dim_index] - 1
+
     @property
     def _layer(self) -> napari.layers.Layer:
         """
@@ -73,7 +94,7 @@ def current_dim_index(self) -> int:
         """
         # Note the reversed list because in napari the z-axis is the first
         # numpy axis
-        return self._dim_names[::-1].index(self.current_dim_name)
+        return self._dim_names.index(self.current_dim_name)
 
     @property
     def _dim_names(self) -> List[str]:
@@ -82,45 +103,31 @@ def _dim_names(self) -> List[str]:
         dimensionality of the currently selected data.
         """
         if self._layer.data.ndim == 2:
-            return ["x", "y"]
+            return ["y", "x"]
         elif self._layer.data.ndim == 3:
-            return ["x", "y", "z"]
+            return ["z", "y", "x"]
         else:
             raise RuntimeError("Don't know how to handle ndim != 2 or 3")
 
-    @property
-    def _selector_values(self) -> Dict[str, int]:
-        """
-        Values of the slice selectors.
-
-        Mapping from dimension name to value.
-        """
-        return {d: self.slice_selectors[d].value() for d in _dims_sel}
-
     def _get_xy(self) -> Tuple[npt.NDArray[Any], npt.NDArray[Any]]:
         """
         Get data for plotting.
         """
-        dim_index = self.current_dim_index
-        if self._layer.data.ndim == 2:
-            dim_index -= 1
-        x = np.arange(self._layer.data.shape[dim_index])
-
-        vals = self._selector_values
-        vals.update({"z": self.current_z})
+        val = self.slice_selector.value()
 
         slices = []
         for dim_name in self._dim_names:
             if dim_name == self.current_dim_name:
                 # Select all data along this axis
                 slices.append(slice(None))
+            elif dim_name == "z":
+                # Only select the currently viewed z-index
+                slices.append(slice(self.current_z, self.current_z + 1))
             else:
                 # Select specific index
-                val = vals[dim_name]
                 slices.append(slice(val, val + 1))
 
-        # Reverse since z is the first axis in napari
-        slices = slices[::-1]
+        x = np.arange(self._slice_width)
         y = self._layer.data[tuple(slices)].ravel()
 
         return x, y
diff --git a/src/napari_matplotlib/tests/test_slice.py b/src/napari_matplotlib/tests/test_slice.py
index 32eb9ad4..ab38f716 100644
--- a/src/napari_matplotlib/tests/test_slice.py
+++ b/src/napari_matplotlib/tests/test_slice.py
@@ -37,3 +37,27 @@ def test_slice_2D(make_napari_viewer, astronaut_data):
     # Need to return a copy, as original figure is too eagerley garbage
     # collected by the widget
     return deepcopy(fig)
+
+
+def test_slice_axes(make_napari_viewer, astronaut_data):
+    viewer = make_napari_viewer()
+    viewer.theme = "light"
+
+    # Take first RGB channel
+    data = astronaut_data[0][:256, :, 0]
+    # Shape:
+    # x: 0 > 512
+    # y: 0 > 256
+    assert data.ndim == 2, data.shape
+    # Make sure data isn't square for later tests
+    assert data.shape[0] != data.shape[1]
+    viewer.add_image(data)
+
+    widget = SliceWidget(viewer)
+    assert widget._dim_names == ["y", "x"]
+    assert widget.current_dim_name == "x"
+    assert widget.slice_selector.value() == 0
+    assert widget.slice_selector.minimum() == 0
+    assert widget.slice_selector.maximum() == data.shape[0]
+    # x/y are flipped in napari
+    assert widget._slice_width == data.shape[1]

From b73bf6fb8415ba8a1c8fe2eef8501d2e1459ccbb Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 23 Aug 2023 16:33:27 +0100
Subject: [PATCH 065/238] Add changelog

---
 docs/changelog.rst | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index f7425bfe..2304fecf 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -2,9 +2,17 @@ Changelog
 =========
 1.0.3
 -----
+Changes
+~~~~~~~
+- The slice widget is now limited to slicing along the x/y dimensions. Support
+  for slicing along z has been removed for now to make the code simpler.
+- The slice widget now uses a slider to select the slice value.
+
 Bug fixes
 ~~~~~~~~~
 - Fixed creating 1D slices of 2D images.
+- Removed the limitation that only the first 99 indices could be sliced using
+  the slice widget.
 
 1.0.2
 -----

From 9eb43eefaa4e114b9b1772c8df59e66963afce11 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 23 Aug 2023 16:44:14 +0100
Subject: [PATCH 066/238] Fix tests

---
 src/napari_matplotlib/slice.py            | 6 ++++--
 src/napari_matplotlib/tests/test_slice.py | 2 +-
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/napari_matplotlib/slice.py b/src/napari_matplotlib/slice.py
index 43635a83..393f2e45 100644
--- a/src/napari_matplotlib/slice.py
+++ b/src/napari_matplotlib/slice.py
@@ -58,20 +58,22 @@ def on_update_layers(self) -> None:
         """
         Called when layer selection is updated.
         """
+        if not len(self.layers):
+            return
         if self.current_dim_name == "x":
             max = self._layer.data.shape[-2]
         elif self.current_dim_name == "y":
             max = self._layer.data.shape[-1]
         else:
             raise RuntimeError("dim name must be x or y")
-        self.slice_selector.setRange(0, max)
+        self.slice_selector.setRange(0, max - 1)
 
     @property
     def _slice_width(self) -> int:
         """
         Width of the slice being plotted.
         """
-        return self._layer.data.shape[self.current_dim_index] - 1
+        return self._layer.data.shape[self.current_dim_index]
 
     @property
     def _layer(self) -> napari.layers.Layer:
diff --git a/src/napari_matplotlib/tests/test_slice.py b/src/napari_matplotlib/tests/test_slice.py
index ab38f716..368a7ded 100644
--- a/src/napari_matplotlib/tests/test_slice.py
+++ b/src/napari_matplotlib/tests/test_slice.py
@@ -58,6 +58,6 @@ def test_slice_axes(make_napari_viewer, astronaut_data):
     assert widget.current_dim_name == "x"
     assert widget.slice_selector.value() == 0
     assert widget.slice_selector.minimum() == 0
-    assert widget.slice_selector.maximum() == data.shape[0]
+    assert widget.slice_selector.maximum() == data.shape[0] - 1
     # x/y are flipped in napari
     assert widget._slice_width == data.shape[1]

From 5e9604b9789b121517bdd3f8444511e9796e1cac Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 25 Aug 2023 16:58:28 +0100
Subject: [PATCH 067/238] Fix test figure location

---
 .../tests/baseline/test_feature_histogram2.png  | Bin 0 -> 12860 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 src/napari_matplotlib/tests/baseline/test_feature_histogram2.png

diff --git a/src/napari_matplotlib/tests/baseline/test_feature_histogram2.png b/src/napari_matplotlib/tests/baseline/test_feature_histogram2.png
new file mode 100644
index 0000000000000000000000000000000000000000..b7bb19e039d73adfd05f0d3860a5a1c4d0711f72
GIT binary patch

From 80ec91fb4f333e0e4d131f392e58851e3716901d Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 25 Aug 2023 17:00:10 +0100
Subject: [PATCH 068/238] Run pre-commit and fix typing

---
 src/napari_matplotlib/histogram.py            | 8 ++++----
 src/napari_matplotlib/tests/test_histogram.py | 6 +++---
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index c5eb9f41..66aa7acc 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -122,7 +122,7 @@ def _get_valid_axis_keys(self) -> List[str]:
         else:
             return self.layers[0].features.keys()
 
-    def _get_data(self) -> Tuple[npt.NDArray[Any], str]:
+    def _get_data(self) -> Tuple[Optional[npt.NDArray[Any]], str]:
         """Get the plot data.
 
         Returns
@@ -137,12 +137,12 @@ def _get_data(self) -> Tuple[npt.NDArray[Any], str]:
         if not hasattr(self.layers[0], "features"):
             # if the selected layer doesn't have a featuretable,
             # skip draw
-            return [], ""
+            return None, ""
 
         feature_table = self.layers[0].features
 
         if (len(feature_table) == 0) or (self.x_axis_key is None):
-            return [], ""
+            return None, ""
 
         data = feature_table[self.x_axis_key]
         x_axis_name = self.x_axis_key.replace("_", " ")
@@ -164,7 +164,7 @@ def draw(self) -> None:
         """Clear the axes and histogram the currently selected layer/slice."""
         data, x_axis_name = self._get_data()
 
-        if len(data) == 0:
+        if data is None:
             return
 
         self.axes.hist(data, bins=50, edgecolor="white", linewidth=0.3)
diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py
index a478a2c2..006c042f 100644
--- a/src/napari_matplotlib/tests/test_histogram.py
+++ b/src/napari_matplotlib/tests/test_histogram.py
@@ -1,5 +1,6 @@
 from copy import deepcopy
 
+import numpy as np
 import pytest
 
 from napari_matplotlib import FeaturesHistogramWidget, HistogramWidget
@@ -35,8 +36,6 @@ def test_histogram_3D(make_napari_viewer, brain_data):
 
 
 def test_feature_histogram(make_napari_viewer):
-    import numpy as np
-
     n_points = 1000
     random_points = np.random.random((n_points, 3)) * 10
     feature1 = np.random.random(n_points)
@@ -72,7 +71,8 @@ def test_feature_histogram(make_napari_viewer):
 
 @pytest.mark.mpl_image_compare
 def test_feature_histogram2(make_napari_viewer):
-    import numpy as np    
+    import numpy as np
+
     np.random.seed(0)
     n_points = 1000
     random_points = np.random.random((n_points, 3)) * 10

From e1ccfb18d4c48abd14037490e1acd4bf3a27baa1 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 25 Aug 2023 16:55:09 +0100
Subject: [PATCH 069/238] Update docs

---
 docs/changelog.rst  | 6 +++++-
 docs/user_guide.rst | 1 +
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 2304fecf..6f77e0c3 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,7 +1,11 @@
 Changelog
 =========
-1.0.3
+1.1.0
 -----
+Additions
+~~~~~~~~~
+- Added a widget to draw a histogram of features.
+
 Changes
 ~~~~~~~
 - The slice widget is now limited to slicing along the x/y dimensions. Support
diff --git a/docs/user_guide.rst b/docs/user_guide.rst
index 0872e540..fbd48db1 100644
--- a/docs/user_guide.rst
+++ b/docs/user_guide.rst
@@ -30,6 +30,7 @@ These widgets plot the data stored in the ``.features`` attribute of individual
 Currently available are:
 
 - 2D scatter plots of two features against each other.
+- Histograms of individual features.
 
 To use these:
 

From dd74f278bd64d2aaa8778b00fecb0784bb06a5f1 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 25 Aug 2023 17:15:53 +0100
Subject: [PATCH 070/238] Add testing on Python 3.11

---
 .github/workflows/test_and_deploy.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml
index df170bdf..6e170d14 100644
--- a/.github/workflows/test_and_deploy.yml
+++ b/.github/workflows/test_and_deploy.yml
@@ -20,7 +20,7 @@ jobs:
       fail-fast: false
       matrix:
         platform: [ubuntu-latest, macos-latest, windows-latest]
-        python-version: ['3.8', '3.9', '3.10']
+        python-version: ['3.8', '3.9', '3.10', '3.11']
 
     steps:
       - uses: actions/checkout@v3

From 70b77dec3d1a330af0f551355370debd288c3cf5 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 25 Aug 2023 17:16:35 +0100
Subject: [PATCH 071/238] Add tox environment

---
 tox.ini | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tox.ini b/tox.ini
index 298887e1..683992f5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py{38,39,310}
+envlist = py{38,39,310,311}
 isolated_build = true
 
 [gh-actions]

From cf7466971bc38d3998c1a3b9584fb49a06fd9747 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 25 Aug 2023 17:17:29 +0100
Subject: [PATCH 072/238] Update development status

---
 setup.cfg | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.cfg b/setup.cfg
index e1fc9e73..b05201cb 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -9,7 +9,7 @@ author_email = d.stansby@ucl.ac.uk
 license = BSD-3-Clause
 license_files = LICENSE
 classifiers =
-    Development Status :: 3 - Alpha
+    Development Status :: 5 - Production/Stable
     Framework :: napari
     Intended Audience :: Developers
     License :: OSI Approved :: BSD License

From 719e60856d1d2377d3b1447a906aa0378bfe0ed2 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 25 Aug 2023 17:19:01 +0100
Subject: [PATCH 073/238] Add link to docs in README

---
 README.md | 27 +--------------------------
 1 file changed, 1 insertion(+), 26 deletions(-)

diff --git a/README.md b/README.md
index 855c4991..e4551d23 100644
--- a/README.md
+++ b/README.md
@@ -15,32 +15,7 @@ A plugin to create Matplotlib plots from napari layers
 ## Introduction
 `napari-matplotlib` is a bridge between `napari` and `matplotlib`, making it easy to create publication quality `Matplotlib` plots based on the data loaded in `napari` layers.
 
-## Available widgets
-
-### `Slice`
-Plots 1D slices of data along a specified axis.
-![](https://raw.githubusercontent.com/matplotlib/napari-matplotlib/main/examples/slice.png)
-
-### `Histogram`
-Plots histograms of individual image layers, or RGB histograms of an RGB image
-![](https://raw.githubusercontent.com/matplotlib/napari-matplotlib/main/examples/hist.png)
-
-### `Scatter`
-Scatters the values of two similarly sized images layers against each other.
-![](https://raw.githubusercontent.com/matplotlib/napari-matplotlib/main/examples/scatter.png)
-
-## Installation
-
-You can install `napari-matplotlib` via [pip]:
-
-    pip install napari-matplotlib
-
-
-
-To install latest development version :
-
-    pip install git+https://github.com/matplotlib/napari-matplotlib.git
-
+Documentaiton can be found at https://napari-matplotlib.github.io/
 
 ## Contributing
 

From 81781d84524ea234593c32e21da3f900f586f6ae Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 28 Aug 2023 20:13:27 +0000
Subject: [PATCH 074/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.285 → v0.0.286](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.285...v0.0.286)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b78e43e1..6d7edc89 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.0.285'
+    rev: 'v0.0.286'
     hooks:
       - id: ruff
 

From ebe47b94b93a612f211a359792c6d0a83c7248ca Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 4 Sep 2023 20:32:36 +0000
Subject: [PATCH 075/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.286 → v0.0.287](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.286...v0.0.287)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6d7edc89..b30938ce 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.0.286'
+    rev: 'v0.0.287'
     hooks:
       - id: ruff
 

From 0e8b3b232377269bc0e7a8a4f6de6989515f22bb Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 11 Sep 2023 20:52:27 +0000
Subject: [PATCH 076/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1)
- [github.com/astral-sh/ruff-pre-commit: v0.0.287 → v0.0.288](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.287...v0.0.288)
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b30938ce..2e7732ce 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -7,7 +7,7 @@ repos:
       - id: trailing-whitespace
 
   - repo: https://github.com/psf/black
-    rev: 23.7.0
+    rev: 23.9.1
     hooks:
       - id: black
 
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.0.287'
+    rev: 'v0.0.288'
     hooks:
       - id: ruff
 

From 064252ba03ad924a4d012e0b85f0bee972fb5b82 Mon Sep 17 00:00:00 2001
From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com>
Date: Wed, 4 Oct 2023 12:49:03 +0200
Subject: [PATCH 077/238] added basic coloring to histogram

---
 src/napari_matplotlib/histogram.py | 27 ++++++++++++++++++++++++++-
 1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 66aa7acc..269fdc52 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -162,12 +162,37 @@ def on_update_layers(self) -> None:
 
     def draw(self) -> None:
         """Clear the axes and histogram the currently selected layer/slice."""
+
+        # get the colormap from the layer depending on its type
+        if isinstance(self.layers[0], napari.layers.Points):
+            colormap = self.layers[0].face_colormap
+            self.viewer.layers[0].face_color = self.x_axis_key
+        elif isinstance(self.layers[0], napari.layers.Vectors):
+            colormap = self.layers[0].edge_colormap
+            self.viewer.layers[0].edge_color = self.x_axis_key
+        elif isinstance(self.layers[0], napari.layers.Labels):
+            colormap = self.layers[0].colormap
+            self.viewer.layers[0].color = self.x_axis_key
+        else:
+            colormap = None
+        self.viewer.layers[self.layers[0].name].refresh_colors(True)
+        
+        # Draw the histogram
         data, x_axis_name = self._get_data()
 
         if data is None:
             return
 
-        self.axes.hist(data, bins=50, edgecolor="white", linewidth=0.3)
+        _, bins, patches = self.axes.hist(data, bins=50, edgecolor="white", linewidth=0.3)
+
+        # recolor the histogram plot
+        if colormap is not None:
+            self.bins_norm = (bins - bins.min())/(bins.max() - bins.min())
+            colors = colormap.map(self.bins_norm)
+
+            # Set histogram style:
+            for idx, patch in enumerate(patches):
+                patch.set_facecolor(colors[idx])        
 
         # set ax labels
         self.axes.set_xlabel(x_axis_name)

From 42ce86dccd8edf61991c19af1b6a079b3a4ec022 Mon Sep 17 00:00:00 2001
From: Johannes Soltwedel <38459088+jo-mueller@users.noreply.github.com>
Date: Wed, 4 Oct 2023 12:49:08 +0200
Subject: [PATCH 078/238] Create test.ipynb

---
 src/napari_matplotlib/tests/test.ipynb | 185 +++++++++++++++++++++++++
 1 file changed, 185 insertions(+)
 create mode 100644 src/napari_matplotlib/tests/test.ipynb

diff --git a/src/napari_matplotlib/tests/test.ipynb b/src/napari_matplotlib/tests/test.ipynb
new file mode 100644
index 00000000..e65c5740
--- /dev/null
+++ b/src/napari_matplotlib/tests/test.ipynb
@@ -0,0 +1,185 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import numpy as np\n",
+    "import napari\n",
+    "from napari_matplotlib import FeaturesHistogramWidget\n",
+    "import matplotlib.pyplot as plt"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 24,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "n_points = 1000\n",
+    "random_points = np.random.random((n_points, 3)) * 10\n",
+    "random_points2 = np.random.random((n_points, 3)) * 10\n",
+    "random_vectors = random_points2 - random_points\n",
+    "\n",
+    "vectors = np.stack([random_points, random_vectors], axis=1)\n",
+    "feature1 = np.random.random(n_points)\n",
+    "feature2 = np.random.normal(size=n_points)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 25,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "viewer = napari.Viewer()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 26,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "<Vectors layer 'vectors1' at 0x1ec34e98130>"
+      ]
+     },
+     "execution_count": 26,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "viewer.add_points(\n",
+    "    random_points,\n",
+    "    properties={\"feature1\": feature1, \"feature2\": feature2},\n",
+    "    name=\"points1\",\n",
+    "    face_color=\"feature1\",\n",
+    "    size=1,\n",
+    ")\n",
+    "\n",
+    "viewer.add_vectors(\n",
+    "    vectors,\n",
+    "    properties={\"feature1\": feature1, \"feature2\": feature2},\n",
+    "    name=\"vectors1\",\n",
+    "    edge_width=1,\n",
+    "    edge_color=\"feature1\",\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 27,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "<napari._qt.widgets.qt_viewer_dock_widget.QtViewerDockWidget at 0x1ec387349d0>"
+      ]
+     },
+     "execution_count": 27,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "widget = FeaturesHistogramWidget(viewer)\n",
+    "viewer.window.add_dock_widget(widget)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "'feature1'"
+      ]
+     },
+     "execution_count": 19,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "widget.x_axis_key"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 36,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "widget.viewer.layers[widget.layers[0].name].refresh_colors(True)\n",
+    "widget.viewer.layers[0].properties\n",
+    "widget.viewer.layers[widget.layers[0].name].refresh_colors(True)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 33,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "True"
+      ]
+     },
+     "execution_count": 33,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "widget.viewer.layers[1]._update_properties"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 40,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "widget.viewer.layers[0].edge_color = \"feature2\""
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "stress",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.9.18"
+  },
+  "orig_nbformat": 4
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}

From ee4f9a8f3f917ebe07ac685c602489d9b1ffe3d5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Wed, 4 Oct 2023 16:24:29 +0200
Subject: [PATCH 079/238] changed color properties of correct layer

---
 src/napari_matplotlib/histogram.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 269fdc52..6a5915b9 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -166,13 +166,13 @@ def draw(self) -> None:
         # get the colormap from the layer depending on its type
         if isinstance(self.layers[0], napari.layers.Points):
             colormap = self.layers[0].face_colormap
-            self.viewer.layers[0].face_color = self.x_axis_key
+            self.layers[0].face_color = self.x_axis_key
         elif isinstance(self.layers[0], napari.layers.Vectors):
             colormap = self.layers[0].edge_colormap
-            self.viewer.layers[0].edge_color = self.x_axis_key
+            self.layers[0].edge_color = self.x_axis_key
         elif isinstance(self.layers[0], napari.layers.Labels):
             colormap = self.layers[0].colormap
-            self.viewer.layers[0].color = self.x_axis_key
+            self.layers[0].color = self.x_axis_key
         else:
             colormap = None
         self.viewer.layers[self.layers[0].name].refresh_colors(True)

From 206f71f54002ad33a3718aa68a29429101538ba1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Wed, 4 Oct 2023 16:24:52 +0200
Subject: [PATCH 080/238] used correct refresh functions to make it work for
 different layer types

---
 src/napari_matplotlib/histogram.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 6a5915b9..82cf11c5 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -175,8 +175,11 @@ def draw(self) -> None:
             self.layers[0].color = self.x_axis_key
         else:
             colormap = None
+
+        # apply new colors to the layer
         self.viewer.layers[self.layers[0].name].refresh_colors(True)
-        
+        self.viewer.layers[self.layers[0].name].refresh()
+
         # Draw the histogram
         data, x_axis_name = self._get_data()
 

From e3540c34808486ef67078c7c53b8710397afa917 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Wed, 4 Oct 2023 16:25:17 +0200
Subject: [PATCH 081/238] made specific tests for every layer type

---
 src/napari_matplotlib/tests/test_histogram.py | 50 ++++++++++++++-----
 1 file changed, 38 insertions(+), 12 deletions(-)

diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py
index 006c042f..534d7952 100644
--- a/src/napari_matplotlib/tests/test_histogram.py
+++ b/src/napari_matplotlib/tests/test_histogram.py
@@ -36,8 +36,11 @@ def test_histogram_3D(make_napari_viewer, brain_data):
 
 
 def test_feature_histogram(make_napari_viewer):
+
     n_points = 1000
     random_points = np.random.random((n_points, 3)) * 10
+    random_directions = np.random.random((n_points, 3)) * 10
+    random_vectors = np.stack([random_points, random_directions], axis=1)
     feature1 = np.random.random(n_points)
     feature2 = np.random.normal(size=n_points)
 
@@ -47,10 +50,10 @@ def test_feature_histogram(make_napari_viewer):
         properties={"feature1": feature1, "feature2": feature2},
         name="points1",
     )
-    viewer.add_points(
-        random_points,
+    viewer.add_vectors(
+        random_vectors,
         properties={"feature1": feature1, "feature2": feature2},
-        name="points2",
+        name="vectors1",
     )
 
     widget = FeaturesHistogramWidget(viewer)
@@ -70,26 +73,44 @@ def test_feature_histogram(make_napari_viewer):
 
 
 @pytest.mark.mpl_image_compare
-def test_feature_histogram2(make_napari_viewer):
-    import numpy as np
+def test_feature_histogram_vectors(make_napari_viewer):
+
+    n_points = 1000
+    np.random.seed(42)
+    random_points = np.random.random((n_points, 3)) * 10
+    random_directions = np.random.random((n_points, 3)) * 10
+    random_vectors = np.stack([random_points, random_directions], axis=1)
+    feature1 = np.random.random(n_points)
+
+    viewer = make_napari_viewer()
+    viewer.add_vectors(
+        random_vectors,
+        properties={"feature1": feature1},
+        name="vectors1",
+    )
+
+    widget = FeaturesHistogramWidget(viewer)
+    viewer.window.add_dock_widget(widget)
+    widget._set_axis_keys("feature1")
+
+    fig = FeaturesHistogramWidget(viewer).figure
+    return deepcopy(fig)
+
+
+@pytest.mark.mpl_image_compare
+def test_feature_histogram_points(make_napari_viewer):
 
     np.random.seed(0)
     n_points = 1000
     random_points = np.random.random((n_points, 3)) * 10
     feature1 = np.random.random(n_points)
-    feature2 = np.random.normal(size=n_points)
 
     viewer = make_napari_viewer()
     viewer.add_points(
         random_points,
-        properties={"feature1": feature1, "feature2": feature2},
+        properties={"feature1": feature1},
         name="points1",
     )
-    viewer.add_points(
-        random_points,
-        properties={"feature1": feature1, "feature2": feature2},
-        name="points2",
-    )
 
     widget = FeaturesHistogramWidget(viewer)
     viewer.window.add_dock_widget(widget)
@@ -120,3 +141,8 @@ def test_change_layer(make_napari_viewer, brain_data, astronaut_data):
     viewer.layers.selection.clear()
     viewer.layers.selection.add(viewer.layers[1])
     assert_figures_not_equal(widget.figure, fig1)
+
+
+if __name__ == '__main__':
+    import napari
+    test_feature_histogram_points(napari.Viewer)
\ No newline at end of file

From d65da102603ac33a5dee8504f876b94849960b0b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Wed, 4 Oct 2023 16:25:26 +0200
Subject: [PATCH 082/238] updated baselines

---
 .../tests/baseline/test_feature_histogram2.png  | Bin 12860 -> 0 bytes
 .../baseline/test_feature_histogram_points.png  | Bin 0 -> 11479 bytes
 .../baseline/test_feature_histogram_vectors.png | Bin 0 -> 10886 bytes
 3 files changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 src/napari_matplotlib/tests/baseline/test_feature_histogram2.png
 create mode 100644 src/napari_matplotlib/tests/baseline/test_feature_histogram_points.png
 create mode 100644 src/napari_matplotlib/tests/baseline/test_feature_histogram_vectors.png

diff --git a/src/napari_matplotlib/tests/baseline/test_feature_histogram2.png b/src/napari_matplotlib/tests/baseline/test_feature_histogram2.png
deleted file mode 100644
index b7bb19e039d73adfd05f0d3860a5a1c4d0711f72..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_feature_histogram_points.png b/src/napari_matplotlib/tests/baseline/test_feature_histogram_points.png
new file mode 100644
index 0000000000000000000000000000000000000000..49e618be0f47332188ab6762015866aa06b4f109
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_feature_histogram_vectors.png b/src/napari_matplotlib/tests/baseline/test_feature_histogram_vectors.png
new file mode 100644
index 0000000000000000000000000000000000000000..64da7056d865ef3c7c4a3a493c62d5210c8714c6
GIT binary patch

From b6312c36573c8707f0ecec0f097898e0615a740f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Wed, 4 Oct 2023 16:28:49 +0200
Subject: [PATCH 083/238] removed labels from supported layers

---
 src/napari_matplotlib/histogram.py | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 82cf11c5..cf677bea 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -170,9 +170,6 @@ def draw(self) -> None:
         elif isinstance(self.layers[0], napari.layers.Vectors):
             colormap = self.layers[0].edge_colormap
             self.layers[0].edge_color = self.x_axis_key
-        elif isinstance(self.layers[0], napari.layers.Labels):
-            colormap = self.layers[0].colormap
-            self.layers[0].color = self.x_axis_key
         else:
             colormap = None
 

From 073613cb0027d2321bf5ced9724b45a0ba06f4b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Wed, 4 Oct 2023 16:45:09 +0200
Subject: [PATCH 084/238] Delete test.ipynb

---
 src/napari_matplotlib/tests/test.ipynb | 185 -------------------------
 1 file changed, 185 deletions(-)
 delete mode 100644 src/napari_matplotlib/tests/test.ipynb

diff --git a/src/napari_matplotlib/tests/test.ipynb b/src/napari_matplotlib/tests/test.ipynb
deleted file mode 100644
index e65c5740..00000000
--- a/src/napari_matplotlib/tests/test.ipynb
+++ /dev/null
@@ -1,185 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": 1,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "import numpy as np\n",
-    "import napari\n",
-    "from napari_matplotlib import FeaturesHistogramWidget\n",
-    "import matplotlib.pyplot as plt"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 24,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "n_points = 1000\n",
-    "random_points = np.random.random((n_points, 3)) * 10\n",
-    "random_points2 = np.random.random((n_points, 3)) * 10\n",
-    "random_vectors = random_points2 - random_points\n",
-    "\n",
-    "vectors = np.stack([random_points, random_vectors], axis=1)\n",
-    "feature1 = np.random.random(n_points)\n",
-    "feature2 = np.random.normal(size=n_points)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 25,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "viewer = napari.Viewer()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 26,
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "<Vectors layer 'vectors1' at 0x1ec34e98130>"
-      ]
-     },
-     "execution_count": 26,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "viewer.add_points(\n",
-    "    random_points,\n",
-    "    properties={\"feature1\": feature1, \"feature2\": feature2},\n",
-    "    name=\"points1\",\n",
-    "    face_color=\"feature1\",\n",
-    "    size=1,\n",
-    ")\n",
-    "\n",
-    "viewer.add_vectors(\n",
-    "    vectors,\n",
-    "    properties={\"feature1\": feature1, \"feature2\": feature2},\n",
-    "    name=\"vectors1\",\n",
-    "    edge_width=1,\n",
-    "    edge_color=\"feature1\",\n",
-    ")"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 27,
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "<napari._qt.widgets.qt_viewer_dock_widget.QtViewerDockWidget at 0x1ec387349d0>"
-      ]
-     },
-     "execution_count": 27,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "widget = FeaturesHistogramWidget(viewer)\n",
-    "viewer.window.add_dock_widget(widget)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 19,
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "'feature1'"
-      ]
-     },
-     "execution_count": 19,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "widget.x_axis_key"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 36,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "widget.viewer.layers[widget.layers[0].name].refresh_colors(True)\n",
-    "widget.viewer.layers[0].properties\n",
-    "widget.viewer.layers[widget.layers[0].name].refresh_colors(True)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 33,
-   "metadata": {},
-   "outputs": [
-    {
-     "data": {
-      "text/plain": [
-       "True"
-      ]
-     },
-     "execution_count": 33,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "widget.viewer.layers[1]._update_properties"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 40,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "widget.viewer.layers[0].edge_color = \"feature2\""
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": []
-  }
- ],
- "metadata": {
-  "kernelspec": {
-   "display_name": "stress",
-   "language": "python",
-   "name": "python3"
-  },
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython",
-    "version": 3
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython3",
-   "version": "3.9.18"
-  },
-  "orig_nbformat": 4
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}

From 7179889a29027942060d30a85dd016a459bf0e13 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Wed, 4 Oct 2023 14:47:32 +0000
Subject: [PATCH 085/238] [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
---
 src/napari_matplotlib/histogram.py            | 9 +++++----
 src/napari_matplotlib/tests/test_histogram.py | 8 +++-----
 2 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index cf677bea..0a49dc41 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -162,7 +162,6 @@ def on_update_layers(self) -> None:
 
     def draw(self) -> None:
         """Clear the axes and histogram the currently selected layer/slice."""
-
         # get the colormap from the layer depending on its type
         if isinstance(self.layers[0], napari.layers.Points):
             colormap = self.layers[0].face_colormap
@@ -183,16 +182,18 @@ def draw(self) -> None:
         if data is None:
             return
 
-        _, bins, patches = self.axes.hist(data, bins=50, edgecolor="white", linewidth=0.3)
+        _, bins, patches = self.axes.hist(
+            data, bins=50, edgecolor="white", linewidth=0.3
+        )
 
         # recolor the histogram plot
         if colormap is not None:
-            self.bins_norm = (bins - bins.min())/(bins.max() - bins.min())
+            self.bins_norm = (bins - bins.min()) / (bins.max() - bins.min())
             colors = colormap.map(self.bins_norm)
 
             # Set histogram style:
             for idx, patch in enumerate(patches):
-                patch.set_facecolor(colors[idx])        
+                patch.set_facecolor(colors[idx])
 
         # set ax labels
         self.axes.set_xlabel(x_axis_name)
diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py
index 534d7952..e71fea2a 100644
--- a/src/napari_matplotlib/tests/test_histogram.py
+++ b/src/napari_matplotlib/tests/test_histogram.py
@@ -36,7 +36,6 @@ def test_histogram_3D(make_napari_viewer, brain_data):
 
 
 def test_feature_histogram(make_napari_viewer):
-
     n_points = 1000
     random_points = np.random.random((n_points, 3)) * 10
     random_directions = np.random.random((n_points, 3)) * 10
@@ -74,7 +73,6 @@ def test_feature_histogram(make_napari_viewer):
 
 @pytest.mark.mpl_image_compare
 def test_feature_histogram_vectors(make_napari_viewer):
-
     n_points = 1000
     np.random.seed(42)
     random_points = np.random.random((n_points, 3)) * 10
@@ -99,7 +97,6 @@ def test_feature_histogram_vectors(make_napari_viewer):
 
 @pytest.mark.mpl_image_compare
 def test_feature_histogram_points(make_napari_viewer):
-
     np.random.seed(0)
     n_points = 1000
     random_points = np.random.random((n_points, 3)) * 10
@@ -143,6 +140,7 @@ def test_change_layer(make_napari_viewer, brain_data, astronaut_data):
     assert_figures_not_equal(widget.figure, fig1)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     import napari
-    test_feature_histogram_points(napari.Viewer)
\ No newline at end of file
+
+    test_feature_histogram_points(napari.Viewer)

From e7789a071c2e0c8bf935c1023fcc2f8480415acb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Wed, 4 Oct 2023 16:51:46 +0200
Subject: [PATCH 086/238] removed main from test

---
 src/napari_matplotlib/tests/test_histogram.py | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py
index e71fea2a..1ceca519 100644
--- a/src/napari_matplotlib/tests/test_histogram.py
+++ b/src/napari_matplotlib/tests/test_histogram.py
@@ -138,9 +138,3 @@ def test_change_layer(make_napari_viewer, brain_data, astronaut_data):
     viewer.layers.selection.clear()
     viewer.layers.selection.add(viewer.layers[1])
     assert_figures_not_equal(widget.figure, fig1)
-
-
-if __name__ == "__main__":
-    import napari
-
-    test_feature_histogram_points(napari.Viewer)

From 7177fb732b7718e94c103584dde2a48c84a76314 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Sun, 8 Oct 2023 08:34:29 +0100
Subject: [PATCH 087/238] Pin Matplotlib to < 3.8

---
 setup.cfg | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.cfg b/setup.cfg
index b05201cb..70ad93a2 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -27,7 +27,7 @@ project_urls =
 [options]
 packages = find:
 install_requires =
-    matplotlib
+    matplotlib<3.8
     napari
     numpy
     tinycss2

From 07c545b6c117718488a737ca0bcc5361e9f42258 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Sun, 8 Oct 2023 08:37:17 +0100
Subject: [PATCH 088/238] Add pin to pre-commit-config

---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2e7732ce..63bb3a79 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -20,7 +20,7 @@ repos:
     rev: v1.5.1
     hooks:
      - id: mypy
-       additional_dependencies: [numpy, matplotlib]
+       additional_dependencies: [numpy, matplotlib<3.8]
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.

From 5b87710d46db3bec3643afb898c22072cbd095f3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Johannes=20M=C3=BCller?=
 <38459088+jo-mueller@users.noreply.github.com>
Date: Sun, 8 Oct 2023 21:04:59 +0200
Subject: [PATCH 089/238] re-saved generated figures

---
 baseline/test_feature_histogram_points.png  | Bin 0 -> 11524 bytes
 baseline/test_feature_histogram_vectors.png | Bin 0 -> 11153 bytes
 2 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 baseline/test_feature_histogram_points.png
 create mode 100644 baseline/test_feature_histogram_vectors.png

diff --git a/baseline/test_feature_histogram_points.png b/baseline/test_feature_histogram_points.png
new file mode 100644
index 0000000000000000000000000000000000000000..0f71e7624c10a7b4f995186d1b59b08d9a052c3c
GIT binary patch
diff --git a/baseline/test_feature_histogram_vectors.png b/baseline/test_feature_histogram_vectors.png
new file mode 100644
index 0000000000000000000000000000000000000000..8048ac91e4d3dd57056d61f19df6d96529b5f551
GIT binary patch

From 1a1d509651802d1e95526234a0ba46d3bbdfc298 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 9 Oct 2023 17:41:42 +0000
Subject: [PATCH 090/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0)
- [github.com/astral-sh/ruff-pre-commit: v0.0.288 → v0.0.292](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.288...v0.0.292)
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 63bb3a79..0ca47628 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.4.0
+    rev: v4.5.0
     hooks:
       - id: check-docstring-first
       - id: end-of-file-fixer
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.0.288'
+    rev: 'v0.0.292'
     hooks:
       - id: ruff
 

From 96ffad3cf752f6cbe42e874e12dfe38d9523c1ed Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 16 Oct 2023 17:31:55 +0000
Subject: [PATCH 091/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/pre-commit/mirrors-mypy: v1.5.1 → v1.6.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.5.1...v1.6.0)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0ca47628..c1adb2e3 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -17,7 +17,7 @@ repos:
       - id: napari-plugin-checks
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.5.1
+    rev: v1.6.0
     hooks:
      - id: mypy
        additional_dependencies: [numpy, matplotlib<3.8]

From 09f4e91869d1459af58037c1b4c67a1ee1096da7 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Sun, 22 Oct 2023 12:50:13 +0100
Subject: [PATCH 092/238] Remove Python 3.8 support

---
 .github/workflows/test_and_deploy.yml | 2 +-
 .pre-commit-config.yaml               | 2 +-
 pyproject.toml                        | 4 ++--
 setup.cfg                             | 2 +-
 tox.ini                               | 4 ++--
 5 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml
index 6e170d14..8665e1d2 100644
--- a/.github/workflows/test_and_deploy.yml
+++ b/.github/workflows/test_and_deploy.yml
@@ -20,7 +20,7 @@ jobs:
       fail-fast: false
       matrix:
         platform: [ubuntu-latest, macos-latest, windows-latest]
-        python-version: ['3.8', '3.9', '3.10', '3.11']
+        python-version: ['3.9', '3.10', '3.11']
 
     steps:
       - uses: actions/checkout@v3
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c1adb2e3..7bfb2d24 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -20,7 +20,7 @@ repos:
     rev: v1.6.0
     hooks:
      - id: mypy
-       additional_dependencies: [numpy, matplotlib<3.8]
+       additional_dependencies: [numpy, matplotlib]
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
diff --git a/pyproject.toml b/pyproject.toml
index 7c7dbbdd..705b4655 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -23,7 +23,7 @@ profile = "black"
 line_length = 79
 
 [tool.ruff]
-target-version = "py38"
+target-version = "py39"
 select = ["I", "UP", "F", "E", "W", "D"]
 ignore = [
     "D100", # Missing docstring in public module
@@ -45,7 +45,7 @@ fix = true
 convention = "numpy"
 
 [tool.mypy]
-python_version = "3.8"
+python_version = "3.9"
 # Block below are checks that form part of mypy 'strict' mode
 warn_unused_configs = true
 warn_redundant_casts = true
diff --git a/setup.cfg b/setup.cfg
index 70ad93a2..b962611d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -31,7 +31,7 @@ install_requires =
     napari
     numpy
     tinycss2
-python_requires = >=3.8
+python_requires = >=3.9
 include_package_data = True
 package_dir =
     =src
diff --git a/tox.ini b/tox.ini
index 683992f5..0125d792 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,12 +1,12 @@
 [tox]
-envlist = py{38,39,310,311}
+envlist = py{39,310,311}
 isolated_build = true
 
 [gh-actions]
 python =
-    3.8: py38
     3.9: py39
     3.10: py310
+    3.11: py311
 
 [testenv]
 extras = testing

From d768a924145e482664a49bee0000740c0e07cc8f Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Sun, 22 Oct 2023 13:00:40 +0100
Subject: [PATCH 093/238] Typing upgrades

---
 src/napari_matplotlib/base.py                        |  6 +++---
 src/napari_matplotlib/histogram.py                   |  6 +++---
 src/napari_matplotlib/scatter.py                     | 12 ++++++------
 src/napari_matplotlib/slice.py                       |  6 +++---
 .../tests/scatter/test_scatter_features.py           |  4 ++--
 src/napari_matplotlib/tests/test_layer_changes.py    |  6 +++---
 src/napari_matplotlib/util.py                        |  8 ++++----
 7 files changed, 24 insertions(+), 24 deletions(-)

diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py
index 792b5aff..21b4a044 100644
--- a/src/napari_matplotlib/base.py
+++ b/src/napari_matplotlib/base.py
@@ -1,6 +1,6 @@
 import os
 from pathlib import Path
-from typing import List, Optional, Tuple
+from typing import Optional
 
 import matplotlib
 import matplotlib.style as mplstyle
@@ -184,7 +184,7 @@ class NapariMPLWidget(BaseNapariMPLWidget):
     #: Number of layers taken as input
     n_layers_input = Interval(None, None)
     #: Type of layer taken as input
-    input_layer_types: Tuple[napari.layers.Layer, ...] = (napari.layers.Layer,)
+    input_layer_types: tuple[napari.layers.Layer, ...] = (napari.layers.Layer,)
 
     def __init__(
         self,
@@ -193,7 +193,7 @@ def __init__(
     ):
         super().__init__(napari_viewer=napari_viewer, parent=parent)
         self._setup_callbacks()
-        self.layers: List[napari.layers.Layer] = []
+        self.layers: list[napari.layers.Layer] = []
 
         helper_text = self.n_layers_input._helper_text
         if helper_text is not None:
diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 66aa7acc..c853988a 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -1,4 +1,4 @@
-from typing import Any, List, Optional, Tuple
+from typing import Any, Optional
 
 import napari
 import numpy as np
@@ -107,7 +107,7 @@ def _set_axis_keys(self, x_axis_key: str) -> None:
         self._x_axis_key = x_axis_key
         self._draw()
 
-    def _get_valid_axis_keys(self) -> List[str]:
+    def _get_valid_axis_keys(self) -> list[str]:
         """
         Get the valid axis keys from the layer FeatureTable.
 
@@ -122,7 +122,7 @@ def _get_valid_axis_keys(self) -> List[str]:
         else:
             return self.layers[0].features.keys()
 
-    def _get_data(self) -> Tuple[Optional[npt.NDArray[Any]], str]:
+    def _get_data(self) -> tuple[Optional[npt.NDArray[Any]], str]:
         """Get the plot data.
 
         Returns
diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py
index a4148bd2..67d6896c 100644
--- a/src/napari_matplotlib/scatter.py
+++ b/src/napari_matplotlib/scatter.py
@@ -1,4 +1,4 @@
-from typing import Any, Dict, List, Optional, Tuple, Union
+from typing import Any, Optional, Union
 
 import napari
 import numpy.typing as npt
@@ -40,7 +40,7 @@ def draw(self) -> None:
         self.axes.set_xlabel(x_axis_name)
         self.axes.set_ylabel(y_axis_name)
 
-    def _get_data(self) -> Tuple[npt.NDArray[Any], npt.NDArray[Any], str, str]:
+    def _get_data(self) -> tuple[npt.NDArray[Any], npt.NDArray[Any], str, str]:
         """
         Get the plot data.
 
@@ -67,7 +67,7 @@ class ScatterWidget(ScatterBaseWidget):
     n_layers_input = Interval(2, 2)
     input_layer_types = (napari.layers.Image,)
 
-    def _get_data(self) -> Tuple[npt.NDArray[Any], npt.NDArray[Any], str, str]:
+    def _get_data(self) -> tuple[npt.NDArray[Any], npt.NDArray[Any], str, str]:
         """
         Get the plot data.
 
@@ -106,7 +106,7 @@ def __init__(
 
         self.layout().addLayout(QVBoxLayout())
 
-        self._selectors: Dict[str, QComboBox] = {}
+        self._selectors: dict[str, QComboBox] = {}
         for dim in ["x", "y"]:
             self._selectors[dim] = QComboBox()
             # Re-draw when combo boxes are updated
@@ -147,7 +147,7 @@ def y_axis_key(self, key: str) -> None:
         self._selectors["y"].setCurrentText(key)
         self._draw()
 
-    def _get_valid_axis_keys(self) -> List[str]:
+    def _get_valid_axis_keys(self) -> list[str]:
         """
         Get the valid axis keys from the layer FeatureTable.
 
@@ -186,7 +186,7 @@ def draw(self) -> None:
         if self._ready_to_scatter():
             super().draw()
 
-    def _get_data(self) -> Tuple[npt.NDArray[Any], npt.NDArray[Any], str, str]:
+    def _get_data(self) -> tuple[npt.NDArray[Any], npt.NDArray[Any], str, str]:
         """
         Get the plot data from the ``features`` attribute of the first
         selected layer.
diff --git a/src/napari_matplotlib/slice.py b/src/napari_matplotlib/slice.py
index 393f2e45..9459fa97 100644
--- a/src/napari_matplotlib/slice.py
+++ b/src/napari_matplotlib/slice.py
@@ -1,4 +1,4 @@
-from typing import Any, List, Optional, Tuple
+from typing import Any, Optional
 
 import matplotlib.ticker as mticker
 import napari
@@ -99,7 +99,7 @@ def current_dim_index(self) -> int:
         return self._dim_names.index(self.current_dim_name)
 
     @property
-    def _dim_names(self) -> List[str]:
+    def _dim_names(self) -> list[str]:
         """
         List of dimension names. This is a property as it varies depending on the
         dimensionality of the currently selected data.
@@ -111,7 +111,7 @@ def _dim_names(self) -> List[str]:
         else:
             raise RuntimeError("Don't know how to handle ndim != 2 or 3")
 
-    def _get_xy(self) -> Tuple[npt.NDArray[Any], npt.NDArray[Any]]:
+    def _get_xy(self) -> tuple[npt.NDArray[Any], npt.NDArray[Any]]:
         """
         Get data for plotting.
         """
diff --git a/src/napari_matplotlib/tests/scatter/test_scatter_features.py b/src/napari_matplotlib/tests/scatter/test_scatter_features.py
index b5a396fd..3ede1e28 100644
--- a/src/napari_matplotlib/tests/scatter/test_scatter_features.py
+++ b/src/napari_matplotlib/tests/scatter/test_scatter_features.py
@@ -1,5 +1,5 @@
 from copy import deepcopy
-from typing import Any, Dict, Tuple
+from typing import Any
 
 import numpy as np
 import numpy.typing as npt
@@ -34,7 +34,7 @@ def test_features_scatter_widget_2D(
 
 
 def make_labels_layer_with_features() -> (
-    Tuple[npt.NDArray[np.uint16], Dict[str, Any]]
+    tuple[npt.NDArray[np.uint16], dict[str, Any]]
 ):
     label_image: npt.NDArray[np.uint16] = np.zeros((100, 100), dtype=np.uint16)
     for label_value, start_index in enumerate([10, 30, 50], start=1):
diff --git a/src/napari_matplotlib/tests/test_layer_changes.py b/src/napari_matplotlib/tests/test_layer_changes.py
index bdd6c600..15958c07 100644
--- a/src/napari_matplotlib/tests/test_layer_changes.py
+++ b/src/napari_matplotlib/tests/test_layer_changes.py
@@ -1,5 +1,5 @@
 from copy import deepcopy
-from typing import Any, Dict, Tuple, Type
+from typing import Any
 
 import numpy as np
 import numpy.typing as npt
@@ -61,8 +61,8 @@ def test_change_features_layer(
 
 def assert_features_plot_changes(
     viewer: Viewer,
-    widget_cls: Type[NapariMPLWidget],
-    data: Tuple[npt.NDArray[np.generic], Dict[str, Any]],
+    widget_cls: type[NapariMPLWidget],
+    data: tuple[npt.NDArray[np.generic], dict[str, Any]],
 ) -> None:
     """
     When the selected layer is changed, make sure the plot generated
diff --git a/src/napari_matplotlib/util.py b/src/napari_matplotlib/util.py
index 2aa15ddd..7d72c9e2 100644
--- a/src/napari_matplotlib/util.py
+++ b/src/napari_matplotlib/util.py
@@ -1,4 +1,4 @@
-from typing import List, Optional, Tuple, Union
+from typing import Optional, Union
 from warnings import warn
 
 import napari.qt
@@ -76,7 +76,7 @@ def _helper_text(self) -> Optional[str]:
         return helper_text
 
 
-def _has_id(nodes: List[tinycss2.ast.Node], id_name: str) -> bool:
+def _has_id(nodes: list[tinycss2.ast.Node], id_name: str) -> bool:
     """
     Is `id_name` in IdentTokens in the list of CSS `nodes`?
     """
@@ -86,7 +86,7 @@ def _has_id(nodes: List[tinycss2.ast.Node], id_name: str) -> bool:
 
 
 def _get_dimension(
-    nodes: List[tinycss2.ast.Node], id_name: str
+    nodes: list[tinycss2.ast.Node], id_name: str
 ) -> Union[int, None]:
     """
     Get the value of the DimensionToken for the IdentToken `id_name`.
@@ -108,7 +108,7 @@ def _get_dimension(
 
 
 def from_napari_css_get_size_of(
-    qt_element_name: str, fallback: Tuple[int, int]
+    qt_element_name: str, fallback: tuple[int, int]
 ) -> QSize:
     """
     Get the size of `qt_element_name` from napari's current stylesheet.

From 272ae95317d94fbaac6b28cdd439cefc362b81b1 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Sun, 22 Oct 2023 12:37:57 +0100
Subject: [PATCH 094/238] Remove Matplotlib pin

---
 setup.cfg | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.cfg b/setup.cfg
index b962611d..6489abde 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -27,7 +27,7 @@ project_urls =
 [options]
 packages = find:
 install_requires =
-    matplotlib<3.8
+    matplotlib
     napari
     numpy
     tinycss2

From d8700f65c8d8f56de091588cbeb9cd99ceb92754 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Sun, 22 Oct 2023 12:59:24 +0100
Subject: [PATCH 095/238] Fix adding subplot

---
 src/napari_matplotlib/base.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py
index 21b4a044..d8f0849f 100644
--- a/src/napari_matplotlib/base.py
+++ b/src/napari_matplotlib/base.py
@@ -97,7 +97,7 @@ def add_single_axes(self) -> None:
         # Sets axes.* style.
         # Does not set any text styling set by axes.* keys
         with mplstyle.context(self.mpl_style_sheet_path):
-            self.axes = self.figure.subplots()
+            self.axes = self.figure.add_subplot()
 
     def _on_napari_theme_changed(self) -> None:
         """

From a1c217a3a0a3d4c7af6439c11079a2f04254bd5c Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Sun, 22 Oct 2023 13:50:12 +0100
Subject: [PATCH 096/238] Fixup tests

---
 .../tests/baseline/test_custom_theme.png      | Bin 15625 -> 15253 bytes
 .../baseline/test_feature_histogram2.png      | Bin 12860 -> 15329 bytes
 .../tests/baseline/test_histogram_2D.png      | Bin 28728 -> 28371 bytes
 .../tests/baseline/test_histogram_3D.png      | Bin 20558 -> 20123 bytes
 .../tests/baseline/test_slice_2D.png          | Bin 33587 -> 33181 bytes
 .../tests/baseline/test_slice_3D.png          | Bin 14356 -> 14108 bytes
 .../test_features_scatter_widget_2D.png       | Bin 24951 -> 24841 bytes
 .../scatter/baseline/test_scatter_2D.png      | Bin 18548 -> 18236 bytes
 .../scatter/baseline/test_scatter_3D.png      | Bin 17321 -> 16831 bytes
 src/napari_matplotlib/tests/test_theme.py     |   2 +-
 10 files changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/tests/baseline/test_custom_theme.png b/src/napari_matplotlib/tests/baseline/test_custom_theme.png
index c385852288bb57b741b8d6373cdc4ae1a43f8f3f..a668c103a7521c6791118def2595d02f312e328e 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_feature_histogram2.png b/src/napari_matplotlib/tests/baseline/test_feature_histogram2.png
index b7bb19e039d73adfd05f0d3860a5a1c4d0711f72..c53a890c73c2eaefc3367ba93c45239551611439 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_histogram_2D.png b/src/napari_matplotlib/tests/baseline/test_histogram_2D.png
index b76d1e10a46f515a28a32ac4ec0b4ae024f801cb..b043bba855054f3c6d58bc0469e3fae66298faef 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_histogram_3D.png b/src/napari_matplotlib/tests/baseline/test_histogram_3D.png
index 2dffdcb2647fd6078a13a2afcbb2d88e6987e95d..724314e122c559d2b14efc8e208c1abd1092302e 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_slice_2D.png b/src/napari_matplotlib/tests/baseline/test_slice_2D.png
index ee3ce3b69a960ddfb07c347ef92c8c5f351d2472..d39920be6e7a22aa819f43e987f147c6e5fcd353 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_slice_3D.png b/src/napari_matplotlib/tests/baseline/test_slice_3D.png
index c30211dab12d3c276a1427990fc86c01b2dff46a..cf563de57381af79660e08d20d69b475d7576172 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/scatter/baseline/test_features_scatter_widget_2D.png b/src/napari_matplotlib/tests/scatter/baseline/test_features_scatter_widget_2D.png
index 269ebd014ae88296fc166ceb95e15ad14b5ea30d..75965607ecf3857805eddaf7b5ab50136063ffc1 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/scatter/baseline/test_scatter_2D.png b/src/napari_matplotlib/tests/scatter/baseline/test_scatter_2D.png
index f96b5354f90467d86b7a531bccbcefaba47f7ecb..102191066069f97781e10c54be841777207a0f26 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/scatter/baseline/test_scatter_3D.png b/src/napari_matplotlib/tests/scatter/baseline/test_scatter_3D.png
index 27e7d6735c8be2f2b0ece1e7415667bd9045f045..3e648eec5deaaf7fb43b699ffaf184ef376d4979 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/test_theme.py b/src/napari_matplotlib/tests/test_theme.py
index 2a340c89..1042d3f3 100644
--- a/src/napari_matplotlib/tests/test_theme.py
+++ b/src/napari_matplotlib/tests/test_theme.py
@@ -187,6 +187,6 @@ def test_custom_stylesheet(make_napari_viewer, image_data):
         assert widget.figure.patch.get_facecolor() == to_rgba("#fdf6e3")
         for gridline in ax.get_xgridlines() + ax.get_ygridlines():
             assert gridline.get_visible() is True
-            assert gridline.get_color() == "#fdf6e3"
+            assert gridline.get_color() == "#b0b0b0"
     finally:
         os.remove(style_sheet_path)

From f253247e89ba1cf64fc1a92fe2f8d9c37c7d9cd1 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Sun, 22 Oct 2023 13:54:56 +0100
Subject: [PATCH 097/238] Make mypy happy

---
 src/napari_matplotlib/base.py | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py
index d8f0849f..0ff5e389 100644
--- a/src/napari_matplotlib/base.py
+++ b/src/napari_matplotlib/base.py
@@ -5,8 +5,8 @@
 import matplotlib
 import matplotlib.style as mplstyle
 import napari
-from matplotlib.backends.backend_qtagg import (
-    FigureCanvas,
+from matplotlib.backends.backend_qtagg import (  # type: ignore[attr-defined]
+    FigureCanvasQTAgg,
     NavigationToolbar2QT,
 )
 from matplotlib.figure import Figure
@@ -49,12 +49,10 @@ def __init__(
 
         # Sets figure.* style
         with mplstyle.context(self.mpl_style_sheet_path):
-            self.canvas = FigureCanvas()
+            self.canvas = FigureCanvasQTAgg()  # type: ignore[no-untyped-call]
 
         self.canvas.figure.set_layout_engine("constrained")
-        self.toolbar = NapariNavigationToolbar(
-            self.canvas, parent=self
-        )  # type: ignore[no-untyped-call]
+        self.toolbar = NapariNavigationToolbar(self.canvas, parent=self)
         self._replace_toolbar_icons()
         # callback to update when napari theme changed
         # TODO: this isn't working completely (see issue #140)
@@ -260,7 +258,7 @@ def _draw(self) -> None:
             isinstance(layer, self.input_layer_types) for layer in self.layers
         ):
             self.draw()
-        self.canvas.draw()
+        self.canvas.draw()  # type: ignore[no-untyped-call]
 
     def clear(self) -> None:
         """
@@ -309,8 +307,8 @@ def clear(self) -> None:
 class NapariNavigationToolbar(NavigationToolbar2QT):
     """Custom Toolbar style for Napari."""
 
-    def __init__(self, *args, **kwargs):  # type: ignore[no-untyped-def]
-        super().__init__(*args, **kwargs)
+    def __init__(self, *args, **kwargs) -> None:  # type: ignore[no-untyped-def]
+        super().__init__(*args, **kwargs)  # type: ignore[no-untyped-call]
         self.setIconSize(
             from_napari_css_get_size_of(
                 "QtViewerPushButton", fallback=(28, 28)
@@ -319,7 +317,7 @@ def __init__(self, *args, **kwargs):  # type: ignore[no-untyped-def]
 
     def _update_buttons_checked(self) -> None:
         """Update toggle tool icons when selected/unselected."""
-        super()._update_buttons_checked()
+        super()._update_buttons_checked()  # type: ignore[no-untyped-call]
         icon_dir = self.parentWidget()._get_path_to_icon()
 
         # changes pan/zoom icons depending on state (checked or not)

From f71a9ad9ced776edf9216eb42697adb8c1c8dfc7 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Mon, 23 Oct 2023 11:06:43 +0100
Subject: [PATCH 098/238] Clean tox file

---
 tox.ini | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/tox.ini b/tox.ini
index 0125d792..4ec0c702 100644
--- a/tox.ini
+++ b/tox.ini
@@ -10,10 +10,5 @@ python =
 
 [testenv]
 extras = testing
-allowlist_externals =
-    cp
-    ls
 commands =
-    cp -R {toxinidir}/src/napari_matplotlib/tests/baseline {envdir}/baseline
-    ls {toxinidir}/src/napari_matplotlib/tests/baseline
     python -m pytest --mpl --mpl-generate-summary=html --mpl-results-path={toxinidir}/reports -v --color=yes --cov=napari_matplotlib --cov-report=xml

From 851281198d3e53c14e1424ba0d8d3fdaa44ef2db Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Mon, 23 Oct 2023 11:16:30 +0100
Subject: [PATCH 099/238] pin pydantic in doc build

---
 setup.cfg | 1 +
 1 file changed, 1 insertion(+)

diff --git a/setup.cfg b/setup.cfg
index 6489abde..f308412c 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -49,6 +49,7 @@ napari.manifest =
 docs =
     napari[all]==0.4.17
     numpydoc
+    pydantic<2
     pydata-sphinx-theme
     qtgallery
     sphinx

From da5e304b2b493908b373b6e8e7a0b981b72c8e73 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Mon, 23 Oct 2023 11:24:33 +0100
Subject: [PATCH 100/238] Remove old test figure

---
 baseline/test_feature_histogram2.png | Bin 12860 -> 0 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 baseline/test_feature_histogram2.png

diff --git a/baseline/test_feature_histogram2.png b/baseline/test_feature_histogram2.png
deleted file mode 100644
index b7bb19e039d73adfd05f0d3860a5a1c4d0711f72..0000000000000000000000000000000000000000
GIT binary patch

From 2ba76717a2cf5a0e2e370a8996db60f66425fe52 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 23 Oct 2023 17:39:23 +0000
Subject: [PATCH 101/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/psf/black: 23.9.1 → 23.10.0](https://github.com/psf/black/compare/23.9.1...23.10.0)
- [github.com/pre-commit/mirrors-mypy: v1.6.0 → v1.6.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.6.0...v1.6.1)
- [github.com/astral-sh/ruff-pre-commit: v0.0.292 → v0.1.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.292...v0.1.1)
---
 .pre-commit-config.yaml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 7bfb2d24..685d9bc6 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -7,7 +7,7 @@ repos:
       - id: trailing-whitespace
 
   - repo: https://github.com/psf/black
-    rev: 23.9.1
+    rev: 23.10.0
     hooks:
       - id: black
 
@@ -17,14 +17,14 @@ repos:
       - id: napari-plugin-checks
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.6.0
+    rev: v1.6.1
     hooks:
      - id: mypy
        additional_dependencies: [numpy, matplotlib]
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.0.292'
+    rev: 'v0.1.1'
     hooks:
       - id: ruff
 

From ec6c7d5b7568fdaf86f516f16a9c76c29f10485c Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 30 Oct 2023 17:34:27 +0000
Subject: [PATCH 102/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/psf/black: 23.10.0 → 23.10.1](https://github.com/psf/black/compare/23.10.0...23.10.1)
- [github.com/astral-sh/ruff-pre-commit: v0.1.1 → v0.1.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.1...v0.1.3)
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 685d9bc6..8483e70c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -7,7 +7,7 @@ repos:
       - id: trailing-whitespace
 
   - repo: https://github.com/psf/black
-    rev: 23.10.0
+    rev: 23.10.1
     hooks:
       - id: black
 
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.1.1'
+    rev: 'v0.1.3'
     hooks:
       - id: ruff
 

From 6d37c7826d137d9907f3c3f8487342786a0a256b Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Thu, 2 Nov 2023 15:15:51 +0000
Subject: [PATCH 103/238] Update test figures

---
 baseline/test_feature_histogram2.png          | Bin 12860 -> 0 bytes
 baseline/test_feature_histogram_points.png    | Bin 11524 -> 0 bytes
 baseline/test_feature_histogram_vectors.png   | Bin 11153 -> 0 bytes
 .../test_feature_histogram_points.png         | Bin 11479 -> 16815 bytes
 .../test_feature_histogram_vectors.png        | Bin 10886 -> 16178 bytes
 5 files changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 baseline/test_feature_histogram2.png
 delete mode 100644 baseline/test_feature_histogram_points.png
 delete mode 100644 baseline/test_feature_histogram_vectors.png

diff --git a/baseline/test_feature_histogram2.png b/baseline/test_feature_histogram2.png
deleted file mode 100644
index b7bb19e039d73adfd05f0d3860a5a1c4d0711f72..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/baseline/test_feature_histogram_points.png b/baseline/test_feature_histogram_points.png
deleted file mode 100644
index 0f71e7624c10a7b4f995186d1b59b08d9a052c3c..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/baseline/test_feature_histogram_vectors.png b/baseline/test_feature_histogram_vectors.png
deleted file mode 100644
index 8048ac91e4d3dd57056d61f19df6d96529b5f551..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_feature_histogram_points.png b/src/napari_matplotlib/tests/baseline/test_feature_histogram_points.png
index 49e618be0f47332188ab6762015866aa06b4f109..b98a0170991a1da25b9ebf04ec259372e2aaf68d 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_feature_histogram_vectors.png b/src/napari_matplotlib/tests/baseline/test_feature_histogram_vectors.png
index 64da7056d865ef3c7c4a3a493c62d5210c8714c6..3b90586ef7def63f182d50e92feae1f659d9d97a 100644
GIT binary patch

From acfb10eadd2733b2ea3654265adb20fe1d04fece Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Thu, 2 Nov 2023 15:47:03 +0000
Subject: [PATCH 104/238] Fix type checking

---
 src/napari_matplotlib/histogram.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 09f7c3dd..f78a8503 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -1,8 +1,9 @@
-from typing import Any, Optional
+from typing import Any, Optional, cast
 
 import napari
 import numpy as np
 import numpy.typing as npt
+from matplotlib.container import BarContainer
 from qtpy.QtWidgets import QComboBox, QLabel, QVBoxLayout, QWidget
 
 from .base import SingleAxesWidget
@@ -185,6 +186,7 @@ def draw(self) -> None:
         _, bins, patches = self.axes.hist(
             data, bins=50, edgecolor="white", linewidth=0.3
         )
+        patches = cast(BarContainer, patches)
 
         # recolor the histogram plot
         if colormap is not None:

From ac64cdee5d37e306feb38f0d76ec19ae573c48e3 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Thu, 2 Nov 2023 16:00:55 +0000
Subject: [PATCH 105/238] Add changelog for version 1.2.0

---
 docs/changelog.rst | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 6f77e0c3..45952311 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,5 +1,13 @@
 Changelog
 =========
+1.2.0
+-----
+Changes
+~~~~~~~
+- Dropped support for Python 3.8, and added support for Python 3.11.
+- Histogram plots of points and vector layers are now coloured with their napari colourmap.
+- Added support for Matplotlib 3.8
+
 1.1.0
 -----
 Additions

From d17753c8aa7f30f7b97b7d7b7e6851b9e83d226d Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 6 Nov 2023 17:32:14 +0000
Subject: [PATCH 106/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.1.3 → v0.1.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.3...v0.1.4)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8483e70c..3e5b719e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.1.3'
+    rev: 'v0.1.4'
     hooks:
       - id: ruff
 

From 8dd32cb5a6682e494098c4b4276cf58cb3ff7918 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 13 Nov 2023 17:40:53 +0000
Subject: [PATCH 107/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/psf/black: 23.10.1 → 23.11.0](https://github.com/psf/black/compare/23.10.1...23.11.0)
- [github.com/pre-commit/mirrors-mypy: v1.6.1 → v1.7.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.6.1...v1.7.0)
- [github.com/astral-sh/ruff-pre-commit: v0.1.4 → v0.1.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.4...v0.1.5)
---
 .pre-commit-config.yaml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3e5b719e..7b568619 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -7,7 +7,7 @@ repos:
       - id: trailing-whitespace
 
   - repo: https://github.com/psf/black
-    rev: 23.10.1
+    rev: 23.11.0
     hooks:
       - id: black
 
@@ -17,14 +17,14 @@ repos:
       - id: napari-plugin-checks
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.6.1
+    rev: v1.7.0
     hooks:
      - id: mypy
        additional_dependencies: [numpy, matplotlib]
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.1.4'
+    rev: 'v0.1.5'
     hooks:
       - id: ruff
 

From 1ae17a55d3ef52efc38a71a3bcfc83bcbe5c07e7 Mon Sep 17 00:00:00 2001
From: "Patrick J. Roddy" <patrickjamesroddy@gmail.com>
Date: Fri, 17 Nov 2023 16:18:04 +0000
Subject: [PATCH 108/238] Enable `strict` and subtract options

If you weren't aware this achieves the same effect, and a bit easier to keep up-to-date https://mypy.readthedocs.io/en/stable/existing_code.html#introduce-stricter-options
---
 pyproject.toml | 13 +------------
 1 file changed, 1 insertion(+), 12 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 705b4655..dea0fd6a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -47,19 +47,8 @@ convention = "numpy"
 [tool.mypy]
 python_version = "3.9"
 # Block below are checks that form part of mypy 'strict' mode
-warn_unused_configs = true
-warn_redundant_casts = true
-warn_unused_ignores = true
-strict_equality = true
-strict_concatenate = true
-check_untyped_defs = true
+strict = true
 disallow_subclassing_any = false # TODO: fix
-disallow_untyped_decorators = true
-disallow_any_generics = true
-disallow_untyped_calls = true
-disallow_incomplete_defs = true
-disallow_untyped_defs = true
-no_implicit_reexport = true
 warn_return_any = false # TODO: fix
 ignore_missing_imports = true
 

From e6063afb16948e92caa0f4d5a9c75c5ac5d97c11 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 20 Nov 2023 17:31:11 +0000
Subject: [PATCH 109/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.1.5 → v0.1.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.5...v0.1.6)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 7b568619..01f884c7 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.1.5'
+    rev: 'v0.1.6'
     hooks:
       - id: ruff
 

From 568cbcb715088de641b2b988094ac2ca1bfa53fe Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 27 Nov 2023 17:30:26 +0000
Subject: [PATCH 110/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/pre-commit/mirrors-mypy: v1.7.0 → v1.7.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.7.0...v1.7.1)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 01f884c7..874c89fc 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -17,7 +17,7 @@ repos:
       - id: napari-plugin-checks
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.7.0
+    rev: v1.7.1
     hooks:
      - id: mypy
        additional_dependencies: [numpy, matplotlib]

From af9e17bcd71357bd58e9b4e261ef8412a1e4e426 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Christopher=20Nauroth-Kre=C3=9F?= <chnauroth@web.de>
Date: Thu, 30 Nov 2023 10:21:22 +0100
Subject: [PATCH 111/238] set mpl style parameters from napari theme

---
 src/napari_matplotlib/base.py | 49 +++++++++++++++++++----------------
 src/napari_matplotlib/util.py | 29 ++++++++++++++++++++-
 2 files changed, 54 insertions(+), 24 deletions(-)

diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py
index 0ff5e389..262d96a1 100644
--- a/src/napari_matplotlib/base.py
+++ b/src/napari_matplotlib/base.py
@@ -5,6 +5,7 @@
 import matplotlib
 import matplotlib.style as mplstyle
 import napari
+from napari.utils.theme import get_theme
 from matplotlib.backends.backend_qtagg import (  # type: ignore[attr-defined]
     FigureCanvasQTAgg,
     NavigationToolbar2QT,
@@ -13,7 +14,7 @@
 from qtpy.QtGui import QIcon
 from qtpy.QtWidgets import QLabel, QVBoxLayout, QWidget
 
-from .util import Interval, from_napari_css_get_size_of
+from .util import Interval, from_napari_css_get_size_of, style_sheet_from_theme
 
 __all__ = ["BaseNapariMPLWidget", "NapariMPLWidget", "SingleAxesWidget"]
 
@@ -45,10 +46,11 @@ def __init__(
     ):
         super().__init__(parent=parent)
         self.viewer = napari_viewer
-        self._mpl_style_sheet_path: Optional[Path] = None
+        #self._mpl_style_sheet_path: Optional[Path] = None
+        self.napari_theme_style_sheet = style_sheet_from_theme(get_theme(napari_viewer.theme, as_dict=False))
 
         # Sets figure.* style
-        with mplstyle.context(self.mpl_style_sheet_path):
+        with mplstyle.context(self.napari_theme_style_sheet):
             self.canvas = FigureCanvasQTAgg()  # type: ignore[no-untyped-call]
 
         self.canvas.figure.set_layout_engine("constrained")
@@ -68,23 +70,23 @@ def figure(self) -> Figure:
         """Matplotlib figure."""
         return self.canvas.figure
 
-    @property
-    def mpl_style_sheet_path(self) -> Path:
-        """
-        Path to the set Matplotlib style sheet.
-        """
-        if self._mpl_style_sheet_path is not None:
-            return self._mpl_style_sheet_path
-        elif (_CUSTOM_STYLE_PATH).exists():
-            return _CUSTOM_STYLE_PATH
-        elif self._napari_theme_has_light_bg():
-            return Path(__file__).parent / "styles" / "light.mplstyle"
-        else:
-            return Path(__file__).parent / "styles" / "dark.mplstyle"
-
-    @mpl_style_sheet_path.setter
-    def mpl_style_sheet_path(self, path: Path) -> None:
-        self._mpl_style_sheet_path = Path(path)
+    #@property
+    #def mpl_style_sheet_path(self) -> Path:
+    #    """
+    #    Path to the set Matplotlib style sheet.
+    #    """
+    #    if self._mpl_style_sheet_path is not None:
+    #        return self._mpl_style_sheet_path
+    #    elif (_CUSTOM_STYLE_PATH).exists():
+    #        return _CUSTOM_STYLE_PATH
+    #    elif self._napari_theme_has_light_bg():
+    #        return Path(__file__).parent / "styles" / "light.mplstyle"
+    #    else:
+    #        return Path(__file__).parent / "styles" / "dark.mplstyle"
+
+    #@mpl_style_sheet_path.setter
+    #def mpl_style_sheet_path(self, path: Path) -> None:
+    #    self._mpl_style_sheet_path = Path(path)
 
     def add_single_axes(self) -> None:
         """
@@ -94,13 +96,14 @@ def add_single_axes(self) -> None:
         """
         # Sets axes.* style.
         # Does not set any text styling set by axes.* keys
-        with mplstyle.context(self.mpl_style_sheet_path):
+        with mplstyle.context(self.napari_theme_style_sheet):
             self.axes = self.figure.add_subplot()
 
     def _on_napari_theme_changed(self) -> None:
         """
         Called when the napari theme is changed.
         """
+        self.napari_theme_style_sheet = style_sheet_from_theme(get_theme(self.napari_viewer.theme, as_dict=False))
         self._replace_toolbar_icons()
 
     def _napari_theme_has_light_bg(self) -> bool:
@@ -252,7 +255,7 @@ def _draw(self) -> None:
         """
         # Clearing axes sets new defaults, so need to make sure style is applied when
         # this happens
-        with mplstyle.context(self.mpl_style_sheet_path):
+        with mplstyle.context(self.napari_theme_style_sheet):
             self.clear()
         if self.n_selected_layers in self.n_layers_input and all(
             isinstance(layer, self.input_layer_types) for layer in self.layers
@@ -300,7 +303,7 @@ def clear(self) -> None:
         """
         Clear the axes.
         """
-        with mplstyle.context(self.mpl_style_sheet_path):
+        with mplstyle.context(self.napari_theme_style_sheet):
             self.axes.clear()
 
 
diff --git a/src/napari_matplotlib/util.py b/src/napari_matplotlib/util.py
index 7d72c9e2..9ebace01 100644
--- a/src/napari_matplotlib/util.py
+++ b/src/napari_matplotlib/util.py
@@ -1,8 +1,9 @@
-from typing import Optional, Union
+from typing import Dict, Optional, Union
 from warnings import warn
 
 import napari.qt
 import tinycss2
+from napari.utils.theme import Theme
 from qtpy.QtCore import QSize
 
 
@@ -138,3 +139,29 @@ def from_napari_css_get_size_of(
         RuntimeWarning,
     )
     return QSize(*fallback)
+
+
+def style_sheet_from_theme(theme: Theme) -> Dict[str, str]:
+    return {
+        'axes.edgecolor':theme.secondary.as_hex(),
+        # alternatively "axes.facecolor" could be background color or not be set at all, 
+        # but this causes problems when saving figure as image
+        'axes.facecolor':theme.canvas.as_hex(),
+        'axes.labelcolor':theme.text.as_hex(),
+        'boxplot.boxprops.color':theme.text.as_hex(),
+        'boxplot.capprops.color':theme.text.as_hex(),
+        'boxplot.flierprops.markeredgecolor':theme.text.as_hex(),
+        'boxplot.whiskerprops.color':theme.text.as_hex(),
+        'figure.edgecolor':theme.secondary.as_hex(),
+        # alternatively "figure.facecolor" could not be set, but this causes problems 
+        # when saving figure as image
+        'figure.facecolor':theme.background.as_hex(),
+        'grid.color':theme.foreground.as_hex(),
+        'legend.edgecolor':theme.secondary.as_hex(),
+        'legend.facecolor':theme.background.as_hex(),
+        'text.color':theme.text.as_hex(),
+        'xtick.color':theme.secondary.as_hex(),
+        'xtick.labelcolor':theme.text.as_hex(),
+        'ytick.color':theme.secondary.as_hex(),
+        'ytick.labelcolor':theme.text.as_hex(),
+    }
\ No newline at end of file

From 533597da6708a3c6739bdd0149fbf3f19197ec21 Mon Sep 17 00:00:00 2001
From: Chris-N-K <chnauroth@web.de>
Date: Thu, 30 Nov 2023 12:08:25 +0100
Subject: [PATCH 112/238] add dynamic viewer theme matching

---
 src/napari_matplotlib/base.py | 57 ++++++++++++++---------------------
 src/napari_matplotlib/util.py | 36 +++++++++++++++++-----
 2 files changed, 51 insertions(+), 42 deletions(-)

diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py
index 262d96a1..2833249f 100644
--- a/src/napari_matplotlib/base.py
+++ b/src/napari_matplotlib/base.py
@@ -5,6 +5,7 @@
 import matplotlib
 import matplotlib.style as mplstyle
 import napari
+from napari.utils.events import Event
 from napari.utils.theme import get_theme
 from matplotlib.backends.backend_qtagg import (  # type: ignore[attr-defined]
     FigureCanvasQTAgg,
@@ -56,9 +57,6 @@ def __init__(
         self.canvas.figure.set_layout_engine("constrained")
         self.toolbar = NapariNavigationToolbar(self.canvas, parent=self)
         self._replace_toolbar_icons()
-        # callback to update when napari theme changed
-        # TODO: this isn't working completely (see issue #140)
-        # most of our styling respects the theme change but not all
         self.viewer.events.theme.connect(self._on_napari_theme_changed)
 
         self.setLayout(QVBoxLayout())
@@ -70,24 +68,6 @@ def figure(self) -> Figure:
         """Matplotlib figure."""
         return self.canvas.figure
 
-    #@property
-    #def mpl_style_sheet_path(self) -> Path:
-    #    """
-    #    Path to the set Matplotlib style sheet.
-    #    """
-    #    if self._mpl_style_sheet_path is not None:
-    #        return self._mpl_style_sheet_path
-    #    elif (_CUSTOM_STYLE_PATH).exists():
-    #        return _CUSTOM_STYLE_PATH
-    #    elif self._napari_theme_has_light_bg():
-    #        return Path(__file__).parent / "styles" / "light.mplstyle"
-    #    else:
-    #        return Path(__file__).parent / "styles" / "dark.mplstyle"
-
-    #@mpl_style_sheet_path.setter
-    #def mpl_style_sheet_path(self, path: Path) -> None:
-    #    self._mpl_style_sheet_path = Path(path)
-
     def add_single_axes(self) -> None:
         """
         Add a single Axes to the figure.
@@ -99,11 +79,16 @@ def add_single_axes(self) -> None:
         with mplstyle.context(self.napari_theme_style_sheet):
             self.axes = self.figure.add_subplot()
 
-    def _on_napari_theme_changed(self) -> None:
+    def _on_napari_theme_changed(self, event: Event) -> None:
         """
         Called when the napari theme is changed.
+
+        Parameters
+        ----------
+        event : napari.utils.events.Event
+            Event that triggered the callback.
         """
-        self.napari_theme_style_sheet = style_sheet_from_theme(get_theme(self.napari_viewer.theme, as_dict=False))
+        self.napari_theme_style_sheet = style_sheet_from_theme(get_theme(event.value, as_dict=False))
         self._replace_toolbar_icons()
 
     def _napari_theme_has_light_bg(self) -> bool:
@@ -214,15 +199,18 @@ def current_z(self) -> int:
         """
         return self.viewer.dims.current_step[0]
 
-    def _on_napari_theme_changed(self) -> None:
+    def _on_napari_theme_changed(self, event: Event) -> None:
         """Update MPL toolbar and axis styling when `napari.Viewer.theme` is changed.
 
-        Note:
-            At the moment we only handle the default 'light' and 'dark' napari themes.
+        Parameters
+        ----------
+        event : napari.utils.events.Event
+            Event that triggered the callback.
         """
-        super()._on_napari_theme_changed()
-        self.clear()
-        self.draw()
+        super()._on_napari_theme_changed(event)
+        # use self._draw instead of self.draw to cope with redraw while there are no 
+        # layers, this makes the self.clear() obsolete
+        self._draw()
 
     def _setup_callbacks(self) -> None:
         """
@@ -256,12 +244,13 @@ def _draw(self) -> None:
         # Clearing axes sets new defaults, so need to make sure style is applied when
         # this happens
         with mplstyle.context(self.napari_theme_style_sheet):
+            # everything should be done in the style context
             self.clear()
-        if self.n_selected_layers in self.n_layers_input and all(
-            isinstance(layer, self.input_layer_types) for layer in self.layers
-        ):
-            self.draw()
-        self.canvas.draw()  # type: ignore[no-untyped-call]
+            if self.n_selected_layers in self.n_layers_input and all(
+                isinstance(layer, self.input_layer_types) for layer in self.layers
+            ):
+                self.draw()
+            self.canvas.draw()  # type: ignore[no-untyped-call]
 
     def clear(self) -> None:
         """
diff --git a/src/napari_matplotlib/util.py b/src/napari_matplotlib/util.py
index 9ebace01..b334dd10 100644
--- a/src/napari_matplotlib/util.py
+++ b/src/napari_matplotlib/util.py
@@ -142,23 +142,43 @@ def from_napari_css_get_size_of(
 
 
 def style_sheet_from_theme(theme: Theme) -> Dict[str, str]:
+    """Translate napari theme to a matplotlib style dictionary.
+
+    Parameters
+    ----------
+    theme : napari.utils.theme.Theme
+        Napari theme object representing the theme of the current viewer.
+    
+    Returns
+    -------
+    Dict[str, str]
+        Matplotlib compatible style dictionary.
+    """
     return {
         'axes.edgecolor':theme.secondary.as_hex(),
-        # alternatively "axes.facecolor" could be background color or not be set at all, 
-        # but this causes problems when saving figure as image
-        'axes.facecolor':theme.canvas.as_hex(),
+        #BUG: could be the same as napari canvas, but facecolors do not get 
+        #     updated upon redraw for what ever reason
+        #'axes.facecolor':theme.canvas.as_hex(),
+        'axes.facecolor':'none',
         'axes.labelcolor':theme.text.as_hex(),
         'boxplot.boxprops.color':theme.text.as_hex(),
         'boxplot.capprops.color':theme.text.as_hex(),
         'boxplot.flierprops.markeredgecolor':theme.text.as_hex(),
         'boxplot.whiskerprops.color':theme.text.as_hex(),
         'figure.edgecolor':theme.secondary.as_hex(),
-        # alternatively "figure.facecolor" could not be set, but this causes problems 
-        # when saving figure as image
-        'figure.facecolor':theme.background.as_hex(),
+        #BUG: should be the same as napari background, but facecolors do not get 
+        #     updated upon redraw for what ever reason
+        #'figure.facecolor':theme.background.as_hex(),
+        'figure.facecolor':'none',
         'grid.color':theme.foreground.as_hex(),
-        'legend.edgecolor':theme.secondary.as_hex(),
-        'legend.facecolor':theme.background.as_hex(),
+        #COMMENT: the hard coded colors are to match the previous behaviour 
+        #         alternativly we could use the theme to style the legend as well
+        #'legend.edgecolor':theme.secondary.as_hex(),
+        'legend.edgecolor':'black',
+        #'legend.facecolor':theme.background.as_hex(),
+        'legend.facecolor':'white',
+        #'legend.labelcolor':theme.text.as_hex()
+        'legend.labelcolor':'black',
         'text.color':theme.text.as_hex(),
         'xtick.color':theme.secondary.as_hex(),
         'xtick.labelcolor':theme.text.as_hex(),

From c25df01d48809a8d34eb2210ea10debe62a028c4 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Sun, 7 Jan 2024 20:57:42 +0000
Subject: [PATCH 113/238] Add workflow dispatch option for doc build

---
 .github/workflows/docs.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 13ce3b85..5b721d92 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -10,6 +10,7 @@ on:
       - main
     tags:
       - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10
+  workflow_dispatch:
 
 jobs:
   build-docs:

From c1aae45735f5334fdfceff6b99709703f3f88c2f Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Sun, 7 Jan 2024 21:08:30 +0000
Subject: [PATCH 114/238] Fix package name???

---
 setup.cfg | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.cfg b/setup.cfg
index f308412c..e51dac3b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
 [metadata]
-name = napari_matplotlib
+name = napari-matplotlib
 description = A plugin to use Matplotlib with napari
 long_description = file: README.md
 long_description_content_type = text/markdown

From f73f225ff3220c841c9bca851050aa1136ae57c5 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Sun, 7 Jan 2024 21:37:12 +0000
Subject: [PATCH 115/238] [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
---
 src/napari_matplotlib/base.py | 19 ++++++++-----
 src/napari_matplotlib/util.py | 50 +++++++++++++++++------------------
 2 files changed, 37 insertions(+), 32 deletions(-)

diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py
index 2833249f..c865c8ad 100644
--- a/src/napari_matplotlib/base.py
+++ b/src/napari_matplotlib/base.py
@@ -5,13 +5,13 @@
 import matplotlib
 import matplotlib.style as mplstyle
 import napari
-from napari.utils.events import Event
-from napari.utils.theme import get_theme
 from matplotlib.backends.backend_qtagg import (  # type: ignore[attr-defined]
     FigureCanvasQTAgg,
     NavigationToolbar2QT,
 )
 from matplotlib.figure import Figure
+from napari.utils.events import Event
+from napari.utils.theme import get_theme
 from qtpy.QtGui import QIcon
 from qtpy.QtWidgets import QLabel, QVBoxLayout, QWidget
 
@@ -47,8 +47,10 @@ def __init__(
     ):
         super().__init__(parent=parent)
         self.viewer = napari_viewer
-        #self._mpl_style_sheet_path: Optional[Path] = None
-        self.napari_theme_style_sheet = style_sheet_from_theme(get_theme(napari_viewer.theme, as_dict=False))
+        # self._mpl_style_sheet_path: Optional[Path] = None
+        self.napari_theme_style_sheet = style_sheet_from_theme(
+            get_theme(napari_viewer.theme, as_dict=False)
+        )
 
         # Sets figure.* style
         with mplstyle.context(self.napari_theme_style_sheet):
@@ -88,7 +90,9 @@ def _on_napari_theme_changed(self, event: Event) -> None:
         event : napari.utils.events.Event
             Event that triggered the callback.
         """
-        self.napari_theme_style_sheet = style_sheet_from_theme(get_theme(event.value, as_dict=False))
+        self.napari_theme_style_sheet = style_sheet_from_theme(
+            get_theme(event.value, as_dict=False)
+        )
         self._replace_toolbar_icons()
 
     def _napari_theme_has_light_bg(self) -> bool:
@@ -208,7 +212,7 @@ def _on_napari_theme_changed(self, event: Event) -> None:
             Event that triggered the callback.
         """
         super()._on_napari_theme_changed(event)
-        # use self._draw instead of self.draw to cope with redraw while there are no 
+        # use self._draw instead of self.draw to cope with redraw while there are no
         # layers, this makes the self.clear() obsolete
         self._draw()
 
@@ -247,7 +251,8 @@ def _draw(self) -> None:
             # everything should be done in the style context
             self.clear()
             if self.n_selected_layers in self.n_layers_input and all(
-                isinstance(layer, self.input_layer_types) for layer in self.layers
+                isinstance(layer, self.input_layer_types)
+                for layer in self.layers
             ):
                 self.draw()
             self.canvas.draw()  # type: ignore[no-untyped-call]
diff --git a/src/napari_matplotlib/util.py b/src/napari_matplotlib/util.py
index b334dd10..ed994256 100644
--- a/src/napari_matplotlib/util.py
+++ b/src/napari_matplotlib/util.py
@@ -1,4 +1,4 @@
-from typing import Dict, Optional, Union
+from typing import Optional, Union
 from warnings import warn
 
 import napari.qt
@@ -141,47 +141,47 @@ def from_napari_css_get_size_of(
     return QSize(*fallback)
 
 
-def style_sheet_from_theme(theme: Theme) -> Dict[str, str]:
+def style_sheet_from_theme(theme: Theme) -> dict[str, str]:
     """Translate napari theme to a matplotlib style dictionary.
 
     Parameters
     ----------
     theme : napari.utils.theme.Theme
         Napari theme object representing the theme of the current viewer.
-    
+
     Returns
     -------
     Dict[str, str]
         Matplotlib compatible style dictionary.
     """
     return {
-        'axes.edgecolor':theme.secondary.as_hex(),
-        #BUG: could be the same as napari canvas, but facecolors do not get 
+        "axes.edgecolor": theme.secondary.as_hex(),
+        # BUG: could be the same as napari canvas, but facecolors do not get
         #     updated upon redraw for what ever reason
         #'axes.facecolor':theme.canvas.as_hex(),
-        'axes.facecolor':'none',
-        'axes.labelcolor':theme.text.as_hex(),
-        'boxplot.boxprops.color':theme.text.as_hex(),
-        'boxplot.capprops.color':theme.text.as_hex(),
-        'boxplot.flierprops.markeredgecolor':theme.text.as_hex(),
-        'boxplot.whiskerprops.color':theme.text.as_hex(),
-        'figure.edgecolor':theme.secondary.as_hex(),
-        #BUG: should be the same as napari background, but facecolors do not get 
+        "axes.facecolor": "none",
+        "axes.labelcolor": theme.text.as_hex(),
+        "boxplot.boxprops.color": theme.text.as_hex(),
+        "boxplot.capprops.color": theme.text.as_hex(),
+        "boxplot.flierprops.markeredgecolor": theme.text.as_hex(),
+        "boxplot.whiskerprops.color": theme.text.as_hex(),
+        "figure.edgecolor": theme.secondary.as_hex(),
+        # BUG: should be the same as napari background, but facecolors do not get
         #     updated upon redraw for what ever reason
         #'figure.facecolor':theme.background.as_hex(),
-        'figure.facecolor':'none',
-        'grid.color':theme.foreground.as_hex(),
-        #COMMENT: the hard coded colors are to match the previous behaviour 
+        "figure.facecolor": "none",
+        "grid.color": theme.foreground.as_hex(),
+        # COMMENT: the hard coded colors are to match the previous behaviour
         #         alternativly we could use the theme to style the legend as well
         #'legend.edgecolor':theme.secondary.as_hex(),
-        'legend.edgecolor':'black',
+        "legend.edgecolor": "black",
         #'legend.facecolor':theme.background.as_hex(),
-        'legend.facecolor':'white',
+        "legend.facecolor": "white",
         #'legend.labelcolor':theme.text.as_hex()
-        'legend.labelcolor':'black',
-        'text.color':theme.text.as_hex(),
-        'xtick.color':theme.secondary.as_hex(),
-        'xtick.labelcolor':theme.text.as_hex(),
-        'ytick.color':theme.secondary.as_hex(),
-        'ytick.labelcolor':theme.text.as_hex(),
-    }
\ No newline at end of file
+        "legend.labelcolor": "black",
+        "text.color": theme.text.as_hex(),
+        "xtick.color": theme.secondary.as_hex(),
+        "xtick.labelcolor": theme.text.as_hex(),
+        "ytick.color": theme.secondary.as_hex(),
+        "ytick.labelcolor": theme.text.as_hex(),
+    }

From a4130210d8bc23041898568caf20bbaa5dc5ba14 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 25 Dec 2023 17:38:11 +0000
Subject: [PATCH 116/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/psf/black: 23.11.0 → 23.12.1](https://github.com/psf/black/compare/23.11.0...23.12.1)
- [github.com/pre-commit/mirrors-mypy: v1.7.1 → v1.8.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.7.1...v1.8.0)
- [github.com/astral-sh/ruff-pre-commit: v0.1.6 → v0.1.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.6...v0.1.9)
---
 .pre-commit-config.yaml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 874c89fc..808fdc68 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -7,7 +7,7 @@ repos:
       - id: trailing-whitespace
 
   - repo: https://github.com/psf/black
-    rev: 23.11.0
+    rev: 23.12.1
     hooks:
       - id: black
 
@@ -17,14 +17,14 @@ repos:
       - id: napari-plugin-checks
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.7.1
+    rev: v1.8.0
     hooks:
      - id: mypy
        additional_dependencies: [numpy, matplotlib]
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.1.6'
+    rev: 'v0.1.9'
     hooks:
       - id: ruff
 

From 8e0c1487a7a06789ee578b877f71203175265535 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 8 Jan 2024 17:37:42 +0000
Subject: [PATCH 117/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.1.9 → v0.1.11](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.9...v0.1.11)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 808fdc68..8df635ab 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.1.9'
+    rev: 'v0.1.11'
     hooks:
       - id: ruff
 

From 75588ded4ac0dbf5b061984393f9dc1c7388b9b4 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Thu, 11 Jan 2024 11:58:44 +0000
Subject: [PATCH 118/238] Try and bump doc build napari pin

---
 setup.cfg | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.cfg b/setup.cfg
index f308412c..fa8b2394 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -47,7 +47,7 @@ napari.manifest =
 
 [options.extras_require]
 docs =
-    napari[all]==0.4.17
+    napari[all]==0.4.18
     numpydoc
     pydantic<2
     pydata-sphinx-theme

From c08e1583a96d0ae5642dd3a60321bc21a9a39f1f Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Wed, 26 Jul 2023 15:18:34 +0100
Subject: [PATCH 119/238] Add contrast limits to histogram plot

---
 src/napari_matplotlib/histogram.py | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index f78a8503..023d6a30 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -30,6 +30,24 @@ def __init__(
     ):
         super().__init__(napari_viewer, parent=parent)
         self._update_layers(None)
+        self.viewer.events.theme.connect(self._on_napari_theme_changed)
+
+    def on_update_layers(self) -> None:
+        """
+        Called when the selected layers are updated.
+        """
+        super().on_update_layers()
+        for layer in self.viewer.layers:
+            layer.events.contrast_limits.connect(self._update_contrast_lims)
+
+    def _update_contrast_lims(self) -> None:
+        [
+            line.set_xdata(lim)
+            for lim, line in zip(
+                self.layers[0].contrast_limits, self._contrast_lines
+            )
+        ]
+        self.figure.canvas.draw()
 
     def draw(self) -> None:
         """
@@ -63,6 +81,10 @@ def draw(self) -> None:
         else:
             self.axes.hist(data.ravel(), bins=bins, label=layer.name)
 
+        self._contrast_lines = [
+            self.axes.axvline(lim, color="white")
+            for lim in layer.contrast_limits
+        ]
         self.axes.legend()
 
 

From 49c64a2542dcc105ff30faf9eac1bfd9f9f96022 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Thu, 11 Jan 2024 12:01:05 +0000
Subject: [PATCH 120/238] Make mypy happy

---
 src/napari_matplotlib/histogram.py | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 023d6a30..27f328ae 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -41,12 +41,11 @@ def on_update_layers(self) -> None:
             layer.events.contrast_limits.connect(self._update_contrast_lims)
 
     def _update_contrast_lims(self) -> None:
-        [
+        for lim, line in zip(
+            self.layers[0].contrast_limits, self._contrast_lines
+        ):
             line.set_xdata(lim)
-            for lim, line in zip(
-                self.layers[0].contrast_limits, self._contrast_lines
-            )
-        ]
+
         self.figure.canvas.draw()
 
     def draw(self) -> None:

From e9853fb86208758ee29518543f3acc4fb0499f34 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Thu, 11 Jan 2024 14:11:08 +0000
Subject: [PATCH 121/238] Fix figure tests

---
 .../tests/baseline/test_custom_theme.png      | Bin 15253 -> 11914 bytes
 .../test_feature_histogram_points.png         | Bin 16815 -> 14262 bytes
 .../test_feature_histogram_vectors.png        | Bin 16178 -> 13818 bytes
 .../tests/baseline/test_histogram_2D.png      | Bin 28371 -> 28430 bytes
 .../tests/baseline/test_histogram_3D.png      | Bin 20123 -> 20215 bytes
 .../tests/baseline/test_slice_2D.png          | Bin 33181 -> 33418 bytes
 .../tests/baseline/test_slice_3D.png          | Bin 14108 -> 14112 bytes
 .../test_features_scatter_widget_2D.png       | Bin 24841 -> 24160 bytes
 .../scatter/baseline/test_scatter_2D.png      | Bin 18236 -> 18394 bytes
 .../scatter/baseline/test_scatter_3D.png      | Bin 16831 -> 17142 bytes
 10 files changed, 0 insertions(+), 0 deletions(-)

diff --git a/src/napari_matplotlib/tests/baseline/test_custom_theme.png b/src/napari_matplotlib/tests/baseline/test_custom_theme.png
index a668c103a7521c6791118def2595d02f312e328e..ffa4635bc5b244096f5e41c676d90fe5601784d3 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_feature_histogram_points.png b/src/napari_matplotlib/tests/baseline/test_feature_histogram_points.png
index b98a0170991a1da25b9ebf04ec259372e2aaf68d..74b84b2d565e293cb767ab9e4efea21e5825dbfa 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_feature_histogram_vectors.png b/src/napari_matplotlib/tests/baseline/test_feature_histogram_vectors.png
index 3b90586ef7def63f182d50e92feae1f659d9d97a..4675192b1d444e04d83bdfa9c872508195775126 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_histogram_2D.png b/src/napari_matplotlib/tests/baseline/test_histogram_2D.png
index b043bba855054f3c6d58bc0469e3fae66298faef..856d22d3381f7ee9c81ab77962573aa52bf56840 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_histogram_3D.png b/src/napari_matplotlib/tests/baseline/test_histogram_3D.png
index 724314e122c559d2b14efc8e208c1abd1092302e..b5670bca7b55900edf960372259216b591052d84 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_slice_2D.png b/src/napari_matplotlib/tests/baseline/test_slice_2D.png
index d39920be6e7a22aa819f43e987f147c6e5fcd353..c1e67637f47ed3023345738317b70b65c4ada2ff 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_slice_3D.png b/src/napari_matplotlib/tests/baseline/test_slice_3D.png
index cf563de57381af79660e08d20d69b475d7576172..046293f31091beb772a6bf17ff3cbf9a24e8625b 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/scatter/baseline/test_features_scatter_widget_2D.png b/src/napari_matplotlib/tests/scatter/baseline/test_features_scatter_widget_2D.png
index 75965607ecf3857805eddaf7b5ab50136063ffc1..9237dbdcdeb8bb300cd8ebb044819e3c0223bec6 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/scatter/baseline/test_scatter_2D.png b/src/napari_matplotlib/tests/scatter/baseline/test_scatter_2D.png
index 102191066069f97781e10c54be841777207a0f26..a11bda5f281b731712a2d1c6076f72569e0ff5e0 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/scatter/baseline/test_scatter_3D.png b/src/napari_matplotlib/tests/scatter/baseline/test_scatter_3D.png
index 3e648eec5deaaf7fb43b699ffaf184ef376d4979..cd42a8a21b31464f21c0e7622c509b667b977d13 100644
GIT binary patch

From 5fd84189d8f6589ecafe4dc31d7a3d40e1153f6c Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Thu, 11 Jan 2024 14:17:44 +0000
Subject: [PATCH 122/238] Clean out old styling code

---
 MANIFEST.in                                   |  1 -
 docs/user_guide.rst                           | 11 +--
 pyproject.toml                                |  2 +-
 src/napari_matplotlib/base.py                 |  6 --
 src/napari_matplotlib/styles/README.md        |  3 -
 src/napari_matplotlib/styles/dark.mplstyle    | 12 ----
 src/napari_matplotlib/styles/light.mplstyle   | 12 ----
 .../tests/data/test_theme.mplstyle            | 15 ----
 src/napari_matplotlib/tests/test_theme.py     | 71 +------------------
 9 files changed, 5 insertions(+), 128 deletions(-)
 delete mode 100644 src/napari_matplotlib/styles/README.md
 delete mode 100644 src/napari_matplotlib/styles/dark.mplstyle
 delete mode 100644 src/napari_matplotlib/styles/light.mplstyle
 delete mode 100644 src/napari_matplotlib/tests/data/test_theme.mplstyle

diff --git a/MANIFEST.in b/MANIFEST.in
index d625d95e..7ce16f9b 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,6 +1,5 @@
 include LICENSE
 include README.md
-recursive-include * *.mplstyle
 
 recursive-exclude * __pycache__
 recursive-exclude * *.py[co]
diff --git a/docs/user_guide.rst b/docs/user_guide.rst
index fbd48db1..253e3149 100644
--- a/docs/user_guide.rst
+++ b/docs/user_guide.rst
@@ -40,11 +40,6 @@ To use these:
 
 Customising plots
 -----------------
-`Matplotlib style sheets <https://matplotlib.org/stable/tutorials/introductory/customizing.html#defining-your-own-style>`__ can be used to customise
-the plots generated by ``napari-matplotlib``.
-To use a custom style sheet:
-
-1. Save it as ``napari-matplotlib.mplstyle``
-2. Put it in the Matplotlib configuration directory.
-   The location of this directory varies on different computers,
-   and can be found by calling :func:`matplotlib.get_configdir()`.
+``napari-matplotlib`` uses colours from the current napari theme to customise the
+Matplotlib plots. See `the example on creating a new napari theme
+<https://napari.org/stable/gallery/new_theme.html>`_ for a helpful guide.
diff --git a/pyproject.toml b/pyproject.toml
index dea0fd6a..22ff307f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,7 +7,7 @@ write_to = "src/napari_matplotlib/_version.py"
 
 [tool.pytest.ini_options]
 qt_api = "pyqt6"
-addopts = "--mpl"
+addopts = "--mpl --mpl-baseline-relative"
 filterwarnings = [
     "error",
     # Coming from vispy
diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py
index c865c8ad..fb9e485c 100644
--- a/src/napari_matplotlib/base.py
+++ b/src/napari_matplotlib/base.py
@@ -2,7 +2,6 @@
 from pathlib import Path
 from typing import Optional
 
-import matplotlib
 import matplotlib.style as mplstyle
 import napari
 from matplotlib.backends.backend_qtagg import (  # type: ignore[attr-defined]
@@ -19,10 +18,6 @@
 
 __all__ = ["BaseNapariMPLWidget", "NapariMPLWidget", "SingleAxesWidget"]
 
-_CUSTOM_STYLE_PATH = (
-    Path(matplotlib.get_configdir()) / "napari-matplotlib.mplstyle"
-)
-
 
 class BaseNapariMPLWidget(QWidget):
     """
@@ -47,7 +42,6 @@ def __init__(
     ):
         super().__init__(parent=parent)
         self.viewer = napari_viewer
-        # self._mpl_style_sheet_path: Optional[Path] = None
         self.napari_theme_style_sheet = style_sheet_from_theme(
             get_theme(napari_viewer.theme, as_dict=False)
         )
diff --git a/src/napari_matplotlib/styles/README.md b/src/napari_matplotlib/styles/README.md
deleted file mode 100644
index 79d3c417..00000000
--- a/src/napari_matplotlib/styles/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-This folder contains default built-in Matplotlib style sheets.
-See https://matplotlib.org/stable/tutorials/introductory/customizing.html#defining-your-own-style
-for more info on Matplotlib style sheets.
diff --git a/src/napari_matplotlib/styles/dark.mplstyle b/src/napari_matplotlib/styles/dark.mplstyle
deleted file mode 100644
index 1658f9b4..00000000
--- a/src/napari_matplotlib/styles/dark.mplstyle
+++ /dev/null
@@ -1,12 +0,0 @@
-# Dark-theme napari colour scheme for matplotlib plots
-
-# text (very light grey - almost white): #f0f1f2
-# foreground (mid grey):                 #414851
-# background (dark blue-gray):           #262930
-
-figure.facecolor    : none
-axes.labelcolor     : f0f1f2
-axes.facecolor      : none
-axes.edgecolor      : 414851
-xtick.color         : f0f1f2
-ytick.color         : f0f1f2
diff --git a/src/napari_matplotlib/styles/light.mplstyle b/src/napari_matplotlib/styles/light.mplstyle
deleted file mode 100644
index 3b8d7d1d..00000000
--- a/src/napari_matplotlib/styles/light.mplstyle
+++ /dev/null
@@ -1,12 +0,0 @@
-# Light-theme napari colour scheme for matplotlib plots
-
-# text (very dark grey - almost black): #3b3a39
-# foreground (mid grey):                #d6d0ce
-# background (brownish beige):          #efebe9
-
-figure.facecolor    : none
-axes.labelcolor     : 3b3a39
-axes.facecolor      : none
-axes.edgecolor      : d6d0ce
-xtick.color         : 3b3a39
-ytick.color         : 3b3a39
diff --git a/src/napari_matplotlib/tests/data/test_theme.mplstyle b/src/napari_matplotlib/tests/data/test_theme.mplstyle
deleted file mode 100644
index 2f94b31f..00000000
--- a/src/napari_matplotlib/tests/data/test_theme.mplstyle
+++ /dev/null
@@ -1,15 +0,0 @@
-# Dark-theme napari colour scheme for matplotlib plots
-
-#f4b8b2 # light red
-#b2e4f4 # light blue
-#0aa3fc # dark blue
-#008939 # dark green
-
-figure.facecolor    : f4b8b2 # light red
-axes.facecolor      : b2e4f4 # light blue
-axes.edgecolor      : 0aa3fc # dark blue
-
-xtick.color         : 008939 # dark green
-xtick.labelcolor    : 008939 # dark green
-ytick.color         : 008939 # dark green
-ytick.labelcolor    : 008939 # dark green
diff --git a/src/napari_matplotlib/tests/test_theme.py b/src/napari_matplotlib/tests/test_theme.py
index 1042d3f3..ac576d9c 100644
--- a/src/napari_matplotlib/tests/test_theme.py
+++ b/src/napari_matplotlib/tests/test_theme.py
@@ -1,15 +1,9 @@
-import os
-import shutil
-from copy import deepcopy
-from pathlib import Path
 
-import matplotlib
 import napari
 import numpy as np
 import pytest
-from matplotlib.colors import to_rgba
 
-from napari_matplotlib import HistogramWidget, ScatterWidget
+from napari_matplotlib import ScatterWidget
 from napari_matplotlib.base import NapariMPLWidget
 
 
@@ -127,66 +121,3 @@ def test_no_theme_side_effects(make_napari_viewer):
     unrelated_figure.tight_layout()
 
     return unrelated_figure
-
-
-@pytest.mark.mpl_image_compare
-def test_custom_theme(make_napari_viewer, theme_path, brain_data):
-    viewer = make_napari_viewer()
-    viewer.theme = "dark"
-
-    widget = ScatterWidget(viewer)
-    widget.mpl_style_sheet_path = theme_path
-
-    viewer.add_image(brain_data[0], **brain_data[1], name="brain")
-    viewer.add_image(
-        brain_data[0] * -1, **brain_data[1], name="brain_reversed"
-    )
-
-    viewer.layers.selection.clear()
-    viewer.layers.selection.add(viewer.layers[0])
-    viewer.layers.selection.add(viewer.layers[1])
-
-    return deepcopy(widget.figure)
-
-
-def find_mpl_stylesheet(name: str) -> Path:
-    """Find the built-in matplotlib stylesheet."""
-    return Path(matplotlib.__path__[0]) / f"mpl-data/stylelib/{name}.mplstyle"
-
-
-def test_custom_stylesheet(make_napari_viewer, image_data):
-    """
-    Test that a stylesheet in the current directory is given precidence.
-
-    Do this by copying over a stylesheet from matplotlib's built in styles,
-    naming it correctly, and checking the colours are as expected.
-    """
-    # Copy Solarize_Light2 as if it was a user-overriden stylesheet.
-    style_sheet_path = (
-        Path(matplotlib.get_configdir()) / "napari-matplotlib.mplstyle"
-    )
-    if style_sheet_path.exists():
-        pytest.skip("Won't ovewrite existing custom style sheet.")
-    shutil.copy(
-        find_mpl_stylesheet("Solarize_Light2"),
-        style_sheet_path,
-    )
-
-    try:
-        viewer = make_napari_viewer()
-        viewer.add_image(image_data[0], **image_data[1])
-        widget = HistogramWidget(viewer)
-        assert widget.mpl_style_sheet_path == style_sheet_path
-        ax = widget.figure.gca()
-
-        # The axes should have a light brownish grey background:
-        assert ax.get_facecolor() == to_rgba("#eee8d5")
-        assert ax.patch.get_facecolor() == to_rgba("#eee8d5")
-
-        # The figure background and axis gridlines are light yellow:
-        assert widget.figure.patch.get_facecolor() == to_rgba("#fdf6e3")
-        for gridline in ax.get_xgridlines() + ax.get_ygridlines():
-            assert gridline.get_visible() is True
-            assert gridline.get_color() == "#b0b0b0"
-    finally:
-        os.remove(style_sheet_path)

From 09abee3aba7ee72dbe4ebf6dcaa26793ed11d2d5 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Thu, 11 Jan 2024 14:46:50 +0000
Subject: [PATCH 123/238] Add a features histogram example

---
 examples/features_hist.py | 42 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)
 create mode 100644 examples/features_hist.py

diff --git a/examples/features_hist.py b/examples/features_hist.py
new file mode 100644
index 00000000..899ddef3
--- /dev/null
+++ b/examples/features_hist.py
@@ -0,0 +1,42 @@
+"""
+Hisogram of features
+====================
+"""
+
+import napari
+import numpy as np
+import numpy.typing as npt
+from skimage.measure import regionprops_table
+
+# make a test label image
+label_image: npt.NDArray[np.uint16] = np.zeros((100, 100), dtype=np.uint16)
+
+label_image[10:20, 10:20] = 1
+label_image[50:70, 50:70] = 2
+
+feature_table_1 = regionprops_table(
+    label_image, properties=("label", "area", "perimeter")
+)
+feature_table_1["index"] = feature_table_1["label"]
+
+# make the points data
+n_points = 100
+points_data = 100 * np.random.random((100, 2))
+points_features = {
+    "feature_0": np.random.random((n_points,)),
+    "feature_1": np.random.random((n_points,)),
+    "feature_2": np.random.random((n_points,)),
+}
+
+# create the viewer
+viewer = napari.Viewer()
+viewer.add_labels(label_image, features=feature_table_1)
+viewer.add_points(points_data, features=points_features)
+
+# make the widget
+viewer.window.add_plugin_dock_widget(
+    plugin_name="napari-matplotlib", widget_name="FeaturesHistogram"
+)
+
+if __name__ == "__main__":
+    napari.run()

From a9630f780aa7abd6a57d00cbeec375dfec39193b Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Sat, 13 Jan 2024 12:02:13 +0000
Subject: [PATCH 124/238] [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
---
 src/napari_matplotlib/tests/test_theme.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/napari_matplotlib/tests/test_theme.py b/src/napari_matplotlib/tests/test_theme.py
index ac576d9c..2310f32f 100644
--- a/src/napari_matplotlib/tests/test_theme.py
+++ b/src/napari_matplotlib/tests/test_theme.py
@@ -1,4 +1,3 @@
-
 import napari
 import numpy as np
 import pytest

From f06d31d250fcb1657e219171d5ce19d2c5db173f Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Sat, 13 Jan 2024 12:21:01 +0000
Subject: [PATCH 125/238] Try napari 0.4.19rc3

---
 setup.cfg | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.cfg b/setup.cfg
index fa8b2394..312ffaa1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -47,7 +47,7 @@ napari.manifest =
 
 [options.extras_require]
 docs =
-    napari[all]==0.4.18
+    napari[all]==0.4.19rc3
     numpydoc
     pydantic<2
     pydata-sphinx-theme

From 99a3fc0a1ab3d4f97937d862b0517890c54d31c1 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Thu, 11 Jan 2024 13:01:59 +0000
Subject: [PATCH 126/238] Set np.linspace dtype based on image dtype for the
 HistogramWidget

---
 src/napari_matplotlib/histogram.py | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 27f328ae..7cf24167 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -4,7 +4,12 @@
 import numpy as np
 import numpy.typing as npt
 from matplotlib.container import BarContainer
-from qtpy.QtWidgets import QComboBox, QLabel, QVBoxLayout, QWidget
+from qtpy.QtWidgets import (
+    QComboBox,
+    QLabel,
+    QVBoxLayout,
+    QWidget,
+)
 
 from .base import SingleAxesWidget
 from .features import FEATURES_LAYER_TYPES
@@ -65,7 +70,7 @@ def draw(self) -> None:
 
         # Important to calculate bins after slicing 3D data, to avoid reading
         # whole cube into memory.
-        bins = np.linspace(np.min(data), np.max(data), 100)
+        bins = np.linspace(np.min(data), np.max(data), 100, dtype=data.dtype)
 
         if layer.rgb:
             # Histogram RGB channels independently

From 2fea1b0587545d28e350abb24ebb903ee8f58ea6 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Thu, 11 Jan 2024 13:04:00 +0000
Subject: [PATCH 127/238] update changelog

---
 docs/changelog.rst | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 45952311..cb591f94 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -8,6 +8,10 @@ Changes
 - Histogram plots of points and vector layers are now coloured with their napari colourmap.
 - Added support for Matplotlib 3.8
 
+Bug fixes
+~~~~~~~~~
+- Use integer bin limits for integer images in ``HistogramWidget``
+
 1.1.0
 -----
 Additions

From c59f1e2c42fd0e3d9b73c26d749c6ecc4d4fa3dc Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Thu, 11 Jan 2024 14:49:44 +0000
Subject: [PATCH 128/238] ensure histograms of integer data have equal bin
 width

---
 src/napari_matplotlib/histogram.py | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 7cf24167..5c2b3997 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -70,7 +70,12 @@ def draw(self) -> None:
 
         # Important to calculate bins after slicing 3D data, to avoid reading
         # whole cube into memory.
-        bins = np.linspace(np.min(data), np.max(data), 100, dtype=data.dtype)
+        if data.dtype.kind == "i":
+            # Make sure integer data types have integer sized bins
+            step = (np.max(data) -  np.min(data)) // 100
+            bins = np.arange(np.min(data), np.max(data) + step, step)
+        else:
+            bins = np.linspace(np.min(data), np.max(data), 100)
 
         if layer.rgb:
             # Histogram RGB channels independently

From 75bfe52c8005bb023b47038debbabba5c7403aa5 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Thu, 11 Jan 2024 14:50:55 +0000
Subject: [PATCH 129/238] make linters happy

---
 src/napari_matplotlib/histogram.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 5c2b3997..8cbf543b 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -72,7 +72,7 @@ def draw(self) -> None:
         # whole cube into memory.
         if data.dtype.kind == "i":
             # Make sure integer data types have integer sized bins
-            step = (np.max(data) -  np.min(data)) // 100
+            step = (np.max(data) - np.min(data)) // 100
             bins = np.arange(np.min(data), np.max(data) + step, step)
         else:
             bins = np.linspace(np.min(data), np.max(data), 100)

From 7b48bc31da0fbd0aa1afff8994ee5c624f2429ed Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Thu, 11 Jan 2024 15:19:22 +0000
Subject: [PATCH 130/238] convert bins array to list to make mypy happy

---
 src/napari_matplotlib/histogram.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 8cbf543b..1b494bef 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -82,13 +82,13 @@ def draw(self) -> None:
             for i, c in enumerate("rgb"):
                 self.axes.hist(
                     data[..., i].ravel(),
-                    bins=bins,
+                    bins=bins.tolist(),
                     label=c,
                     histtype="step",
                     color=_COLORS[c],
                 )
         else:
-            self.axes.hist(data.ravel(), bins=bins, label=layer.name)
+            self.axes.hist(data.ravel(), bins=bins.tolist(), label=layer.name)
 
         self._contrast_lines = [
             self.axes.axvline(lim, color="white")

From 71f671662a70a746343762f9449ea66af9436ea8 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Sat, 13 Jan 2024 12:43:44 +0000
Subject: [PATCH 131/238] Fix integer step size

---
 src/napari_matplotlib/histogram.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 1b494bef..f4191d78 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -72,7 +72,7 @@ def draw(self) -> None:
         # whole cube into memory.
         if data.dtype.kind == "i":
             # Make sure integer data types have integer sized bins
-            step = (np.max(data) - np.min(data)) // 100
+            step = 1 + (np.max(data) - np.min(data)) // 100
             bins = np.arange(np.min(data), np.max(data) + step, step)
         else:
             bins = np.linspace(np.min(data), np.max(data), 100)

From 166d77643f5851c87b3200dce1ef5f7d3912abff Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Mon, 15 Jan 2024 09:01:23 +0000
Subject: [PATCH 132/238] Use integer bins for unisnged int data

---
 src/napari_matplotlib/histogram.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index f4191d78..32ea0d9d 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -70,7 +70,7 @@ def draw(self) -> None:
 
         # Important to calculate bins after slicing 3D data, to avoid reading
         # whole cube into memory.
-        if data.dtype.kind == "i":
+        if data.dtype.kind in {"i", "u"}:
             # Make sure integer data types have integer sized bins
             step = 1 + (np.max(data) - np.min(data)) // 100
             bins = np.arange(np.min(data), np.max(data) + step, step)

From 14121492690b46bffb0aa7d05082d3ea86e8f120 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Mon, 15 Jan 2024 09:02:08 +0000
Subject: [PATCH 133/238] ensure histogram bin step size is always a positive
 integer

---
 src/napari_matplotlib/histogram.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 32ea0d9d..1d1f29bb 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -72,7 +72,7 @@ def draw(self) -> None:
         # whole cube into memory.
         if data.dtype.kind in {"i", "u"}:
             # Make sure integer data types have integer sized bins
-            step = 1 + (np.max(data) - np.min(data)) // 100
+            step = 1 + abs(np.max(data) - np.min(data)) // 100
             bins = np.arange(np.min(data), np.max(data) + step, step)
         else:
             bins = np.linspace(np.min(data), np.max(data), 100)

From 610f19cbeae553c969785763f3d505e46847771d Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Mon, 15 Jan 2024 11:00:17 +0000
Subject: [PATCH 134/238] Ensure step size is at least 1

Update baseline images
---
 src/napari_matplotlib/histogram.py            |   3 ++-
 .../tests/baseline/test_histogram_2D.png      | Bin 28430 -> 25526 bytes
 .../tests/baseline/test_histogram_3D.png      | Bin 20215 -> 20181 bytes
 3 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 1d1f29bb..2db2f08b 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -72,7 +72,8 @@ def draw(self) -> None:
         # whole cube into memory.
         if data.dtype.kind in {"i", "u"}:
             # Make sure integer data types have integer sized bins
-            step = 1 + abs(np.max(data) - np.min(data)) // 100
+            step = abs(np.max(data) - np.min(data)) // 100
+            step = max(1, step)
             bins = np.arange(np.min(data), np.max(data) + step, step)
         else:
             bins = np.linspace(np.min(data), np.max(data), 100)
diff --git a/src/napari_matplotlib/tests/baseline/test_histogram_2D.png b/src/napari_matplotlib/tests/baseline/test_histogram_2D.png
index 856d22d3381f7ee9c81ab77962573aa52bf56840..00023ae722cf7317a8ef83b9cc9f7ce4fecf286d 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_histogram_3D.png b/src/napari_matplotlib/tests/baseline/test_histogram_3D.png
index b5670bca7b55900edf960372259216b591052d84..bd3cafb84784d97343f4bf9f1ef52ad4856a6d96 100644
GIT binary patch

From f149a10ebbfbc88c96cca213a1710a610eb163d9 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Mon, 15 Jan 2024 15:09:40 +0000
Subject: [PATCH 135/238] Add changelog for 2.0

---
 docs/changelog.rst | 25 +++++++++++++++++++++----
 1 file changed, 21 insertions(+), 4 deletions(-)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index cb591f94..6395e9c1 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,5 +1,26 @@
 Changelog
 =========
+2.0.0
+-----
+Changes to custom theming
+~~~~~~~~~~~~~~~~~~~~~~~~~
+``napari-matplotlib`` now uses colours from the current napari theme to customise the
+Matplotlib plots. See `the example on creating a new napari theme
+<https://napari.org/stable/gallery/new_theme.html>`_ for a helpful guide on how to
+create custom napari themes.
+
+This means support for custom Matplotlib styles sheets has been removed.
+
+If you spot any issues with the new theming, please report them at
+https://github.com/matplotlib/napari-matplotlib/issues.
+
+Other changes
+~~~~~~~~~~~~~
+- Histogram bin sizes for integer-type data are now force to be an integer.
+- The ``HistogramWidget`` now has two vertical lines showing the contrast limits used
+  to render the selected layer in the main napari window.
+- Added an example gallery for the ``FeaturesHistogramWidget``.
+
 1.2.0
 -----
 Changes
@@ -8,10 +29,6 @@ Changes
 - Histogram plots of points and vector layers are now coloured with their napari colourmap.
 - Added support for Matplotlib 3.8
 
-Bug fixes
-~~~~~~~~~
-- Use integer bin limits for integer images in ``HistogramWidget``
-
 1.1.0
 -----
 Additions

From a7b09d4ce297cad9dd6fa424403c8398ec20bc74 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Thu, 11 Jan 2024 10:55:41 +0000
Subject: [PATCH 136/238] Add widgets to set bin parameters for histograms

Also set np.linspace dtype based on image dtype
---
 src/napari_matplotlib/histogram.py | 130 +++++++++++++++++++++++++----
 1 file changed, 116 insertions(+), 14 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 2db2f08b..fd44d6f0 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -4,12 +4,7 @@
 import numpy as np
 import numpy.typing as npt
 from matplotlib.container import BarContainer
-from qtpy.QtWidgets import (
-    QComboBox,
-    QLabel,
-    QVBoxLayout,
-    QWidget,
-)
+from qtpy.QtWidgets import QComboBox, QLabel, QVBoxLayout, QWidget, QGroupBox, QFormLayout, QDoubleSpinBox, QSpinBox, QAbstractSpinBox
 
 from .base import SingleAxesWidget
 from .features import FEATURES_LAYER_TYPES
@@ -34,6 +29,50 @@ def __init__(
         parent: Optional[QWidget] = None,
     ):
         super().__init__(napari_viewer, parent=parent)
+
+        # Create widgets for setting bin parameters
+        bins_start = QDoubleSpinBox()
+        bins_start.setObjectName("bins start")
+        bins_start.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType)
+        bins_start.setRange(-1e10, 1e10)
+        bins_start.setValue(0)
+        bins_start.setWrapping(True)
+        bins_start.setKeyboardTracking(False)
+        bins_start.setDecimals(2)
+
+        bins_stop = QDoubleSpinBox()
+        bins_stop.setObjectName("bins stop")
+        bins_stop.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType)
+        bins_stop.setRange(-1e10, 1e10)
+        bins_stop.setValue(100)
+        bins_stop.setKeyboardTracking(False)
+        bins_stop.setDecimals(2)
+
+        bins_num = QSpinBox()
+        bins_num.setObjectName("bins num")
+        bins_num.setRange(1, 100_000)
+        bins_num.setValue(101)
+        bins_num.setWrapping(False)
+        bins_num.setKeyboardTracking(False)
+
+        # Set bins widget layout
+        bins_selection_layout = QFormLayout()
+        bins_selection_layout.addRow("start", bins_start)
+        bins_selection_layout.addRow("stop", bins_stop)
+        bins_selection_layout.addRow("num", bins_num)
+
+        # Group the widgets and add to main layout
+        bins_widget_group = QGroupBox("Bins")
+        bins_widget_group_layout = QVBoxLayout()
+        bins_widget_group_layout.addLayout(bins_selection_layout)
+        bins_widget_group.setLayout(bins_widget_group_layout)
+        self.layout().addWidget(bins_widget_group)
+
+        # Add callbacks
+        bins_start.valueChanged.connect(self._draw)
+        bins_stop.valueChanged.connect(self._draw)
+        bins_num.valueChanged.connect(self._draw)
+
         self._update_layers(None)
         self.viewer.events.theme.connect(self._on_napari_theme_changed)
 
@@ -53,11 +92,47 @@ def _update_contrast_lims(self) -> None:
 
         self.figure.canvas.draw()
 
-    def draw(self) -> None:
-        """
-        Clear the axes and histogram the currently selected layer/slice.
-        """
-        layer = self.layers[0]
+    @property
+    def bins_start(self) -> float:
+        """Minimum bin edge"""
+        return self.findChild(QDoubleSpinBox, name="bins start").value()
+
+    @bins_start.setter
+    def bins_start(self, start: int | float) -> None:
+        """Set the minimum bin edge"""
+        self.findChild(QDoubleSpinBox, name="bins start").setValue(start)
+
+    @property
+    def bins_stop(self) -> float:
+        """Maximum bin edge"""
+        return self.findChild(QDoubleSpinBox, name="bins stop").value()
+
+    @bins_stop.setter
+    def bins_stop(self, stop: int | float) -> None:
+        """Set the maximum bin edge"""
+        self.findChild(QDoubleSpinBox, name="bins stop").setValue(stop)
+
+    @property
+    def bins_num(self) -> int:
+        """Number of bins to use"""
+        return self.findChild(QSpinBox, name="bins num").value()
+
+    @bins_num.setter
+    def bins_num(self, num: int) -> None:
+        """Set the number of bins to use"""
+        self.findChild(QSpinBox, name="bins num").setValue(num)
+
+    def autoset_widget_bins(self, data: npt.ArrayLike) -> None:
+        """Update widgets with bins determined from the image data"""
+
+        bins = np.linspace(np.min(data), np.max(data), 100, dtype=data.dtype)
+        self.bins_start = bins[0]
+        self.bins_stop = bins[-1]
+        self.bins_num = bins.size
+
+
+    def _get_layer_data(self, layer) -> np.ndarray:
+        """Get the data associated with a given layer"""
 
         if layer.data.ndim - layer.rgb == 3:
             # 3D data, can be single channel or RGB
@@ -65,18 +140,45 @@ def draw(self) -> None:
             self.axes.set_title(f"z={self.current_z}")
         else:
             data = layer.data
+
         # Read data into memory if it's a dask array
         data = np.asarray(data)
 
+        return data
+
+    def on_update_layers(self) -> None:
+        """
+        Called when the layer selection changes by ``self._update_layers()``.
+        """
+
+        if not self.layers:
+            return
+
+        # Reset to bin start, stop and step
+        layer_data = self._get_layer_data(self.layers[0])
+        self.autoset_widget_bins(data=layer_data)
+
+        # Only allow integer bins for integer data
+        n_decimals = 0 if np.issubdtype(layer_data.dtype, np.integer) else 2
+        self.findChild(QDoubleSpinBox, name="bins start").setDecimals(n_decimals)
+        self.findChild(QDoubleSpinBox, name="bins stop").setDecimals(n_decimals)
+
+    def draw(self) -> None:
+        """
+        Clear the axes and histogram the currently selected layer/slice.
+        """
+        layer = self.layers[0]
+        data = self._get_layer_data(layer)
+
         # Important to calculate bins after slicing 3D data, to avoid reading
         # whole cube into memory.
         if data.dtype.kind in {"i", "u"}:
             # Make sure integer data types have integer sized bins
-            step = abs(np.max(data) - np.min(data)) // 100
+            step = (self.bins_start - self.bins_stop) // self.bins_num
             step = max(1, step)
-            bins = np.arange(np.min(data), np.max(data) + step, step)
+            bins = np.arange(self.bins_start, self.bins_stop + step, step)
         else:
-            bins = np.linspace(np.min(data), np.max(data), 100)
+            bins = np.linspace(self.bins_start, self.bins_stop, self.bins_num)
 
         if layer.rgb:
             # Histogram RGB channels independently

From 96b2f0cd3cc6f5400ba4f8eaca71ea9482a56a07 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Thu, 11 Jan 2024 11:44:02 +0000
Subject: [PATCH 137/238] Add test for histogram widget when setting bin
 parameters

---
 .../tests/baseline/test_histogram_2D_bins.png   | Bin 0 -> 21366 bytes
 src/napari_matplotlib/tests/test_histogram.py   |  15 ++++++++++++++-
 2 files changed, 14 insertions(+), 1 deletion(-)
 create mode 100644 src/napari_matplotlib/tests/baseline/test_histogram_2D_bins.png

diff --git a/src/napari_matplotlib/tests/baseline/test_histogram_2D_bins.png b/src/napari_matplotlib/tests/baseline/test_histogram_2D_bins.png
new file mode 100644
index 0000000000000000000000000000000000000000..eb43c3108147079f9410a40936975d94158ac6f0
GIT binary patch
diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py
index 1ceca519..b392ef50 100644
--- a/src/napari_matplotlib/tests/test_histogram.py
+++ b/src/napari_matplotlib/tests/test_histogram.py
@@ -9,6 +9,20 @@
     assert_figures_not_equal,
 )
 
+@pytest.mark.mpl_image_compare
+def test_histogram_2D_bins(make_napari_viewer, astronaut_data):
+    viewer = make_napari_viewer()
+    viewer.theme = "light"
+    viewer.add_image(astronaut_data[0], **astronaut_data[1])
+    widget = HistogramWidget(viewer)
+    viewer.window.add_dock_widget(widget)
+    widget.bins_start = -50
+    widget.bins_stop = 300
+    widget.bins_num = 35
+    fig = widget.figure
+    # Need to return a copy, as original figure is too eagerley garbage
+    # collected by the widget
+    return deepcopy(fig)
 
 @pytest.mark.mpl_image_compare
 def test_histogram_2D(make_napari_viewer, astronaut_data):
@@ -20,7 +34,6 @@ def test_histogram_2D(make_napari_viewer, astronaut_data):
     # collected by the widget
     return deepcopy(fig)
 
-
 @pytest.mark.mpl_image_compare
 def test_histogram_3D(make_napari_viewer, brain_data):
     viewer = make_napari_viewer()

From b8623ededfa0dd4c922f507a38a9430b86665328 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Thu, 11 Jan 2024 11:50:21 +0000
Subject: [PATCH 138/238] Update changelog

---
 docs/changelog.rst | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index cb591f94..96b353dc 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -7,6 +7,11 @@ Changes
 - Dropped support for Python 3.8, and added support for Python 3.11.
 - Histogram plots of points and vector layers are now coloured with their napari colourmap.
 - Added support for Matplotlib 3.8
+- Add widgets for setting histogram bin parameters
+
+Bug fixes
+~~~~~~~~~
+- Use integer bin limits for integer images in ``HistogramWidget``
 
 Bug fixes
 ~~~~~~~~~

From 4e4fb84b8dc7a1049dd7b1795425d06e117310f5 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Thu, 11 Jan 2024 11:52:56 +0000
Subject: [PATCH 139/238] Make linters happy

---
 src/napari_matplotlib/histogram.py            | 24 +++++++++++++------
 src/napari_matplotlib/tests/test_histogram.py |  3 +++
 2 files changed, 20 insertions(+), 7 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index fd44d6f0..c5272665 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -4,7 +4,17 @@
 import numpy as np
 import numpy.typing as npt
 from matplotlib.container import BarContainer
-from qtpy.QtWidgets import QComboBox, QLabel, QVBoxLayout, QWidget, QGroupBox, QFormLayout, QDoubleSpinBox, QSpinBox, QAbstractSpinBox
+from qtpy.QtWidgets import (
+    QAbstractSpinBox,
+    QComboBox,
+    QDoubleSpinBox,
+    QFormLayout,
+    QGroupBox,
+    QLabel,
+    QSpinBox,
+    QVBoxLayout,
+    QWidget,
+)
 
 from .base import SingleAxesWidget
 from .features import FEATURES_LAYER_TYPES
@@ -124,16 +134,13 @@ def bins_num(self, num: int) -> None:
 
     def autoset_widget_bins(self, data: npt.ArrayLike) -> None:
         """Update widgets with bins determined from the image data"""
-
         bins = np.linspace(np.min(data), np.max(data), 100, dtype=data.dtype)
         self.bins_start = bins[0]
         self.bins_stop = bins[-1]
         self.bins_num = bins.size
 
-
     def _get_layer_data(self, layer) -> np.ndarray:
         """Get the data associated with a given layer"""
-
         if layer.data.ndim - layer.rgb == 3:
             # 3D data, can be single channel or RGB
             data = layer.data[self.current_z]
@@ -150,7 +157,6 @@ def on_update_layers(self) -> None:
         """
         Called when the layer selection changes by ``self._update_layers()``.
         """
-
         if not self.layers:
             return
 
@@ -160,8 +166,12 @@ def on_update_layers(self) -> None:
 
         # Only allow integer bins for integer data
         n_decimals = 0 if np.issubdtype(layer_data.dtype, np.integer) else 2
-        self.findChild(QDoubleSpinBox, name="bins start").setDecimals(n_decimals)
-        self.findChild(QDoubleSpinBox, name="bins stop").setDecimals(n_decimals)
+        self.findChild(QDoubleSpinBox, name="bins start").setDecimals(
+            n_decimals
+        )
+        self.findChild(QDoubleSpinBox, name="bins stop").setDecimals(
+            n_decimals
+        )
 
     def draw(self) -> None:
         """
diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py
index b392ef50..58acf236 100644
--- a/src/napari_matplotlib/tests/test_histogram.py
+++ b/src/napari_matplotlib/tests/test_histogram.py
@@ -9,6 +9,7 @@
     assert_figures_not_equal,
 )
 
+
 @pytest.mark.mpl_image_compare
 def test_histogram_2D_bins(make_napari_viewer, astronaut_data):
     viewer = make_napari_viewer()
@@ -24,6 +25,7 @@ def test_histogram_2D_bins(make_napari_viewer, astronaut_data):
     # collected by the widget
     return deepcopy(fig)
 
+
 @pytest.mark.mpl_image_compare
 def test_histogram_2D(make_napari_viewer, astronaut_data):
     viewer = make_napari_viewer()
@@ -34,6 +36,7 @@ def test_histogram_2D(make_napari_viewer, astronaut_data):
     # collected by the widget
     return deepcopy(fig)
 
+
 @pytest.mark.mpl_image_compare
 def test_histogram_3D(make_napari_viewer, brain_data):
     viewer = make_napari_viewer()

From 5ac1f712cfa8f760a5e4e9ad028fd8882d087ee4 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Thu, 11 Jan 2024 12:18:55 +0000
Subject: [PATCH 140/238] Fix type hints

---
 src/napari_matplotlib/histogram.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index c5272665..3615172b 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -1,4 +1,4 @@
-from typing import Any, Optional, cast
+from typing import Any, Optional, Union, cast
 
 import napari
 import numpy as np
@@ -108,7 +108,7 @@ def bins_start(self) -> float:
         return self.findChild(QDoubleSpinBox, name="bins start").value()
 
     @bins_start.setter
-    def bins_start(self, start: int | float) -> None:
+    def bins_start(self, start: Union[int, float]) -> None:
         """Set the minimum bin edge"""
         self.findChild(QDoubleSpinBox, name="bins start").setValue(start)
 
@@ -118,7 +118,7 @@ def bins_stop(self) -> float:
         return self.findChild(QDoubleSpinBox, name="bins stop").value()
 
     @bins_stop.setter
-    def bins_stop(self, stop: int | float) -> None:
+    def bins_stop(self, stop: Union[int, float]) -> None:
         """Set the maximum bin edge"""
         self.findChild(QDoubleSpinBox, name="bins stop").setValue(stop)
 
@@ -132,14 +132,14 @@ def bins_num(self, num: int) -> None:
         """Set the number of bins to use"""
         self.findChild(QSpinBox, name="bins num").setValue(num)
 
-    def autoset_widget_bins(self, data: npt.ArrayLike) -> None:
+    def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None:
         """Update widgets with bins determined from the image data"""
         bins = np.linspace(np.min(data), np.max(data), 100, dtype=data.dtype)
         self.bins_start = bins[0]
         self.bins_stop = bins[-1]
         self.bins_num = bins.size
 
-    def _get_layer_data(self, layer) -> np.ndarray:
+    def _get_layer_data(self, layer: napari.layers.Layer) -> npt.NDArray[Any]:
         """Get the data associated with a given layer"""
         if layer.data.ndim - layer.rgb == 3:
             # 3D data, can be single channel or RGB

From fab29063cdb87b848c35002847656f663ac051ef Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Thu, 11 Jan 2024 12:51:53 +0000
Subject: [PATCH 141/238] Don't allow bins lower than 0 if dtype is unisgned

---
 examples/histogram.py              |  2 +-
 src/napari_matplotlib/histogram.py | 19 ++++++++++++-------
 2 files changed, 13 insertions(+), 8 deletions(-)

diff --git a/examples/histogram.py b/examples/histogram.py
index ccda491a..17111f53 100644
--- a/examples/histogram.py
+++ b/examples/histogram.py
@@ -5,7 +5,7 @@
 import napari
 
 viewer = napari.Viewer()
-viewer.open_sample("napari", "kidney")
+viewer.open_sample("napari", "coins")
 
 viewer.window.add_plugin_dock_widget(
     plugin_name="napari-matplotlib", widget_name="Histogram"
diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 3615172b..348a506e 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -46,7 +46,7 @@ def __init__(
         bins_start.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType)
         bins_start.setRange(-1e10, 1e10)
         bins_start.setValue(0)
-        bins_start.setWrapping(True)
+        bins_start.setWrapping(False)
         bins_start.setKeyboardTracking(False)
         bins_start.setDecimals(2)
 
@@ -55,6 +55,7 @@ def __init__(
         bins_stop.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType)
         bins_stop.setRange(-1e10, 1e10)
         bins_stop.setValue(100)
+        bins_start.setWrapping(False)
         bins_stop.setKeyboardTracking(False)
         bins_stop.setDecimals(2)
 
@@ -165,13 +166,17 @@ def on_update_layers(self) -> None:
         self.autoset_widget_bins(data=layer_data)
 
         # Only allow integer bins for integer data
+        # And only allow values greater than 0 for unsigned integers
         n_decimals = 0 if np.issubdtype(layer_data.dtype, np.integer) else 2
-        self.findChild(QDoubleSpinBox, name="bins start").setDecimals(
-            n_decimals
-        )
-        self.findChild(QDoubleSpinBox, name="bins stop").setDecimals(
-            n_decimals
-        )
+        is_unsigned = layer_data.dtype.kind == "u"
+        minimum_value = 0 if is_unsigned else -1e10
+
+        bins_start = self.findChild(QDoubleSpinBox, name="bins start")
+        bins_stop = self.findChild(QDoubleSpinBox, name="bins stop")
+        bins_start.setDecimals(n_decimals)
+        bins_stop.setDecimals(n_decimals)
+        bins_start.setMinimum(minimum_value)
+        bins_stop.setMinimum(minimum_value)
 
     def draw(self) -> None:
         """

From 55531747ef1e8c5597df9c081c44bc92a6dc3d3c Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Thu, 11 Jan 2024 13:12:56 +0000
Subject: [PATCH 142/238] Update changelog

---
 docs/changelog.rst | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 96b353dc..226cbb55 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -13,10 +13,6 @@ Bug fixes
 ~~~~~~~~~
 - Use integer bin limits for integer images in ``HistogramWidget``
 
-Bug fixes
-~~~~~~~~~
-- Use integer bin limits for integer images in ``HistogramWidget``
-
 1.1.0
 -----
 Additions

From e86d4f692a5252881924a3e35c317d1cb24a1723 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Thu, 11 Jan 2024 13:13:17 +0000
Subject: [PATCH 143/238] Undo changes to example of HistogramWidget

---
 examples/histogram.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/examples/histogram.py b/examples/histogram.py
index 17111f53..ccda491a 100644
--- a/examples/histogram.py
+++ b/examples/histogram.py
@@ -5,7 +5,7 @@
 import napari
 
 viewer = napari.Viewer()
-viewer.open_sample("napari", "coins")
+viewer.open_sample("napari", "kidney")
 
 viewer.window.add_plugin_dock_widget(
     plugin_name="napari-matplotlib", widget_name="Histogram"

From 6513a69c16d27d44a64396ca968152f4f45dddef Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Mon, 15 Jan 2024 15:26:25 +0000
Subject: [PATCH 144/238] Re-use histogram binning

---
 src/napari_matplotlib/histogram.py            |  24 ++++++++++--------
 .../test_feature_histogram_points.png         | Bin 14262 -> 17145 bytes
 .../test_feature_histogram_vectors.png        | Bin 13818 -> 17259 bytes
 .../tests/baseline/test_histogram_2D.png      | Bin 25526 -> 25761 bytes
 .../tests/baseline/test_histogram_3D.png      | Bin 20181 -> 20216 bytes
 5 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 2db2f08b..073620d1 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -20,6 +20,16 @@
 _COLORS = {"r": "tab:red", "g": "tab:green", "b": "tab:blue"}
 
 
+def _get_bins(data: npt.NDArray[Any]) -> npt.NDArray[Any]:
+    if data.dtype.kind in {"i", "u"}:
+        # Make sure integer data types have integer sized bins
+        step = np.ceil(np.ptp(data) / 100)
+        return np.arange(np.min(data), np.max(data) + step, step)
+    else:
+        # For other data types, just have 128 evenly spaced bins
+        return np.linspace(np.min(data), np.max(data), 100)
+
+
 class HistogramWidget(SingleAxesWidget):
     """
     Display a histogram of the currently selected layer.
@@ -70,13 +80,7 @@ def draw(self) -> None:
 
         # Important to calculate bins after slicing 3D data, to avoid reading
         # whole cube into memory.
-        if data.dtype.kind in {"i", "u"}:
-            # Make sure integer data types have integer sized bins
-            step = abs(np.max(data) - np.min(data)) // 100
-            step = max(1, step)
-            bins = np.arange(np.min(data), np.max(data) + step, step)
-        else:
-            bins = np.linspace(np.min(data), np.max(data), 100)
+        bins = _get_bins(data)
 
         if layer.rgb:
             # Histogram RGB channels independently
@@ -215,9 +219,9 @@ def draw(self) -> None:
         if data is None:
             return
 
-        _, bins, patches = self.axes.hist(
-            data, bins=50, edgecolor="white", linewidth=0.3
-        )
+        bins = _get_bins(data)
+
+        _, bins, patches = self.axes.hist(data, bins=bins.tolist())
         patches = cast(BarContainer, patches)
 
         # recolor the histogram plot
diff --git a/src/napari_matplotlib/tests/baseline/test_feature_histogram_points.png b/src/napari_matplotlib/tests/baseline/test_feature_histogram_points.png
index 74b84b2d565e293cb767ab9e4efea21e5825dbfa..be8770ee9229ae198086224cd76da3ce6c4df007 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_feature_histogram_vectors.png b/src/napari_matplotlib/tests/baseline/test_feature_histogram_vectors.png
index 4675192b1d444e04d83bdfa9c872508195775126..119c28e1269af57f8d8cff5c5da2e84a1ac306da 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_histogram_2D.png b/src/napari_matplotlib/tests/baseline/test_histogram_2D.png
index 00023ae722cf7317a8ef83b9cc9f7ce4fecf286d..b9096e4dbde29d967d423c8fd25fefa4e543614f 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_histogram_3D.png b/src/napari_matplotlib/tests/baseline/test_histogram_3D.png
index bd3cafb84784d97343f4bf9f1ef52ad4856a6d96..ec4ad96dde9bb1694ff6aca9dbd22710f1ac529f 100644
GIT binary patch

From c5e08861b18084217eb297bb042996ea46f44cc3 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Mon, 15 Jan 2024 15:59:34 +0000
Subject: [PATCH 145/238] Fix autosetting bins from data

---
 src/napari_matplotlib/histogram.py | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 348a506e..b0a04f14 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -135,7 +135,19 @@ def bins_num(self, num: int) -> None:
 
     def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None:
         """Update widgets with bins determined from the image data"""
-        bins = np.linspace(np.min(data), np.max(data), 100, dtype=data.dtype)
+        if data.dtype.kind in {"i", "u"}:
+            # Make sure integer data types have integer sized bins
+            # We can't use unsigned ints when calculating the step, otherwise
+            # the following warning is raised:
+            # 'RuntimeWarning: overflow encountered in scalar subtract'
+            step = (
+                abs(np.min(data).astype(int) - np.max(data).astype(int)) // 100
+            )
+            step = max(1, step)
+            bins = np.arange(np.min(data), np.max(data) + step, step)
+        else:
+            bins = np.linspace(np.min(data), np.max(data), 100)
+
         self.bins_start = bins[0]
         self.bins_stop = bins[-1]
         self.bins_num = bins.size

From 127d325d37133447dd9ba462c5a4a2f806ae86c4 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Mon, 15 Jan 2024 16:05:24 +0000
Subject: [PATCH 146/238] remove duplicate on_update_layers method

---
 src/napari_matplotlib/histogram.py | 84 ++++++++++++++----------------
 1 file changed, 40 insertions(+), 44 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index b0a04f14..4371ea9e 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -95,6 +95,26 @@ def on_update_layers(self) -> None:
         for layer in self.viewer.layers:
             layer.events.contrast_limits.connect(self._update_contrast_lims)
 
+        if not self.layers:
+            return
+
+        # Reset to bin start, stop and step
+        layer_data = self._get_layer_data(self.layers[0])
+        self.autoset_widget_bins(data=layer_data)
+
+        # Only allow integer bins for integer data
+        # And only allow values greater than 0 for unsigned integers
+        n_decimals = 0 if np.issubdtype(layer_data.dtype, np.integer) else 2
+        is_unsigned = layer_data.dtype.kind == "u"
+        minimum_value = 0 if is_unsigned else -1e10
+
+        bins_start = self.findChild(QDoubleSpinBox, name="bins start")
+        bins_stop = self.findChild(QDoubleSpinBox, name="bins stop")
+        bins_start.setDecimals(n_decimals)
+        bins_stop.setDecimals(n_decimals)
+        bins_start.setMinimum(minimum_value)
+        bins_stop.setMinimum(minimum_value)
+
     def _update_contrast_lims(self) -> None:
         for lim, line in zip(
             self.layers[0].contrast_limits, self._contrast_lines
@@ -103,6 +123,25 @@ def _update_contrast_lims(self) -> None:
 
         self.figure.canvas.draw()
 
+    def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None:
+        """Update widgets with bins determined from the image data"""
+
+        if data.dtype.kind in {"i", "u"}:
+            # Make sure integer data types have integer sized bins
+            # We can't use unsigned ints when calculating the step, otherwise
+            # the following warning is raised:
+            # 'RuntimeWarning: overflow encountered in scalar subtract'
+            step = abs(np.min(data).astype(int) - np.max(data).astype(int) // 100)
+            step = max(1, step)
+            bins = np.arange(np.min(data), np.max(data) + step, step)
+        else:
+            bins = np.linspace(np.min(data), np.max(data), 100)
+
+        self.bins_start = bins[0]
+        self.bins_stop = bins[-1]
+        self.bins_num = bins.size
+
+
     @property
     def bins_start(self) -> float:
         """Minimum bin edge"""
@@ -133,25 +172,6 @@ def bins_num(self, num: int) -> None:
         """Set the number of bins to use"""
         self.findChild(QSpinBox, name="bins num").setValue(num)
 
-    def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None:
-        """Update widgets with bins determined from the image data"""
-        if data.dtype.kind in {"i", "u"}:
-            # Make sure integer data types have integer sized bins
-            # We can't use unsigned ints when calculating the step, otherwise
-            # the following warning is raised:
-            # 'RuntimeWarning: overflow encountered in scalar subtract'
-            step = (
-                abs(np.min(data).astype(int) - np.max(data).astype(int)) // 100
-            )
-            step = max(1, step)
-            bins = np.arange(np.min(data), np.max(data) + step, step)
-        else:
-            bins = np.linspace(np.min(data), np.max(data), 100)
-
-        self.bins_start = bins[0]
-        self.bins_stop = bins[-1]
-        self.bins_num = bins.size
-
     def _get_layer_data(self, layer: napari.layers.Layer) -> npt.NDArray[Any]:
         """Get the data associated with a given layer"""
         if layer.data.ndim - layer.rgb == 3:
@@ -166,30 +186,6 @@ def _get_layer_data(self, layer: napari.layers.Layer) -> npt.NDArray[Any]:
 
         return data
 
-    def on_update_layers(self) -> None:
-        """
-        Called when the layer selection changes by ``self._update_layers()``.
-        """
-        if not self.layers:
-            return
-
-        # Reset to bin start, stop and step
-        layer_data = self._get_layer_data(self.layers[0])
-        self.autoset_widget_bins(data=layer_data)
-
-        # Only allow integer bins for integer data
-        # And only allow values greater than 0 for unsigned integers
-        n_decimals = 0 if np.issubdtype(layer_data.dtype, np.integer) else 2
-        is_unsigned = layer_data.dtype.kind == "u"
-        minimum_value = 0 if is_unsigned else -1e10
-
-        bins_start = self.findChild(QDoubleSpinBox, name="bins start")
-        bins_stop = self.findChild(QDoubleSpinBox, name="bins stop")
-        bins_start.setDecimals(n_decimals)
-        bins_stop.setDecimals(n_decimals)
-        bins_start.setMinimum(minimum_value)
-        bins_stop.setMinimum(minimum_value)
-
     def draw(self) -> None:
         """
         Clear the axes and histogram the currently selected layer/slice.
@@ -201,7 +197,7 @@ def draw(self) -> None:
         # whole cube into memory.
         if data.dtype.kind in {"i", "u"}:
             # Make sure integer data types have integer sized bins
-            step = (self.bins_start - self.bins_stop) // self.bins_num
+            step = abs((self.bins_start - self.bins_stop) // self.bins_num)
             step = max(1, step)
             bins = np.arange(self.bins_start, self.bins_stop + step, step)
         else:

From b8ffdb79a7c20fe0ceaf7b9a0dbd8174c52c63ec Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Mon, 15 Jan 2024 16:05:35 +0000
Subject: [PATCH 147/238] Make linters happy

---
 src/napari_matplotlib/histogram.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 4371ea9e..294e509e 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -125,13 +125,14 @@ def _update_contrast_lims(self) -> None:
 
     def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None:
         """Update widgets with bins determined from the image data"""
-
         if data.dtype.kind in {"i", "u"}:
             # Make sure integer data types have integer sized bins
             # We can't use unsigned ints when calculating the step, otherwise
             # the following warning is raised:
             # 'RuntimeWarning: overflow encountered in scalar subtract'
-            step = abs(np.min(data).astype(int) - np.max(data).astype(int) // 100)
+            step = abs(
+                np.min(data).astype(int) - np.max(data).astype(int) // 100
+            )
             step = max(1, step)
             bins = np.arange(np.min(data), np.max(data) + step, step)
         else:
@@ -141,7 +142,6 @@ def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None:
         self.bins_stop = bins[-1]
         self.bins_num = bins.size
 
-
     @property
     def bins_start(self) -> float:
         """Minimum bin edge"""

From 88760cf203f89df667bb3711a47e1a588626c5b9 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Mon, 15 Jan 2024 16:21:15 +0000
Subject: [PATCH 148/238] Add HistogramWidget._bin_widgets attribute for
 storing bin widgets

---
 src/napari_matplotlib/histogram.py | 46 ++++++++++++++++++++----------
 1 file changed, 31 insertions(+), 15 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 294e509e..b846af6e 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -42,7 +42,6 @@ def __init__(
 
         # Create widgets for setting bin parameters
         bins_start = QDoubleSpinBox()
-        bins_start.setObjectName("bins start")
         bins_start.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType)
         bins_start.setRange(-1e10, 1e10)
         bins_start.setValue(0)
@@ -51,7 +50,6 @@ def __init__(
         bins_start.setDecimals(2)
 
         bins_stop = QDoubleSpinBox()
-        bins_stop.setObjectName("bins stop")
         bins_stop.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType)
         bins_stop.setRange(-1e10, 1e10)
         bins_stop.setValue(100)
@@ -60,7 +58,6 @@ def __init__(
         bins_stop.setDecimals(2)
 
         bins_num = QSpinBox()
-        bins_num.setObjectName("bins num")
         bins_num.setRange(1, 100_000)
         bins_num.setValue(101)
         bins_num.setWrapping(False)
@@ -84,6 +81,13 @@ def __init__(
         bins_stop.valueChanged.connect(self._draw)
         bins_num.valueChanged.connect(self._draw)
 
+        # Store widgets for later usage
+        self._bin_widgets = {
+            "start": bins_start,
+            "stop": bins_stop,
+            "num": bins_num,
+        }
+
         self._update_layers(None)
         self.viewer.events.theme.connect(self._on_napari_theme_changed)
 
@@ -108,12 +112,17 @@ def on_update_layers(self) -> None:
         is_unsigned = layer_data.dtype.kind == "u"
         minimum_value = 0 if is_unsigned else -1e10
 
-        bins_start = self.findChild(QDoubleSpinBox, name="bins start")
-        bins_stop = self.findChild(QDoubleSpinBox, name="bins stop")
-        bins_start.setDecimals(n_decimals)
-        bins_stop.setDecimals(n_decimals)
-        bins_start.setMinimum(minimum_value)
-        bins_stop.setMinimum(minimum_value)
+        # Disable callbacks whilst widget values might change
+        for widget in self._bin_widgets.values():
+            widget.blockSignals(True)
+
+        self._bin_widgets["start"].setDecimals(n_decimals)
+        self._bin_widgets["stop"].setDecimals(n_decimals)
+        self._bin_widgets["start"].setMinimum(minimum_value)
+        self._bin_widgets["stop"].setMinimum(minimum_value)
+
+        for widget in self._bin_widgets.values():
+            widget.blockSignals(False)
 
     def _update_contrast_lims(self) -> None:
         for lim, line in zip(
@@ -138,39 +147,46 @@ def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None:
         else:
             bins = np.linspace(np.min(data), np.max(data), 100)
 
+        # Disable callbacks whilst setting widget values
+        for widget in self._bin_widgets.values():
+            widget.blockSignals(True)
+
         self.bins_start = bins[0]
         self.bins_stop = bins[-1]
         self.bins_num = bins.size
 
+        for widget in self._bin_widgets.values():
+            widget.blockSignals(False)
+
     @property
     def bins_start(self) -> float:
         """Minimum bin edge"""
-        return self.findChild(QDoubleSpinBox, name="bins start").value()
+        return self._bin_widgets["start"].value()
 
     @bins_start.setter
     def bins_start(self, start: Union[int, float]) -> None:
         """Set the minimum bin edge"""
-        self.findChild(QDoubleSpinBox, name="bins start").setValue(start)
+        self._bin_widgets["start"].setValue(start)
 
     @property
     def bins_stop(self) -> float:
         """Maximum bin edge"""
-        return self.findChild(QDoubleSpinBox, name="bins stop").value()
+        return self._bin_widgets["stop"].value()
 
     @bins_stop.setter
     def bins_stop(self, stop: Union[int, float]) -> None:
         """Set the maximum bin edge"""
-        self.findChild(QDoubleSpinBox, name="bins stop").setValue(stop)
+        self._bin_widgets["stop"].setValue(stop)
 
     @property
     def bins_num(self) -> int:
         """Number of bins to use"""
-        return self.findChild(QSpinBox, name="bins num").value()
+        return self._bin_widgets["num"].value()
 
     @bins_num.setter
     def bins_num(self, num: int) -> None:
         """Set the number of bins to use"""
-        self.findChild(QSpinBox, name="bins num").setValue(num)
+        self._bin_widgets["num"].setValue(num)
 
     def _get_layer_data(self, layer: napari.layers.Layer) -> npt.NDArray[Any]:
         """Get the data associated with a given layer"""

From d56942b72abd8e65f821ba07feb99ff6f0a25526 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Mon, 15 Jan 2024 16:33:50 +0000
Subject: [PATCH 149/238] Fix calculation of bins from widget values

---
 src/napari_matplotlib/histogram.py | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index b846af6e..3215b145 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -136,12 +136,7 @@ def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None:
         """Update widgets with bins determined from the image data"""
         if data.dtype.kind in {"i", "u"}:
             # Make sure integer data types have integer sized bins
-            # We can't use unsigned ints when calculating the step, otherwise
-            # the following warning is raised:
-            # 'RuntimeWarning: overflow encountered in scalar subtract'
-            step = abs(
-                np.min(data).astype(int) - np.max(data).astype(int) // 100
-            )
+            step = abs(np.max(data) - np.min(data)) // 100
             step = max(1, step)
             bins = np.arange(np.min(data), np.max(data) + step, step)
         else:
@@ -213,7 +208,7 @@ def draw(self) -> None:
         # whole cube into memory.
         if data.dtype.kind in {"i", "u"}:
             # Make sure integer data types have integer sized bins
-            step = abs((self.bins_start - self.bins_stop) // self.bins_num)
+            step = abs(self.bins_stop - self.bins_start) // self.bins_num
             step = max(1, step)
             bins = np.arange(self.bins_start, self.bins_stop + step, step)
         else:

From 11590b251846ab9b6023663bacb6d286c3e5a15f Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Mon, 15 Jan 2024 16:51:22 +0000
Subject: [PATCH 150/238] Calculate step using n_bins-1

n_bins corresponds to number of bin edges rather than bins
---
 src/napari_matplotlib/histogram.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 3215b145..4ce6ebc0 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -208,7 +208,7 @@ def draw(self) -> None:
         # whole cube into memory.
         if data.dtype.kind in {"i", "u"}:
             # Make sure integer data types have integer sized bins
-            step = abs(self.bins_stop - self.bins_start) // self.bins_num
+            step = abs(self.bins_stop - self.bins_start) // (self.bins_num - 1)
             step = max(1, step)
             bins = np.arange(self.bins_start, self.bins_stop + step, step)
         else:

From 08a5086f345e5db2418f2f4e1f5f4e5b169567a4 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Mon, 15 Jan 2024 16:52:16 +0000
Subject: [PATCH 151/238] cannot use negative start bin for uint data

---
 .../tests/baseline/test_histogram_2D_bins.png | Bin 21366 -> 19894 bytes
 src/napari_matplotlib/tests/test_histogram.py |   4 ++--
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/napari_matplotlib/tests/baseline/test_histogram_2D_bins.png b/src/napari_matplotlib/tests/baseline/test_histogram_2D_bins.png
index eb43c3108147079f9410a40936975d94158ac6f0..db401612c44fb7eb92dc1ae9f2fd66a7bbd69fd6 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py
index 58acf236..399db3d0 100644
--- a/src/napari_matplotlib/tests/test_histogram.py
+++ b/src/napari_matplotlib/tests/test_histogram.py
@@ -17,8 +17,8 @@ def test_histogram_2D_bins(make_napari_viewer, astronaut_data):
     viewer.add_image(astronaut_data[0], **astronaut_data[1])
     widget = HistogramWidget(viewer)
     viewer.window.add_dock_widget(widget)
-    widget.bins_start = -50
-    widget.bins_stop = 300
+    widget.bins_start = 0
+    widget.bins_stop = 350
     widget.bins_num = 35
     fig = widget.figure
     # Need to return a copy, as original figure is too eagerley garbage

From 65e84f8692e7ad42586304a643621b02dab95670 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Mon, 15 Jan 2024 16:56:00 +0000
Subject: [PATCH 152/238] Make HistogramWidget bins_num widget correspond to
 number of bins rather than number of bin edges

---
 src/napari_matplotlib/histogram.py | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 4ce6ebc0..728dfc84 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -148,7 +148,7 @@ def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None:
 
         self.bins_start = bins[0]
         self.bins_stop = bins[-1]
-        self.bins_num = bins.size
+        self.bins_num = bins.size - 1
 
         for widget in self._bin_widgets.values():
             widget.blockSignals(False)
@@ -208,11 +208,13 @@ def draw(self) -> None:
         # whole cube into memory.
         if data.dtype.kind in {"i", "u"}:
             # Make sure integer data types have integer sized bins
-            step = abs(self.bins_stop - self.bins_start) // (self.bins_num - 1)
+            step = abs(self.bins_stop - self.bins_start) // (self.bins_num)
             step = max(1, step)
             bins = np.arange(self.bins_start, self.bins_stop + step, step)
         else:
-            bins = np.linspace(self.bins_start, self.bins_stop, self.bins_num)
+            bins = np.linspace(
+                self.bins_start, self.bins_stop, self.bins_num + 1
+            )
 
         if layer.rgb:
             # Histogram RGB channels independently

From 8a7d9e46da49492172df8f36e0f579c995407a65 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Mon, 15 Jan 2024 17:07:02 +0000
Subject: [PATCH 153/238] fix typo in comment about using 128 bins for float
 data

---
 src/napari_matplotlib/histogram.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 2329c1a2..39bcfa48 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -31,7 +31,7 @@ def _get_bins(data: npt.NDArray[Any]) -> npt.NDArray[Any]:
         step = np.ceil(np.ptp(data) / 100)
         return np.arange(np.min(data), np.max(data) + step, step)
     else:
-        # For other data types, just have 128 evenly spaced bins
+        # For other data types, just have 99 evenly spaced bins
         return np.linspace(np.min(data), np.max(data), 100)
 
 

From 7c4cdc87328480e53bd07847e79fcf27b9fef697 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Mon, 15 Jan 2024 17:10:07 +0000
Subject: [PATCH 154/238] Update changelog

---
 docs/changelog.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 673a0efb..f4caaf38 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -20,6 +20,7 @@ Other changes
 - The ``HistogramWidget`` now has two vertical lines showing the contrast limits used
   to render the selected layer in the main napari window.
 - Added an example gallery for the ``FeaturesHistogramWidget``.
+- Add widgets for setting bin parameters for ``HistogramWidget``.
 
 1.2.0
 -----
@@ -28,7 +29,6 @@ Changes
 - Dropped support for Python 3.8, and added support for Python 3.11.
 - Histogram plots of points and vector layers are now coloured with their napari colourmap.
 - Added support for Matplotlib 3.8
-- Add widgets for setting histogram bin parameters
 
 1.1.0
 -----

From 109418e4557e3b84cfe74602c6329c18047fb19f Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Mon, 15 Jan 2024 19:12:59 +0000
Subject: [PATCH 155/238] Use 101 bin edges for histograms of float data

---
 src/napari_matplotlib/histogram.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 073620d1..8269d985 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -26,8 +26,9 @@ def _get_bins(data: npt.NDArray[Any]) -> npt.NDArray[Any]:
         step = np.ceil(np.ptp(data) / 100)
         return np.arange(np.min(data), np.max(data) + step, step)
     else:
-        # For other data types, just have 128 evenly spaced bins
-        return np.linspace(np.min(data), np.max(data), 100)
+        # For other data types, just have 100 evenly spaced bins
+        # (and 101 bin edges)
+        return np.linspace(np.min(data), np.max(data), 101)
 
 
 class HistogramWidget(SingleAxesWidget):

From 101b4e5fe0e473f7440aa32879011d71036c5379 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Mon, 15 Jan 2024 19:16:08 +0000
Subject: [PATCH 156/238] update baseline images for histogram tests with float
 data

---
 .../test_feature_histogram_points.png         | Bin 17145 -> 17200 bytes
 .../test_feature_histogram_vectors.png        | Bin 17259 -> 17725 bytes
 2 files changed, 0 insertions(+), 0 deletions(-)

diff --git a/src/napari_matplotlib/tests/baseline/test_feature_histogram_points.png b/src/napari_matplotlib/tests/baseline/test_feature_histogram_points.png
index be8770ee9229ae198086224cd76da3ce6c4df007..88a28f79c8b07efca0319bd565bc306ba4a35c37 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/baseline/test_feature_histogram_vectors.png b/src/napari_matplotlib/tests/baseline/test_feature_histogram_vectors.png
index 119c28e1269af57f8d8cff5c5da2e84a1ac306da..857d93449651193b77014826950c8a3d61e608d4 100644
GIT binary patch

From ae412cb24421c9b18e9a1b92317844bdfa191290 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 22 Jan 2024 17:29:12 +0000
Subject: [PATCH 157/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.1.11 → v0.1.14](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.11...v0.1.14)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8df635ab..9f843050 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.1.11'
+    rev: 'v0.1.14'
     hooks:
       - id: ruff
 

From 34c9e2c3226370717c2f86703a21154dd56d8607 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Tue, 23 Jan 2024 18:33:34 +0000
Subject: [PATCH 158/238] Fix HistogramWidget for multiscale data

---
 src/napari_matplotlib/histogram.py | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 073620d1..ffbd1fff 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -4,6 +4,8 @@
 import numpy as np
 import numpy.typing as npt
 from matplotlib.container import BarContainer
+from napari.layers import Image
+from napari.layers._multiscale_data import MultiScaleData
 from qtpy.QtWidgets import (
     QComboBox,
     QLabel,
@@ -67,14 +69,18 @@ def draw(self) -> None:
         """
         Clear the axes and histogram the currently selected layer/slice.
         """
-        layer = self.layers[0]
+        layer: Image = self.layers[0]
+        data = layer.data
 
-        if layer.data.ndim - layer.rgb == 3:
+        if isinstance(layer.data, MultiScaleData):
+            data = data[layer.data_level]
+
+        if layer.ndim - layer.rgb == 3:
             # 3D data, can be single channel or RGB
-            data = layer.data[self.current_z]
+            # Slice in z dimension
+            data = data[self.current_z]
             self.axes.set_title(f"z={self.current_z}")
-        else:
-            data = layer.data
+
         # Read data into memory if it's a dask array
         data = np.asarray(data)
 

From 31c8fac9bacb87c34a6dffa46781ac7849be1c70 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Tue, 23 Jan 2024 18:39:17 +0000
Subject: [PATCH 159/238] Fix pandas deprecation warning

---
 pyproject.toml | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 22ff307f..ba9f9e66 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,14 +6,15 @@ build-backend = "setuptools.build_meta"
 write_to = "src/napari_matplotlib/_version.py"
 
 [tool.pytest.ini_options]
-qt_api = "pyqt6"
-addopts = "--mpl --mpl-baseline-relative"
 filterwarnings = [
     "error",
+    "ignore:(?s).*Pyarrow will become a required dependency of pandas",
     # Coming from vispy
     "ignore:distutils Version classes are deprecated:DeprecationWarning",
     "ignore:`np.bool8` is a deprecated alias for `np.bool_`:DeprecationWarning",
 ]
+qt_api = "pyqt6"
+addopts = "--mpl --mpl-baseline-relative"
 
 [tool.black]
 line-length = 79

From c3c4dbd2774da51fbb9a8a072363201129bce3df Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Tue, 23 Jan 2024 18:47:48 +0000
Subject: [PATCH 160/238] Add 2.0.1 changelog

---
 docs/changelog.rst | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 6395e9c1..255982a3 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,5 +1,13 @@
 Changelog
 =========
+2.0.1
+-----
+Bug fixes
+~~~~~~~~~
+- Fixed using the ``HistogramWidget`` with layers containing multiscale data.
+- Make sure ``HistogramWidget`` uses 100 bins (not 99) when floating point data is
+  selected.
+
 2.0.0
 -----
 Changes to custom theming

From 266f4849136a4b11be6d4b27dffb886058e3cf2e Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 5 Feb 2024 17:32:20 +0000
Subject: [PATCH 161/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/psf/black: 23.12.1 → 24.1.1](https://github.com/psf/black/compare/23.12.1...24.1.1)
- [github.com/astral-sh/ruff-pre-commit: v0.1.14 → v0.2.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.14...v0.2.0)
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 9f843050..5bff1036 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -7,7 +7,7 @@ repos:
       - id: trailing-whitespace
 
   - repo: https://github.com/psf/black
-    rev: 23.12.1
+    rev: 24.1.1
     hooks:
       - id: black
 
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.1.14'
+    rev: 'v0.2.0'
     hooks:
       - id: ruff
 

From 0efd2b2c03e3dcc1cd455489af5a1fc813527935 Mon Sep 17 00:00:00 2001
From: Sam Cunliffe <s.cunliffe@ucl.ac.uk>
Date: Tue, 6 Feb 2024 19:31:50 +0000
Subject: [PATCH 162/238] Expecting two warnings so nested with pytest.warns.

---
 src/napari_matplotlib/tests/test_util.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/tests/test_util.py b/src/napari_matplotlib/tests/test_util.py
index a8792d41..918e7633 100644
--- a/src/napari_matplotlib/tests/test_util.py
+++ b/src/napari_matplotlib/tests/test_util.py
@@ -69,7 +69,10 @@ def test_fallback_if_missing_dimensions(mocker):
     test_css = " Flobble { background-color: rgb(0, 97, 163); } "
     mocker.patch("napari.qt.get_current_stylesheet").return_value = test_css
     with pytest.warns(RuntimeWarning, match="Unable to find DimensionToken"):
-        assert from_napari_css_get_size_of("Flobble", (1, 2)) == QSize(1, 2)
+        with pytest.warns(RuntimeWarning, match="Unable to find Flobble"):
+            assert from_napari_css_get_size_of("Flobble", (1, 2)) == QSize(
+                1, 2
+            )
 
 
 def test_fallback_if_prelude_not_in_css():

From cd429ee947b9e8dc949851bab0ef106d157b0531 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Wed, 7 Feb 2024 09:45:06 +0000
Subject: [PATCH 163/238] [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
---
 examples/histogram.py | 1 +
 examples/scatter.py   | 1 +
 examples/slice.py     | 1 +
 3 files changed, 3 insertions(+)

diff --git a/examples/histogram.py b/examples/histogram.py
index ccda491a..b9ceb377 100644
--- a/examples/histogram.py
+++ b/examples/histogram.py
@@ -2,6 +2,7 @@
 Histograms
 ==========
 """
+
 import napari
 
 viewer = napari.Viewer()
diff --git a/examples/scatter.py b/examples/scatter.py
index cd812401..00e01ec9 100644
--- a/examples/scatter.py
+++ b/examples/scatter.py
@@ -2,6 +2,7 @@
 Scatter plots
 =============
 """
+
 import napari
 
 viewer = napari.Viewer()
diff --git a/examples/slice.py b/examples/slice.py
index 3e43443e..242a16cc 100644
--- a/examples/slice.py
+++ b/examples/slice.py
@@ -2,6 +2,7 @@
 1D slices
 =========
 """
+
 import napari
 
 viewer = napari.Viewer()

From 07217c621302d644a8796e878cf70bfdde2f2164 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 12 Feb 2024 17:33:44 +0000
Subject: [PATCH 164/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.2.0 → v0.2.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.0...v0.2.1)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5bff1036..f97ded25 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.2.0'
+    rev: 'v0.2.1'
     hooks:
       - id: ruff
 

From 6261f4c36fb531146cb270f3ad2bf9fc5d62ac1c Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Wed, 14 Feb 2024 10:54:40 +0000
Subject: [PATCH 165/238] Add 'num_bins, 'start', and 'stop' parameters to
 '_get_bins'

---
 src/napari_matplotlib/histogram.py | 56 ++++++++++++++++++++++--------
 1 file changed, 41 insertions(+), 15 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index bec5c61f..50360710 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -27,15 +27,44 @@
 _COLORS = {"r": "tab:red", "g": "tab:green", "b": "tab:blue"}
 
 
-def _get_bins(data: npt.NDArray[Any]) -> npt.NDArray[Any]:
+def _get_bins(
+    data: npt.NDArray[Any],
+    num_bins: int = 100,
+    start: Optional[Union[int, float]] = None,
+    stop: Optional[Union[int, float]] = None,
+) -> npt.NDArray[Any]:
+    """Create evenly spaced bins with a given interval.
+
+    If `start` or `stop` are `None`, they will be set based on the minimum
+    and maximum values, respectively, of the data.
+
+    Parameters
+    ----------
+    data : napari.layers.Layer.data
+        Napari layer data.
+    num_bins : integer, optional
+        Number of evenly-spaced bins to create.
+    start : integer or real, optional
+        Start bin edge. Defaults to the minimum value of `data`.
+    stop : integer or real, optional
+        Stop bin edge. Defaults to the maximum value of `data`.
+
+    Returns
+    -------
+    bin_edges : numpy.ndarray
+        Array of evenly spaced bin edges.
+    """
+    start = np.min(data) if start is None else start
+    stop = np.max(data) if stop is None else stop
+
     if data.dtype.kind in {"i", "u"}:
         # Make sure integer data types have integer sized bins
-        step = np.ceil(np.ptp(data) / 100)
-        return np.arange(np.min(data), np.max(data) + step, step)
+        step = np.ceil((stop - start) / num_bins)
+        return np.arange(start, stop + step, step)
     else:
-        # For other data types, just have 100 evenly spaced bins
-        # (and 101 bin edges)
-        return np.linspace(np.min(data), np.max(data), 101)
+        # For other data types we can use exactly `num_bins` bins
+        # (and `num_bins` + 1 bin edges)
+        return np.linspace(start, stop, num_bins + 1)
 
 
 class HistogramWidget(SingleAxesWidget):
@@ -217,15 +246,12 @@ def draw(self) -> None:
 
         # Important to calculate bins after slicing 3D data, to avoid reading
         # whole cube into memory.
-        if data.dtype.kind in {"i", "u"}:
-            # Make sure integer data types have integer sized bins
-            step = abs(self.bins_stop - self.bins_start) // (self.bins_num)
-            step = max(1, step)
-            bins = np.arange(self.bins_start, self.bins_stop + step, step)
-        else:
-            bins = np.linspace(
-                self.bins_start, self.bins_stop, self.bins_num + 1
-            )
+        bins = _get_bins(
+            data,
+            num_bins=self.bins_num,
+            start=self.bins_start,
+            stop=self.bins_stop,
+        )
 
         if layer.rgb:
             # Histogram RGB channels independently

From 46058eea210c3b7c532d0e857f05592e15568de5 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 19 Feb 2024 17:43:22 +0000
Subject: [PATCH 166/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/psf/black: 24.1.1 → 24.2.0](https://github.com/psf/black/compare/24.1.1...24.2.0)
- [github.com/astral-sh/ruff-pre-commit: v0.2.1 → v0.2.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.1...v0.2.2)
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index f97ded25..e57a9c82 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -7,7 +7,7 @@ repos:
       - id: trailing-whitespace
 
   - repo: https://github.com/psf/black
-    rev: 24.1.1
+    rev: 24.2.0
     hooks:
       - id: black
 
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.2.1'
+    rev: 'v0.2.2'
     hooks:
       - id: ruff
 

From 517b2139ccfe59a4cc486467692a0e62bb9839aa Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 3 May 2024 15:21:40 +0100
Subject: [PATCH 167/238] Update pre-commit

---
 .pre-commit-config.yaml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e57a9c82..40048ea4 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,13 +1,13 @@
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.5.0
+    rev: v4.6.0
     hooks:
       - id: check-docstring-first
       - id: end-of-file-fixer
       - id: trailing-whitespace
 
   - repo: https://github.com/psf/black
-    rev: 24.2.0
+    rev: 24.4.2
     hooks:
       - id: black
 
@@ -17,14 +17,14 @@ repos:
       - id: napari-plugin-checks
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.8.0
+    rev: v1.10.0
     hooks:
      - id: mypy
        additional_dependencies: [numpy, matplotlib]
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.2.2'
+    rev: 'v0.4.2'
     hooks:
       - id: ruff
 

From 9949427de3b7212931e2601baabf2040c7cbfe22 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 3 May 2024 15:36:11 +0100
Subject: [PATCH 168/238] Bump codecov action

---
 .github/workflows/test_and_deploy.yml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml
index 8665e1d2..9b24e7f4 100644
--- a/.github/workflows/test_and_deploy.yml
+++ b/.github/workflows/test_and_deploy.yml
@@ -58,7 +58,7 @@ jobs:
         if: ${{ always() }}
 
       - name: Coverage
-        uses: codecov/codecov-action@v3
+        uses: codecov/codecov-action@v4
         # Don't run coverage on merge queue CI to avoid duplicating reports
         # to codecov. See https://github.com/matplotlib/napari-matplotlib/issues/155
         if: github.event_name != 'merge_group'
@@ -66,6 +66,8 @@ jobs:
           token: ${{ secrets.CODECOV_TOKEN }}
           fail_ci_if_error: true
 
+
+
   deploy:
     # this will run when you have tagged a commit, starting with "v*"
     # and requires that you have put your twine API key in your

From ea21e54a0c30ceafb3b7c1bdbf7e9ab77f8629c7 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 3 May 2024 15:47:12 +0100
Subject: [PATCH 169/238] Suppress doc build warning

---
 docs/conf.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/docs/conf.py b/docs/conf.py
index 2517a59c..5be3c950 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -33,6 +33,7 @@
     "sphinx_automodapi.automodapi",
     "sphinx_automodapi.smart_resolver",
     "sphinx.ext.intersphinx",
+    "qtgallery",
 ]
 
 sphinx_gallery_conf = {
@@ -40,6 +41,7 @@
     "image_scrapers": (qtgallery.qtscraper,),
     "reset_modules": (qtgallery.reset_qapp,),
 }
+suppress_warnings = ["config.cache"]
 
 qtgallery_conf = {
     "xvfb_size": (640, 480),

From 2645f43d139202da084aba3cecaa5b19f762093b Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 3 May 2024 15:54:05 +0100
Subject: [PATCH 170/238] Remove napari hub preview

---
 .github/workflows/napari_hub_preview.yml | 21 ---------------------
 1 file changed, 21 deletions(-)
 delete mode 100644 .github/workflows/napari_hub_preview.yml

diff --git a/.github/workflows/napari_hub_preview.yml b/.github/workflows/napari_hub_preview.yml
deleted file mode 100644
index c204ac45..00000000
--- a/.github/workflows/napari_hub_preview.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-name: napari hub Preview Page # we use this name to find your preview page artifact, so don't change it!
-# For more info on this action, see https://github.com/chanzuckerberg/napari-hub-preview-action/blob/main/action.yml
-
-on:
-  pull_request:
-    types: [ labeled ]
-
-jobs:
-  preview-page:
-    if: ${{ github.event.label.name == 'napari hub preview' }}
-    name: Preview Page Deploy
-    runs-on: ubuntu-latest
-
-    steps:
-      - name: Checkout repo
-        uses: actions/checkout@v3
-
-      - name: napari hub Preview Page Builder
-        uses: chanzuckerberg/napari-hub-preview-action@v0.1
-        with:
-          hub-ref: main

From e5b9bb2b5bf5b00aaf0fcbf5598a235b08fd9c83 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 3 May 2024 15:54:42 +0100
Subject: [PATCH 171/238] Update headless gui version

---
 .github/workflows/docs.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 5b721d92..39c1ab51 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -32,7 +32,7 @@ jobs:
           sudo apt install graphviz --yes
 
       - name: Build Docs
-        uses: aganders3/headless-gui@v1
+        uses: aganders3/headless-gui@v2
         with:
           run: make html
           working-directory: ./docs

From 8cc74cab748cc0da54e6c08ea52d8d0a119fee79 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 3 May 2024 16:01:03 +0100
Subject: [PATCH 172/238] Try stable release of napari for doc build

---
 setup.cfg | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.cfg b/setup.cfg
index 41e4e34f..25aa4cf8 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -47,7 +47,7 @@ napari.manifest =
 
 [options.extras_require]
 docs =
-    napari[all]==0.4.19rc3
+    napari[all]==0.4.19
     numpydoc
     pydantic<2
     pydata-sphinx-theme

From 0be3f7b70c60fd8cd44220ab4f1c6bfe24722770 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 3 May 2024 16:10:26 +0100
Subject: [PATCH 173/238] Remoe doc napari pin

---
 setup.cfg | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.cfg b/setup.cfg
index 25aa4cf8..cccec1c5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -47,7 +47,7 @@ napari.manifest =
 
 [options.extras_require]
 docs =
-    napari[all]==0.4.19
+    napari[all]
     numpydoc
     pydantic<2
     pydata-sphinx-theme

From 797e4605e99e2773f9ebcbe5ddd0f687e316fb6e Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 24 May 2024 14:00:28 +0100
Subject: [PATCH 174/238] Try custom scrapers

---
 docs/conf.py | 57 +++++++++++++++++++++++++++++++++++++++++++---------
 setup.cfg    |  1 -
 2 files changed, 47 insertions(+), 11 deletions(-)

diff --git a/docs/conf.py b/docs/conf.py
index 5be3c950..3c6ce6fb 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -13,7 +13,7 @@
 # import os
 # import sys
 # sys.path.insert(0, os.path.abspath('.'))
-import qtgallery
+from sphinx_gallery import scrapers
 
 # -- Project information -----------------------------------------------------
 
@@ -33,22 +33,59 @@
     "sphinx_automodapi.automodapi",
     "sphinx_automodapi.smart_resolver",
     "sphinx.ext.intersphinx",
-    "qtgallery",
 ]
 
+
+def reset_napari(gallery_conf, fname):
+    from napari.settings import get_settings
+    from qtpy.QtWidgets import QApplication
+
+    settings = get_settings()
+    settings.appearance.theme = "dark"
+
+    # Disabling `QApplication.exec_` means example scripts can call `exec_`
+    # (scripts work when run normally) without blocking example execution by
+    # sphinx-gallery. (from qtgallery)
+    QApplication.exec_ = lambda _: None
+
+
+def napari_scraper(block, block_vars, gallery_conf):
+    """Basic napari window scraper.
+
+    Looks for any QtMainWindow instances and takes a screenshot of them.
+
+    `app.processEvents()` allows Qt events to propagateo and prevents hanging.
+    """
+    import napari
+
+    imgpath_iter = block_vars["image_path_iterator"]
+
+    if app := napari.qt.get_app():
+        app.processEvents()
+    else:
+        return ""
+
+    img_paths = []
+    for win, img_path in zip(
+        reversed(napari._qt.qt_main_window._QtMainWindow._instances),
+        imgpath_iter,
+    ):
+        img_paths.append(img_path)
+        win._window.screenshot(img_path, canvas_only=False)
+
+    napari.Viewer.close_all()
+    app.processEvents()
+
+    return scrapers.figure_rst(img_paths, gallery_conf["src_dir"])
+
+
 sphinx_gallery_conf = {
     "filename_pattern": ".",
-    "image_scrapers": (qtgallery.qtscraper,),
-    "reset_modules": (qtgallery.reset_qapp,),
+    "image_scrapers": (napari_scraper,),
+    "reset_modules": (reset_napari,),
 }
 suppress_warnings = ["config.cache"]
 
-qtgallery_conf = {
-    "xvfb_size": (640, 480),
-    "xvfb_color_depth": 24,
-    "xfvb_use_xauth": False,
-    "xfvb_extra_args": [],
-}
 
 numpydoc_show_class_members = False
 automodapi_inheritance_diagram = True
diff --git a/setup.cfg b/setup.cfg
index cccec1c5..76fc8bf3 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -51,7 +51,6 @@ docs =
     numpydoc
     pydantic<2
     pydata-sphinx-theme
-    qtgallery
     sphinx
     sphinx-automodapi
     sphinx-gallery

From 312d50c03bd9e4df775c6baa17d728affe37658b Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 24 May 2024 14:03:57 +0100
Subject: [PATCH 175/238] Add some type ignores

---
 docs/conf.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/conf.py b/docs/conf.py
index 3c6ce6fb..2818f02e 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -36,7 +36,7 @@
 ]
 
 
-def reset_napari(gallery_conf, fname):
+def reset_napari(gallery_conf, fname):  # type: ignore[no-untyped-def]
     from napari.settings import get_settings
     from qtpy.QtWidgets import QApplication
 
@@ -49,7 +49,7 @@ def reset_napari(gallery_conf, fname):
     QApplication.exec_ = lambda _: None
 
 
-def napari_scraper(block, block_vars, gallery_conf):
+def napari_scraper(block, block_vars, gallery_conf):  # type: ignore[no-untyped-def]
     """Basic napari window scraper.
 
     Looks for any QtMainWindow instances and takes a screenshot of them.

From 5c80464405f4c5205628805911c33ef9bf0e1226 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 3 May 2024 15:27:26 +0100
Subject: [PATCH 176/238] Adhere to SPEC0

---
 .github/workflows/test_and_deploy.yml | 2 +-
 docs/changelog.rst                    | 9 +++++++++
 pyproject.toml                        | 4 ++--
 setup.cfg                             | 4 ++--
 tox.ini                               | 4 ++--
 5 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml
index 9b24e7f4..94cf1e95 100644
--- a/.github/workflows/test_and_deploy.yml
+++ b/.github/workflows/test_and_deploy.yml
@@ -20,7 +20,7 @@ jobs:
       fail-fast: false
       matrix:
         platform: [ubuntu-latest, macos-latest, windows-latest]
-        python-version: ['3.9', '3.10', '3.11']
+        python-version: ['3.10', '3.11', '3.12']
 
     steps:
       - uses: actions/checkout@v3
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 255982a3..8e3e8598 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,5 +1,14 @@
 Changelog
 =========
+2.0.2
+-----
+Dependencies
+~~~~~~~~~~~~
+napari-matplotlib now adheres to `SPEC 0<https://scientific-python.org/specs/spec-0000/>`_, and has:
+- Dropped support for Python 3.9
+- Added support for Python 3.12
+- Added a minimum required numpy verison of 1.23
+
 2.0.1
 -----
 Bug fixes
diff --git a/pyproject.toml b/pyproject.toml
index ba9f9e66..d5691133 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -24,7 +24,7 @@ profile = "black"
 line_length = 79
 
 [tool.ruff]
-target-version = "py39"
+target-version = "py310"
 select = ["I", "UP", "F", "E", "W", "D"]
 ignore = [
     "D100", # Missing docstring in public module
@@ -46,7 +46,7 @@ fix = true
 convention = "numpy"
 
 [tool.mypy]
-python_version = "3.9"
+python_version = "3.10"
 # Block below are checks that form part of mypy 'strict' mode
 strict = true
 disallow_subclassing_any = false # TODO: fix
diff --git a/setup.cfg b/setup.cfg
index 76fc8bf3..2b745044 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -29,9 +29,9 @@ packages = find:
 install_requires =
     matplotlib
     napari
-    numpy
+    numpy>=1.23
     tinycss2
-python_requires = >=3.9
+python_requires = >=3.10
 include_package_data = True
 package_dir =
     =src
diff --git a/tox.ini b/tox.ini
index 4ec0c702..f4aed6a8 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,12 +1,12 @@
 [tox]
-envlist = py{39,310,311}
+envlist = py{310,311,312}
 isolated_build = true
 
 [gh-actions]
 python =
-    3.9: py39
     3.10: py310
     3.11: py311
+    3.12: py312
 
 [testenv]
 extras = testing

From 891f4aa263e1d08dccd22e27f7b00941cf076268 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 24 May 2024 14:17:39 +0100
Subject: [PATCH 177/238] Ignore pydantic warning

---
 pyproject.toml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/pyproject.toml b/pyproject.toml
index d5691133..bbec4096 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,6 +12,8 @@ filterwarnings = [
     # Coming from vispy
     "ignore:distutils Version classes are deprecated:DeprecationWarning",
     "ignore:`np.bool8` is a deprecated alias for `np.bool_`:DeprecationWarning",
+    # Coming from pydantic via napari
+    "ignore:Pickle, copy, and deepcopy support will be removed from itertools in Python 3.14.:DeprecationWarning"
 ]
 qt_api = "pyqt6"
 addopts = "--mpl --mpl-baseline-relative"

From 52bb0a902bc4c427f159a7ab4e4f2b969d33169b Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 24 May 2024 14:40:06 +0100
Subject: [PATCH 178/238] pre-commit fixes

---
 src/napari_matplotlib/base.py      |  7 +++----
 src/napari_matplotlib/histogram.py | 12 ++++++------
 src/napari_matplotlib/scatter.py   |  8 ++++----
 src/napari_matplotlib/slice.py     |  4 ++--
 src/napari_matplotlib/util.py      |  9 +++------
 5 files changed, 18 insertions(+), 22 deletions(-)

diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py
index fb9e485c..c455335e 100644
--- a/src/napari_matplotlib/base.py
+++ b/src/napari_matplotlib/base.py
@@ -1,6 +1,5 @@
 import os
 from pathlib import Path
-from typing import Optional
 
 import matplotlib.style as mplstyle
 import napari
@@ -38,7 +37,7 @@ class BaseNapariMPLWidget(QWidget):
     def __init__(
         self,
         napari_viewer: napari.Viewer,
-        parent: Optional[QWidget] = None,
+        parent: QWidget | None = None,
     ):
         super().__init__(parent=parent)
         self.viewer = napari_viewer
@@ -173,7 +172,7 @@ class NapariMPLWidget(BaseNapariMPLWidget):
     def __init__(
         self,
         napari_viewer: napari.viewer.Viewer,
-        parent: Optional[QWidget] = None,
+        parent: QWidget | None = None,
     ):
         super().__init__(napari_viewer=napari_viewer, parent=parent)
         self._setup_callbacks()
@@ -282,7 +281,7 @@ class SingleAxesWidget(NapariMPLWidget):
     def __init__(
         self,
         napari_viewer: napari.viewer.Viewer,
-        parent: Optional[QWidget] = None,
+        parent: QWidget | None = None,
     ):
         super().__init__(napari_viewer=napari_viewer, parent=parent)
         self.add_single_axes()
diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 40765287..a0b6e09f 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -1,4 +1,4 @@
-from typing import Any, Optional, cast
+from typing import Any, cast
 
 import napari
 import numpy as np
@@ -44,7 +44,7 @@ class HistogramWidget(SingleAxesWidget):
     def __init__(
         self,
         napari_viewer: napari.viewer.Viewer,
-        parent: Optional[QWidget] = None,
+        parent: QWidget | None = None,
     ):
         super().__init__(napari_viewer, parent=parent)
         self._update_layers(None)
@@ -121,7 +121,7 @@ class FeaturesHistogramWidget(SingleAxesWidget):
     def __init__(
         self,
         napari_viewer: napari.viewer.Viewer,
-        parent: Optional[QWidget] = None,
+        parent: QWidget | None = None,
     ):
         super().__init__(napari_viewer, parent=parent)
 
@@ -137,12 +137,12 @@ def __init__(
         self._update_layers(None)
 
     @property
-    def x_axis_key(self) -> Optional[str]:
+    def x_axis_key(self) -> str | None:
         """Key to access x axis data from the FeaturesTable"""
         return self._x_axis_key
 
     @x_axis_key.setter
-    def x_axis_key(self, key: Optional[str]) -> None:
+    def x_axis_key(self, key: str | None) -> None:
         self._x_axis_key = key
         self._draw()
 
@@ -166,7 +166,7 @@ def _get_valid_axis_keys(self) -> list[str]:
         else:
             return self.layers[0].features.keys()
 
-    def _get_data(self) -> tuple[Optional[npt.NDArray[Any]], str]:
+    def _get_data(self) -> tuple[npt.NDArray[Any] | None, str]:
         """Get the plot data.
 
         Returns
diff --git a/src/napari_matplotlib/scatter.py b/src/napari_matplotlib/scatter.py
index 67d6896c..98ebe928 100644
--- a/src/napari_matplotlib/scatter.py
+++ b/src/napari_matplotlib/scatter.py
@@ -1,4 +1,4 @@
-from typing import Any, Optional, Union
+from typing import Any
 
 import napari
 import numpy.typing as npt
@@ -100,7 +100,7 @@ class FeaturesScatterWidget(ScatterBaseWidget):
     def __init__(
         self,
         napari_viewer: napari.viewer.Viewer,
-        parent: Optional[QWidget] = None,
+        parent: QWidget | None = None,
     ):
         super().__init__(napari_viewer, parent=parent)
 
@@ -118,7 +118,7 @@ def __init__(
         self._update_layers(None)
 
     @property
-    def x_axis_key(self) -> Union[str, None]:
+    def x_axis_key(self) -> str | None:
         """
         Key for the x-axis data.
         """
@@ -133,7 +133,7 @@ def x_axis_key(self, key: str) -> None:
         self._draw()
 
     @property
-    def y_axis_key(self) -> Union[str, None]:
+    def y_axis_key(self) -> str | None:
         """
         Key for the y-axis data.
         """
diff --git a/src/napari_matplotlib/slice.py b/src/napari_matplotlib/slice.py
index 9459fa97..1924bf2b 100644
--- a/src/napari_matplotlib/slice.py
+++ b/src/napari_matplotlib/slice.py
@@ -1,4 +1,4 @@
-from typing import Any, Optional
+from typing import Any
 
 import matplotlib.ticker as mticker
 import napari
@@ -30,7 +30,7 @@ class SliceWidget(SingleAxesWidget):
     def __init__(
         self,
         napari_viewer: napari.viewer.Viewer,
-        parent: Optional[QWidget] = None,
+        parent: QWidget | None = None,
     ):
         # Setup figure/axes
         super().__init__(napari_viewer, parent=parent)
diff --git a/src/napari_matplotlib/util.py b/src/napari_matplotlib/util.py
index ed994256..c54b60a7 100644
--- a/src/napari_matplotlib/util.py
+++ b/src/napari_matplotlib/util.py
@@ -1,4 +1,3 @@
-from typing import Optional, Union
 from warnings import warn
 
 import napari.qt
@@ -12,7 +11,7 @@ class Interval:
     An integer interval.
     """
 
-    def __init__(self, lower_bound: Optional[int], upper_bound: Optional[int]):
+    def __init__(self, lower_bound: int | None, upper_bound: int | None):
         """
         Parameters
         ----------
@@ -48,7 +47,7 @@ def __contains__(self, val: int) -> bool:
         return True
 
     @property
-    def _helper_text(self) -> Optional[str]:
+    def _helper_text(self) -> str | None:
         """
         Helper text for widgets.
         """
@@ -86,9 +85,7 @@ def _has_id(nodes: list[tinycss2.ast.Node], id_name: str) -> bool:
     )
 
 
-def _get_dimension(
-    nodes: list[tinycss2.ast.Node], id_name: str
-) -> Union[int, None]:
+def _get_dimension(nodes: list[tinycss2.ast.Node], id_name: str) -> int | None:
     """
     Get the value of the DimensionToken for the IdentToken `id_name`.
 

From e7c49451e5e1b25ccef43ddba054ad4a23328e99 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 24 May 2024 14:43:04 +0100
Subject: [PATCH 179/238] Fix doc link

---
 docs/changelog.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 8e3e8598..ff2e60cf 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -4,7 +4,7 @@ Changelog
 -----
 Dependencies
 ~~~~~~~~~~~~
-napari-matplotlib now adheres to `SPEC 0<https://scientific-python.org/specs/spec-0000/>`_, and has:
+napari-matplotlib now adheres to `SPEC 0 <https://scientific-python.org/specs/spec-0000/>`_, and has:
 - Dropped support for Python 3.9
 - Added support for Python 3.12
 - Added a minimum required numpy verison of 1.23

From 1818110f3a04f9948b28ca789dd34eb7e7369674 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 24 May 2024 14:46:43 +0100
Subject: [PATCH 180/238] Don't fail if codecov fails

---
 .github/workflows/test_and_deploy.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml
index 94cf1e95..5f9ed4b9 100644
--- a/.github/workflows/test_and_deploy.yml
+++ b/.github/workflows/test_and_deploy.yml
@@ -64,7 +64,7 @@ jobs:
         if: github.event_name != 'merge_group'
         with:
           token: ${{ secrets.CODECOV_TOKEN }}
-          fail_ci_if_error: true
+          fail_ci_if_error: false
 
 
 

From 687ba2a279f0f1f5ef80b59258fe0caf6b8aa7fa Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 24 May 2024 14:16:47 +0100
Subject: [PATCH 181/238] Add some scientific Python repo recommendatiosn

---
 .github/workflows/test_and_deploy.yml | 4 ++++
 pyproject.toml                        | 8 ++++++--
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml
index 5f9ed4b9..2a8b7317 100644
--- a/.github/workflows/test_and_deploy.yml
+++ b/.github/workflows/test_and_deploy.yml
@@ -12,6 +12,10 @@ on:
   workflow_dispatch:
   merge_group:
 
+concurrency:
+    group: ${{ github.workflow }}-${{ github.ref }}
+    cancel-in-progress: true
+
 jobs:
   test:
     name: ${{ matrix.platform }} py${{ matrix.python-version }}
diff --git a/pyproject.toml b/pyproject.toml
index bbec4096..5a865b0f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,5 +1,5 @@
 [build-system]
-requires = ["setuptools", "wheel", "setuptools_scm"]
+requires = ["setuptools", "setuptools_scm"]
 build-backend = "setuptools.build_meta"
 
 [tool.setuptools_scm]
@@ -16,7 +16,11 @@ filterwarnings = [
     "ignore:Pickle, copy, and deepcopy support will be removed from itertools in Python 3.14.:DeprecationWarning"
 ]
 qt_api = "pyqt6"
-addopts = "--mpl --mpl-baseline-relative"
+addopts = ["--mpl", "--mpl-baseline-relative", "--strict-config", "--strict-markers", "-ra"]
+minversion = "7"
+testpaths = ["src/napari_matplotlib/tests"]
+log_cli_level = "INFO"
+xfail_strict = true
 
 [tool.black]
 line-length = 79

From 33a71d3231841b6452d677e2a94d0792e4ee3ffe Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Sat, 25 May 2024 09:29:58 +0100
Subject: [PATCH 182/238] Some more repo-review suggestions

---
 .pre-commit-config.yaml                  |  2 +-
 docs/conf.py                             |  1 +
 pyproject.toml                           | 12 ++++++++----
 src/napari_matplotlib/histogram.py       |  2 +-
 src/napari_matplotlib/tests/test_util.py |  2 +-
 src/napari_matplotlib/util.py            |  9 +++++++--
 6 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 40048ea4..79342e26 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -6,7 +6,7 @@ repos:
       - id: end-of-file-fixer
       - id: trailing-whitespace
 
-  - repo: https://github.com/psf/black
+  - repo: https://github.com/psf/black-pre-commit-mirror
     rev: 24.4.2
     hooks:
       - id: black
diff --git a/docs/conf.py b/docs/conf.py
index 2818f02e..f1533830 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -69,6 +69,7 @@ def napari_scraper(block, block_vars, gallery_conf):  # type: ignore[no-untyped-
     for win, img_path in zip(
         reversed(napari._qt.qt_main_window._QtMainWindow._instances),
         imgpath_iter,
+        strict=False,
     ):
         img_paths.append(img_path)
         win._window.screenshot(img_path, canvas_only=False)
diff --git a/pyproject.toml b/pyproject.toml
index 5a865b0f..05f7df68 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -31,7 +31,10 @@ line_length = 79
 
 [tool.ruff]
 target-version = "py310"
-select = ["I", "UP", "F", "E", "W", "D"]
+fix = true
+
+[tool.ruff.lint]
+select = ["B", "I", "UP", "F", "E", "W", "D"]
 ignore = [
     "D100", # Missing docstring in public module
     "D104", # Missing docstring in public package
@@ -41,14 +44,13 @@ ignore = [
     "D401", # First line of docstring should be in imperative mood
 
 ]
-fix = true
 
-[tool.ruff.per-file-ignores]
+[tool.ruff.lint.per-file-ignores]
 "docs/*" = ["D"]
 "examples/*" = ["D"]
 "src/napari_matplotlib/tests/*" = ["D"]
 
-[tool.ruff.pydocstyle]
+[tool.ruff.lint.pydocstyle]
 convention = "numpy"
 
 [tool.mypy]
@@ -59,6 +61,8 @@ disallow_subclassing_any = false # TODO: fix
 warn_return_any = false # TODO: fix
 ignore_missing_imports = true
 
+enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
+
 [[tool.mypy.overrides]]
 module = [
     "napari_matplotlib/tests/*",
diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index a0b6e09f..adbbae64 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -60,7 +60,7 @@ def on_update_layers(self) -> None:
 
     def _update_contrast_lims(self) -> None:
         for lim, line in zip(
-            self.layers[0].contrast_limits, self._contrast_lines
+            self.layers[0].contrast_limits, self._contrast_lines, strict=False
         ):
             line.set_xdata(lim)
 
diff --git a/src/napari_matplotlib/tests/test_util.py b/src/napari_matplotlib/tests/test_util.py
index 918e7633..e966cc26 100644
--- a/src/napari_matplotlib/tests/test_util.py
+++ b/src/napari_matplotlib/tests/test_util.py
@@ -26,7 +26,7 @@ def test_interval():
     assert 10 not in interval
 
     with pytest.raises(ValueError, match="must be an integer"):
-        "string" in interval  # type: ignore
+        assert "string" in interval  # type: ignore[operator]
 
     with pytest.raises(ValueError, match="must be <= upper_bound"):
         Interval(5, 3)
diff --git a/src/napari_matplotlib/util.py b/src/napari_matplotlib/util.py
index c54b60a7..8d4150c3 100644
--- a/src/napari_matplotlib/util.py
+++ b/src/napari_matplotlib/util.py
@@ -94,14 +94,18 @@ def _get_dimension(nodes: list[tinycss2.ast.Node], id_name: str) -> int | None:
         None if no IdentToken is found.
     """
     cleaned_nodes = [node for node in nodes if node.type != "whitespace"]
-    for name, _, value, _ in zip(*(iter(cleaned_nodes),) * 4):
+    for name, _, value, _ in zip(*(iter(cleaned_nodes),) * 4, strict=False):
         if (
             name.type == "ident"
             and value.type == "dimension"
             and name.value == id_name
         ):
             return value.int_value
-    warn(f"Unable to find DimensionToken for {id_name}", RuntimeWarning)
+    warn(
+        f"Unable to find DimensionToken for {id_name}",
+        RuntimeWarning,
+        stacklevel=1,
+    )
     return None
 
 
@@ -134,6 +138,7 @@ def from_napari_css_get_size_of(
         f"Unable to find {qt_element_name} or unable to find its size in "
         f"the current Napari stylesheet, falling back to {fallback}",
         RuntimeWarning,
+        stacklevel=1,
     )
     return QSize(*fallback)
 

From 426a0f5f8401300b1c8c0e93c479e5559e1dec97 Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Sat, 25 May 2024 10:36:31 +0100
Subject: [PATCH 183/238] use '| None' rather than Optional[Union[...]] for
 type hints

---
 src/napari_matplotlib/histogram.py | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 042ef8fe..fe281c41 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -1,4 +1,4 @@
-from typing import Any, Optional, Union, cast
+from typing import Any, cast
 
 import napari
 import numpy as np
@@ -30,8 +30,8 @@
 def _get_bins(
     data: npt.NDArray[Any],
     num_bins: int = 100,
-    start: Optional[Union[int, float]] = None,
-    stop: Optional[Union[int, float]] = None,
+    start: int | float | None = None,
+    stop: int | float | None = None,
 ) -> npt.NDArray[Any]:
     """Create evenly spaced bins with a given interval.
 
@@ -195,7 +195,7 @@ def bins_start(self) -> float:
         return self._bin_widgets["start"].value()
 
     @bins_start.setter
-    def bins_start(self, start: Union[int, float]) -> None:
+    def bins_start(self, start: int | float) -> None:
         """Set the minimum bin edge"""
         self._bin_widgets["start"].setValue(start)
 
@@ -205,7 +205,7 @@ def bins_stop(self) -> float:
         return self._bin_widgets["stop"].value()
 
     @bins_stop.setter
-    def bins_stop(self, stop: Union[int, float]) -> None:
+    def bins_stop(self, stop: int | float) -> None:
         """Set the maximum bin edge"""
         self._bin_widgets["stop"].setValue(stop)
 

From 67a864101d2d7d037bfbe8f9f3e1b55569cd125e Mon Sep 17 00:00:00 2001
From: Paul Smith <paul.j.smith@ucl.ac.uk>
Date: Sat, 25 May 2024 11:02:24 +0100
Subject: [PATCH 184/238] remove widgest to set start and stop values for
 histogram bins

---
 src/napari_matplotlib/histogram.py            | 145 +++---------------
 .../tests/baseline/test_histogram_2D_bins.png | Bin 19894 -> 19832 bytes
 src/napari_matplotlib/tests/test_histogram.py |   4 +-
 3 files changed, 24 insertions(+), 125 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index fe281c41..aeef8419 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -7,9 +7,7 @@
 from napari.layers import Image
 from napari.layers._multiscale_data import MultiScaleData
 from qtpy.QtWidgets import (
-    QAbstractSpinBox,
     QComboBox,
-    QDoubleSpinBox,
     QFormLayout,
     QGroupBox,
     QLabel,
@@ -30,41 +28,29 @@
 def _get_bins(
     data: npt.NDArray[Any],
     num_bins: int = 100,
-    start: int | float | None = None,
-    stop: int | float | None = None,
 ) -> npt.NDArray[Any]:
     """Create evenly spaced bins with a given interval.
 
-    If `start` or `stop` are `None`, they will be set based on the minimum
-    and maximum values, respectively, of the data.
-
     Parameters
     ----------
     data : napari.layers.Layer.data
         Napari layer data.
     num_bins : integer, optional
-        Number of evenly-spaced bins to create.
-    start : integer or real, optional
-        Start bin edge. Defaults to the minimum value of `data`.
-    stop : integer or real, optional
-        Stop bin edge. Defaults to the maximum value of `data`.
+        Number of evenly-spaced bins to create. Defaults to 100.
 
     Returns
     -------
     bin_edges : numpy.ndarray
         Array of evenly spaced bin edges.
     """
-    start = np.min(data) if start is None else start
-    stop = np.max(data) if stop is None else stop
-
     if data.dtype.kind in {"i", "u"}:
         # Make sure integer data types have integer sized bins
-        step = np.ceil((stop - start) / num_bins)
-        return np.arange(start, stop + step, step)
+        step = np.ceil(np.ptp(data) / num_bins)
+        return np.arange(np.min(data), np.max(data) + step, step)
     else:
         # For other data types we can use exactly `num_bins` bins
         # (and `num_bins` + 1 bin edges)
-        return np.linspace(start, stop, num_bins + 1)
+        return np.linspace(np.min(data), np.max(data), num_bins + 1)
 
 
 class HistogramWidget(SingleAxesWidget):
@@ -82,53 +68,28 @@ def __init__(
     ):
         super().__init__(napari_viewer, parent=parent)
 
-        # Create widgets for setting bin parameters
-        bins_start = QDoubleSpinBox()
-        bins_start.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType)
-        bins_start.setRange(-1e10, 1e10)
-        bins_start.setValue(0)
-        bins_start.setWrapping(False)
-        bins_start.setKeyboardTracking(False)
-        bins_start.setDecimals(2)
-
-        bins_stop = QDoubleSpinBox()
-        bins_stop.setStepType(QAbstractSpinBox.AdaptiveDecimalStepType)
-        bins_stop.setRange(-1e10, 1e10)
-        bins_stop.setValue(100)
-        bins_start.setWrapping(False)
-        bins_stop.setKeyboardTracking(False)
-        bins_stop.setDecimals(2)
-
-        bins_num = QSpinBox()
-        bins_num.setRange(1, 100_000)
-        bins_num.setValue(101)
-        bins_num.setWrapping(False)
-        bins_num.setKeyboardTracking(False)
+        num_bins_widget = QSpinBox()
+        num_bins_widget.setRange(1, 100_000)
+        num_bins_widget.setValue(101)
+        num_bins_widget.setWrapping(False)
+        num_bins_widget.setKeyboardTracking(False)
 
         # Set bins widget layout
         bins_selection_layout = QFormLayout()
-        bins_selection_layout.addRow("start", bins_start)
-        bins_selection_layout.addRow("stop", bins_stop)
-        bins_selection_layout.addRow("num", bins_num)
+        bins_selection_layout.addRow("num bins", num_bins_widget)
 
         # Group the widgets and add to main layout
-        bins_widget_group = QGroupBox("Bins")
-        bins_widget_group_layout = QVBoxLayout()
-        bins_widget_group_layout.addLayout(bins_selection_layout)
-        bins_widget_group.setLayout(bins_widget_group_layout)
-        self.layout().addWidget(bins_widget_group)
+        params_widget_group = QGroupBox("Params")
+        params_widget_group_layout = QVBoxLayout()
+        params_widget_group_layout.addLayout(bins_selection_layout)
+        params_widget_group.setLayout(params_widget_group_layout)
+        self.layout().addWidget(params_widget_group)
 
         # Add callbacks
-        bins_start.valueChanged.connect(self._draw)
-        bins_stop.valueChanged.connect(self._draw)
-        bins_num.valueChanged.connect(self._draw)
+        num_bins_widget.valueChanged.connect(self._draw)
 
         # Store widgets for later usage
-        self._bin_widgets = {
-            "start": bins_start,
-            "stop": bins_stop,
-            "num": bins_num,
-        }
+        self.num_bins_widget = num_bins_widget
 
         self._update_layers(None)
         self.viewer.events.theme.connect(self._on_napari_theme_changed)
@@ -144,27 +105,9 @@ def on_update_layers(self) -> None:
         if not self.layers:
             return
 
-        # Reset the bin start, stop and step values based on new layer data
+        # Reset the num bins based on new layer data
         layer_data = self._get_layer_data(self.layers[0])
-        self.autoset_widget_bins(data=layer_data)
-
-        # Only allow integer bins for integer data
-        # And only allow values greater than 0 for unsigned integers
-        n_decimals = 0 if np.issubdtype(layer_data.dtype, np.integer) else 2
-        is_unsigned = layer_data.dtype.kind == "u"
-        minimum_value = 0 if is_unsigned else -1e10
-
-        # Disable callbacks whilst widget values might change
-        for widget in self._bin_widgets.values():
-            widget.blockSignals(True)
-
-        self._bin_widgets["start"].setDecimals(n_decimals)
-        self._bin_widgets["stop"].setDecimals(n_decimals)
-        self._bin_widgets["start"].setMinimum(minimum_value)
-        self._bin_widgets["stop"].setMinimum(minimum_value)
-
-        for widget in self._bin_widgets.values():
-            widget.blockSignals(False)
+        self._set_widget_nums_bins(data=layer_data)
 
     def _update_contrast_lims(self) -> None:
         for lim, line in zip(
@@ -174,50 +117,10 @@ def _update_contrast_lims(self) -> None:
 
         self.figure.canvas.draw()
 
-    def autoset_widget_bins(self, data: npt.NDArray[Any]) -> None:
-        """Update widgets with bins determined from the image data"""
+    def _set_widget_nums_bins(self, data: npt.NDArray[Any]) -> None:
+        """Update num_bins widget with bins determined from the image data"""
         bins = _get_bins(data)
-
-        # Disable callbacks whilst setting widget values
-        for widget in self._bin_widgets.values():
-            widget.blockSignals(True)
-
-        self.bins_start = bins[0]
-        self.bins_stop = bins[-1]
-        self.bins_num = bins.size - 1
-
-        for widget in self._bin_widgets.values():
-            widget.blockSignals(False)
-
-    @property
-    def bins_start(self) -> float:
-        """Minimum bin edge"""
-        return self._bin_widgets["start"].value()
-
-    @bins_start.setter
-    def bins_start(self, start: int | float) -> None:
-        """Set the minimum bin edge"""
-        self._bin_widgets["start"].setValue(start)
-
-    @property
-    def bins_stop(self) -> float:
-        """Maximum bin edge"""
-        return self._bin_widgets["stop"].value()
-
-    @bins_stop.setter
-    def bins_stop(self, stop: int | float) -> None:
-        """Set the maximum bin edge"""
-        self._bin_widgets["stop"].setValue(stop)
-
-    @property
-    def bins_num(self) -> int:
-        """Number of bins to use"""
-        return self._bin_widgets["num"].value()
-
-    @bins_num.setter
-    def bins_num(self, num: int) -> None:
-        """Set the number of bins to use"""
-        self._bin_widgets["num"].setValue(num)
+        self.num_bins_widget.setValue(bins.size - 1)
 
     def _get_layer_data(self, layer: napari.layers.Layer) -> npt.NDArray[Any]:
         """Get the data associated with a given layer"""
@@ -248,9 +151,7 @@ def draw(self) -> None:
         # whole cube into memory.
         bins = _get_bins(
             data,
-            num_bins=self.bins_num,
-            start=self.bins_start,
-            stop=self.bins_stop,
+            num_bins=self.num_bins_widget.value(),
         )
 
         if layer.rgb:
diff --git a/src/napari_matplotlib/tests/baseline/test_histogram_2D_bins.png b/src/napari_matplotlib/tests/baseline/test_histogram_2D_bins.png
index db401612c44fb7eb92dc1ae9f2fd66a7bbd69fd6..98e3cde1254b75ffc92e7786f8106fe4cce3c16f 100644
GIT binary patch
diff --git a/src/napari_matplotlib/tests/test_histogram.py b/src/napari_matplotlib/tests/test_histogram.py
index 399db3d0..435973ba 100644
--- a/src/napari_matplotlib/tests/test_histogram.py
+++ b/src/napari_matplotlib/tests/test_histogram.py
@@ -17,9 +17,7 @@ def test_histogram_2D_bins(make_napari_viewer, astronaut_data):
     viewer.add_image(astronaut_data[0], **astronaut_data[1])
     widget = HistogramWidget(viewer)
     viewer.window.add_dock_widget(widget)
-    widget.bins_start = 0
-    widget.bins_stop = 350
-    widget.bins_num = 35
+    widget.num_bins_widget.setValue(25)
     fig = widget.figure
     # Need to return a copy, as original figure is too eagerley garbage
     # collected by the widget

From cf9aae9e5f2ed6acc9182a5b53110abf5f4a19ad Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 27 May 2024 17:37:35 +0000
Subject: [PATCH 185/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.2 → v0.4.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.2...v0.4.5)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 79342e26..ad714815 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.4.2'
+    rev: 'v0.4.5'
     hooks:
       - id: ruff
 

From dea3e6d9e66187c8b3212453c076b7ec7492cbc2 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 3 Jun 2024 17:41:38 +0000
Subject: [PATCH 186/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.5 → v0.4.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.5...v0.4.7)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ad714815..aefedf4d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.4.5'
+    rev: 'v0.4.7'
     hooks:
       - id: ruff
 

From 34099bc0073e7e097773a328aaafae442a66889e Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 10 Jun 2024 17:44:38 +0000
Subject: [PATCH 187/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.7 → v0.4.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.7...v0.4.8)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index aefedf4d..c921bc02 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.4.7'
+    rev: 'v0.4.8'
     hooks:
       - id: ruff
 

From 5990ef63ad6580adadacc48ddc9548aa7c3c6d82 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 17 Jun 2024 17:41:36 +0000
Subject: [PATCH 188/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.8 → v0.4.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.8...v0.4.9)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c921bc02..2fedfd88 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.4.8'
+    rev: 'v0.4.9'
     hooks:
       - id: ruff
 

From 2ad5adf83523e6ae8474c7aa9b0397c8f5ff20ab Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 24 Jun 2024 17:41:57 +0000
Subject: [PATCH 189/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.4.9 → v0.4.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.9...v0.4.10)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2fedfd88..305eb83c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.4.9'
+    rev: 'v0.4.10'
     hooks:
       - id: ruff
 

From c60ff0e3e2b829f55a8906f143189a72a1868ebd Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 8 Jul 2024 17:34:35 +0000
Subject: [PATCH 190/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/pre-commit/mirrors-mypy: v1.10.0 → v1.10.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.0...v1.10.1)
- [github.com/astral-sh/ruff-pre-commit: v0.4.10 → v0.5.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.10...v0.5.1)
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 305eb83c..ebae2d3f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -17,14 +17,14 @@ repos:
       - id: napari-plugin-checks
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.10.0
+    rev: v1.10.1
     hooks:
      - id: mypy
        additional_dependencies: [numpy, matplotlib]
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.4.10'
+    rev: 'v0.5.1'
     hooks:
       - id: ruff
 

From 3cb0fe7b7ddd391039b96e6fb6ff63cff3bbcfa9 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 12 Jul 2024 15:35:23 +0200
Subject: [PATCH 191/238] Pin max napari version

---
 docs/changelog.rst | 4 ++++
 setup.cfg          | 2 +-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index ff2e60cf..697e4838 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,13 +1,17 @@
 Changelog
 =========
+
 2.0.2
 -----
 Dependencies
 ~~~~~~~~~~~~
 napari-matplotlib now adheres to `SPEC 0 <https://scientific-python.org/specs/spec-0000/>`_, and has:
+
 - Dropped support for Python 3.9
 - Added support for Python 3.12
 - Added a minimum required numpy verison of 1.23
+- Pinned the maximum napari version to ``< 0.5``.
+  Version 3.0 of ``napari-matplotlib`` will introduce support for ``napari`` version 0.5.
 
 2.0.1
 -----
diff --git a/setup.cfg b/setup.cfg
index 2b745044..073478a3 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -28,7 +28,7 @@ project_urls =
 packages = find:
 install_requires =
     matplotlib
-    napari
+    napari<0.5
     numpy>=1.23
     tinycss2
 python_requires = >=3.10

From 70dc2567a17869f635d0b38189d2b07720717ed0 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 3 May 2024 15:16:10 +0100
Subject: [PATCH 192/238] Only update layers when selection is valid

---
 docs/changelog.rst            |  6 ++++++
 src/napari_matplotlib/base.py | 18 +++++++++++++-----
 2 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 697e4838..cab6ca68 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -13,6 +13,12 @@ napari-matplotlib now adheres to `SPEC 0 <https://scientific-python.org/specs/sp
 - Pinned the maximum napari version to ``< 0.5``.
   Version 3.0 of ``napari-matplotlib`` will introduce support for ``napari`` version 0.5.
 
+Bug fixes
+~~~~~~~~~
+- Only trigger layer update code paths if the layer selection is valid for the current
+  widget. This prevents errors when e.g., a labels layer is selected when a widget
+  that does not support a labels layer is open.
+
 2.0.1
 -----
 Bug fixes
diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py
index c455335e..9440b748 100644
--- a/src/napari_matplotlib/base.py
+++ b/src/napari_matplotlib/base.py
@@ -224,13 +224,23 @@ def _setup_callbacks(self) -> None:
             self._update_layers
         )
 
+    @property
+    def _valid_layer_selection(self) -> bool:
+        """
+        Return `True` if layer selection is valid.
+        """
+        return self.n_selected_layers in self.n_layers_input and all(
+            isinstance(layer, self.input_layer_types) for layer in self.layers
+        )
+
     def _update_layers(self, event: napari.utils.events.Event) -> None:
         """
         Update the ``layers`` attribute with currently selected layers and re-draw.
         """
         self.layers = list(self.viewer.layers.selection)
         self.layers = sorted(self.layers, key=lambda layer: layer.name)
-        self.on_update_layers()
+        if self._valid_layer_selection:
+            self.on_update_layers()
         self._draw()
 
     def _draw(self) -> None:
@@ -243,10 +253,7 @@ def _draw(self) -> None:
         with mplstyle.context(self.napari_theme_style_sheet):
             # everything should be done in the style context
             self.clear()
-            if self.n_selected_layers in self.n_layers_input and all(
-                isinstance(layer, self.input_layer_types)
-                for layer in self.layers
-            ):
+            if self._valid_layer_selection:
                 self.draw()
             self.canvas.draw()  # type: ignore[no-untyped-call]
 
@@ -269,6 +276,7 @@ def on_update_layers(self) -> None:
         Called when the selected layers are updated.
 
         This is a no-op, and is intended for derived classes to override.
+        It is only called if a selected layer is one of the input layer types.
         """
 
 

From 72e5791a56dfddb7c1c5646f7d1c69cfb6d1d022 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Sat, 25 May 2024 09:36:29 +0100
Subject: [PATCH 193/238] Only set colors if x_axis_key is string

---
 src/napari_matplotlib/histogram.py | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index adbbae64..560676ce 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -209,10 +209,12 @@ def draw(self) -> None:
         # get the colormap from the layer depending on its type
         if isinstance(self.layers[0], napari.layers.Points):
             colormap = self.layers[0].face_colormap
-            self.layers[0].face_color = self.x_axis_key
+            if self.x_axis_key:
+                self.layers[0].face_color = self.x_axis_key
         elif isinstance(self.layers[0], napari.layers.Vectors):
             colormap = self.layers[0].edge_colormap
-            self.layers[0].edge_color = self.x_axis_key
+            if self.x_axis_key:
+                self.layers[0].edge_color = self.x_axis_key
         else:
             colormap = None
 

From 2874081c869b32b17977c4df54a7267d59a9a286 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Sat, 25 May 2024 09:44:58 +0100
Subject: [PATCH 194/238] Debugging

---
 src/napari_matplotlib/base.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py
index 9440b748..97f4146e 100644
--- a/src/napari_matplotlib/base.py
+++ b/src/napari_matplotlib/base.py
@@ -229,6 +229,10 @@ def _valid_layer_selection(self) -> bool:
         """
         Return `True` if layer selection is valid.
         """
+        print(f"{self.n_selected_layers=}")
+        print(f"{self.n_layers_input=}")
+        print(f"{self.layers=}")
+        print(f"{self.input_layer_types=}")
         return self.n_selected_layers in self.n_layers_input and all(
             isinstance(layer, self.input_layer_types) for layer in self.layers
         )

From 3ebea2fdcbe375a079a445bdbffc0d504b62cece Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Sat, 25 May 2024 10:01:58 +0100
Subject: [PATCH 195/238] Put self._draw() in clause

---
 src/napari_matplotlib/base.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py
index 97f4146e..eb023c31 100644
--- a/src/napari_matplotlib/base.py
+++ b/src/napari_matplotlib/base.py
@@ -245,7 +245,7 @@ def _update_layers(self, event: napari.utils.events.Event) -> None:
         self.layers = sorted(self.layers, key=lambda layer: layer.name)
         if self._valid_layer_selection:
             self.on_update_layers()
-        self._draw()
+            self._draw()
 
     def _draw(self) -> None:
         """

From b01809f49893af89965e85a0c98fb3c0dbfc3b79 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 12 Jul 2024 15:52:02 +0200
Subject: [PATCH 196/238] Always call on_update_layers

---
 src/napari_matplotlib/base.py | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py
index eb023c31..720333e4 100644
--- a/src/napari_matplotlib/base.py
+++ b/src/napari_matplotlib/base.py
@@ -229,10 +229,6 @@ def _valid_layer_selection(self) -> bool:
         """
         Return `True` if layer selection is valid.
         """
-        print(f"{self.n_selected_layers=}")
-        print(f"{self.n_layers_input=}")
-        print(f"{self.layers=}")
-        print(f"{self.input_layer_types=}")
         return self.n_selected_layers in self.n_layers_input and all(
             isinstance(layer, self.input_layer_types) for layer in self.layers
         )
@@ -243,8 +239,8 @@ def _update_layers(self, event: napari.utils.events.Event) -> None:
         """
         self.layers = list(self.viewer.layers.selection)
         self.layers = sorted(self.layers, key=lambda layer: layer.name)
+        self.on_update_layers()
         if self._valid_layer_selection:
-            self.on_update_layers()
             self._draw()
 
     def _draw(self) -> None:
@@ -280,7 +276,6 @@ def on_update_layers(self) -> None:
         Called when the selected layers are updated.
 
         This is a no-op, and is intended for derived classes to override.
-        It is only called if a selected layer is one of the input layer types.
         """
 
 

From 8a545953b8de227fb4f0f79ffddf384ccadcac26 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 12 Jul 2024 16:00:36 +0200
Subject: [PATCH 197/238] Fix layer selection

---
 src/napari_matplotlib/histogram.py | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index 560676ce..2881cf78 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -55,8 +55,10 @@ def on_update_layers(self) -> None:
         Called when the selected layers are updated.
         """
         super().on_update_layers()
-        for layer in self.viewer.layers:
-            layer.events.contrast_limits.connect(self._update_contrast_lims)
+        if self._valid_layer_selection:
+            self.layers[0].events.contrast_limits.connect(
+                self._update_contrast_lims
+            )
 
     def _update_contrast_lims(self) -> None:
         for lim, line in zip(

From 25b9b0ee979694d3fc33f9812115cc47b91efe35 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 12 Jul 2024 16:13:42 +0200
Subject: [PATCH 198/238] Update changelog

---
 docs/changelog.rst | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index cab6ca68..54e6bba3 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,6 +1,13 @@
 Changelog
 =========
 
+2.0.3
+-----
+Bug fixes
+~~~~~~~~~
+- Fix an error that happened when the histogram widget was open, but a layer that doesn't support
+  histogramming (e.g., a labels layer) was selected.
+
 2.0.2
 -----
 Dependencies
@@ -13,12 +20,6 @@ napari-matplotlib now adheres to `SPEC 0 <https://scientific-python.org/specs/sp
 - Pinned the maximum napari version to ``< 0.5``.
   Version 3.0 of ``napari-matplotlib`` will introduce support for ``napari`` version 0.5.
 
-Bug fixes
-~~~~~~~~~
-- Only trigger layer update code paths if the layer selection is valid for the current
-  widget. This prevents errors when e.g., a labels layer is selected when a widget
-  that does not support a labels layer is open.
-
 2.0.1
 -----
 Bug fixes

From 8fe7c7f0b75da3a8018ebead7ddfc4823c338b35 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 12 Jul 2024 16:24:48 +0200
Subject: [PATCH 199/238] Update changelog

---
 docs/changelog.rst | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/docs/changelog.rst b/docs/changelog.rst
index 4c2509fa..60dd72ba 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,6 +1,12 @@
 Changelog
 =========
 
+2.1.0
+-----
+New features
+~~~~~~~~~~~~
+- Added a GUI element to manually set the number of bins in the histogram widgets.
+
 2.0.3
 -----
 Bug fixes
@@ -48,7 +54,6 @@ Other changes
 - The ``HistogramWidget`` now has two vertical lines showing the contrast limits used
   to render the selected layer in the main napari window.
 - Added an example gallery for the ``FeaturesHistogramWidget``.
-- Add widgets for setting bin parameters for ``HistogramWidget``.
 
 1.2.0
 -----

From 597e1c5aee7c92d22217887092a4e67303af62f3 Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Fri, 12 Jul 2024 16:55:24 +0200
Subject: [PATCH 200/238] Update test image

---
 .../scatter/baseline/test_scatter_2D.png      | Bin 18394 -> 18788 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)

diff --git a/src/napari_matplotlib/tests/scatter/baseline/test_scatter_2D.png b/src/napari_matplotlib/tests/scatter/baseline/test_scatter_2D.png
index a11bda5f281b731712a2d1c6076f72569e0ff5e0..0685b192b01b00ab02c5bceb4d4c966a8264cb4a 100644
GIT binary patch

From 323c55f22b9a6a47661be9bff6cdf5e548bbfbda Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Mon, 15 Jul 2024 20:06:37 +0100
Subject: [PATCH 201/238] Revert "Update test image"

This reverts commit 597e1c5aee7c92d22217887092a4e67303af62f3.
---
 .../scatter/baseline/test_scatter_2D.png      | Bin 18788 -> 18394 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)

diff --git a/src/napari_matplotlib/tests/scatter/baseline/test_scatter_2D.png b/src/napari_matplotlib/tests/scatter/baseline/test_scatter_2D.png
index 0685b192b01b00ab02c5bceb4d4c966a8264cb4a..a11bda5f281b731712a2d1c6076f72569e0ff5e0 100644
GIT binary patch

From 1ea1c5b65c28368ee8d7a44c242dc4b750a8bb3b Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 22 Jul 2024 17:45:02 +0000
Subject: [PATCH 202/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/pre-commit/mirrors-mypy: v1.10.1 → v1.11.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.1...v1.11.0)
- [github.com/astral-sh/ruff-pre-commit: v0.5.1 → v0.5.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.1...v0.5.4)
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ebae2d3f..54ee2023 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -17,14 +17,14 @@ repos:
       - id: napari-plugin-checks
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.10.1
+    rev: v1.11.0
     hooks:
      - id: mypy
        additional_dependencies: [numpy, matplotlib]
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.5.1'
+    rev: 'v0.5.4'
     hooks:
       - id: ruff
 

From 2e8330f509a93f30a6a1f8ccc6cd6d7627e04122 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 29 Jul 2024 17:48:13 +0000
Subject: [PATCH 203/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.5.4 → v0.5.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.4...v0.5.5)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 54ee2023..7ede235e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.5.4'
+    rev: 'v0.5.5'
     hooks:
       - id: ruff
 

From 3db8cae7298debeea16577065739047feeab137a Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 5 Aug 2024 17:56:44 +0000
Subject: [PATCH 204/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/pre-commit/mirrors-mypy: v1.11.0 → v1.11.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.0...v1.11.1)
- [github.com/astral-sh/ruff-pre-commit: v0.5.5 → v0.5.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.5...v0.5.6)
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 7ede235e..14ccabce 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -17,14 +17,14 @@ repos:
       - id: napari-plugin-checks
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.11.0
+    rev: v1.11.1
     hooks:
      - id: mypy
        additional_dependencies: [numpy, matplotlib]
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.5.5'
+    rev: 'v0.5.6'
     hooks:
       - id: ruff
 

From 6e0348dafef343dbaf9272c1231cbfed33b4cc7c Mon Sep 17 00:00:00 2001
From: Kabilar Gunalan <kabi@mit.edu>
Date: Thu, 8 Aug 2024 16:16:28 -0500
Subject: [PATCH 205/238] Fix typo

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index e4551d23..fb7aa635 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ A plugin to create Matplotlib plots from napari layers
 ## Introduction
 `napari-matplotlib` is a bridge between `napari` and `matplotlib`, making it easy to create publication quality `Matplotlib` plots based on the data loaded in `napari` layers.
 
-Documentaiton can be found at https://napari-matplotlib.github.io/
+Documentation can be found at https://napari-matplotlib.github.io/
 
 ## Contributing
 

From 0efe4432143760800872f224e45b9a295e7619cb Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 19 Aug 2024 17:42:28 +0000
Subject: [PATCH 206/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/psf/black-pre-commit-mirror: 24.4.2 → 24.8.0](https://github.com/psf/black-pre-commit-mirror/compare/24.4.2...24.8.0)
- [github.com/astral-sh/ruff-pre-commit: v0.5.6 → v0.6.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.6...v0.6.1)
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 14ccabce..cbf9cb88 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -7,7 +7,7 @@ repos:
       - id: trailing-whitespace
 
   - repo: https://github.com/psf/black-pre-commit-mirror
-    rev: 24.4.2
+    rev: 24.8.0
     hooks:
       - id: black
 
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.5.6'
+    rev: 'v0.6.1'
     hooks:
       - id: ruff
 

From 3dbfc475bd981cfe11a48c21eb9af81c1f44ca58 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 26 Aug 2024 17:48:50 +0000
Subject: [PATCH 207/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/pre-commit/mirrors-mypy: v1.11.1 → v1.11.2](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.1...v1.11.2)
- [github.com/astral-sh/ruff-pre-commit: v0.6.1 → v0.6.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.1...v0.6.2)
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index cbf9cb88..26d290e6 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -17,14 +17,14 @@ repos:
       - id: napari-plugin-checks
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.11.1
+    rev: v1.11.2
     hooks:
      - id: mypy
        additional_dependencies: [numpy, matplotlib]
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.6.1'
+    rev: 'v0.6.2'
     hooks:
       - id: ruff
 

From 0acbb986caa2637881369787c403b70e8390d8bf Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 2 Sep 2024 17:43:14 +0000
Subject: [PATCH 208/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.2 → v0.6.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.2...v0.6.3)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 26d290e6..bd4cb83a 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.6.2'
+    rev: 'v0.6.3'
     hooks:
       - id: ruff
 

From 0878bc1af0d261ea01ae82c6eb18b3428c3aae6c Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 4 Sep 2024 10:06:11 +0000
Subject: [PATCH 209/238] Bump actions/download-artifact from 3 to 4.1.7 in
 /.github/workflows

Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.1.7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v3...v4.1.7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
---
 .github/workflows/docs.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 39c1ab51..e1a86215 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -50,7 +50,7 @@ jobs:
     if: contains(github.ref, 'tags')
     steps:
     - uses: actions/checkout@v3
-    - uses: actions/download-artifact@v3
+    - uses: actions/download-artifact@v4.1.7
       with:
         name: docs
 

From 183f4e2e9e5bdd21ba960c351de44bce15dd8355 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 9 Sep 2024 17:45:46 +0000
Subject: [PATCH 210/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.3 → v0.6.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.3...v0.6.4)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index bd4cb83a..9b8c4466 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.6.3'
+    rev: 'v0.6.4'
     hooks:
       - id: ruff
 

From d1b75b1e2a4355f7a338f60089500e188de8226a Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 16 Sep 2024 17:43:13 +0000
Subject: [PATCH 211/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.4 → v0.6.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.4...v0.6.5)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 9b8c4466..8fb0414b 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.6.4'
+    rev: 'v0.6.5'
     hooks:
       - id: ruff
 

From bbba362e8cee5eaa96b0c8afa117df648708cef8 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 23 Sep 2024 17:43:16 +0000
Subject: [PATCH 212/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.5 → v0.6.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.5...v0.6.7)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8fb0414b..331601a3 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.6.5'
+    rev: 'v0.6.7'
     hooks:
       - id: ruff
 

From d21e7c8a2390040722651807ed9988184952ae58 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 30 Sep 2024 17:47:20 +0000
Subject: [PATCH 213/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.6.7 → v0.6.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.7...v0.6.8)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 331601a3..e8079098 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.6.7'
+    rev: 'v0.6.8'
     hooks:
       - id: ruff
 

From 7e860f541d3398adaa567f3e56c665e2c58d5bfc Mon Sep 17 00:00:00 2001
From: David Stansby <dstansby@gmail.com>
Date: Tue, 1 Oct 2024 12:35:54 +0100
Subject: [PATCH 214/238] Support napari>=0.5 (#274)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Remove as_dict kwarg

* Pin to napari >= 0.5

* Fix scatter test?

* Update test image

* Fix brain data

* Fix another as_dict arg

* Fix FutureWarning

* Fix scatter test's OverflowError

And revert to original baseline images.

* Try updating mypy's python version.

* Revert shape → newshape and suppress deprecation warning.

---------

Co-authored-by: Sam Cunliffe <s.cunliffe@ucl.ac.uk>
Co-authored-by: Sam Cunliffe <samcunliffe@users.noreply.github.com>
---
 pyproject.toml                                | 20 ++++++++++++-------
 setup.cfg                                     |  2 +-
 src/napari_matplotlib/base.py                 |  6 +++---
 .../tests/scatter/test_scatter.py             |  6 ++++--
 src/napari_matplotlib/tests/test_theme.py     |  2 +-
 5 files changed, 22 insertions(+), 14 deletions(-)

diff --git a/pyproject.toml b/pyproject.toml
index 05f7df68..f76831a3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,10 +13,18 @@ filterwarnings = [
     "ignore:distutils Version classes are deprecated:DeprecationWarning",
     "ignore:`np.bool8` is a deprecated alias for `np.bool_`:DeprecationWarning",
     # Coming from pydantic via napari
-    "ignore:Pickle, copy, and deepcopy support will be removed from itertools in Python 3.14.:DeprecationWarning"
+    "ignore:Pickle, copy, and deepcopy support will be removed from itertools in Python 3.14.:DeprecationWarning",
+    # Until we stop supporting older numpy versions (<2.1)
+    "ignore:(?s).*`newshape` keyword argument is deprecated.*$:DeprecationWarning",
 ]
 qt_api = "pyqt6"
-addopts = ["--mpl", "--mpl-baseline-relative", "--strict-config", "--strict-markers", "-ra"]
+addopts = [
+    "--mpl",
+    "--mpl-baseline-relative",
+    "--strict-config",
+    "--strict-markers",
+    "-ra",
+]
 minversion = "7"
 testpaths = ["src/napari_matplotlib/tests"]
 log_cli_level = "INFO"
@@ -54,17 +62,15 @@ ignore = [
 convention = "numpy"
 
 [tool.mypy]
-python_version = "3.10"
+python_version = "3.12"
 # Block below are checks that form part of mypy 'strict' mode
 strict = true
 disallow_subclassing_any = false # TODO: fix
-warn_return_any = false # TODO: fix
+warn_return_any = false          # TODO: fix
 ignore_missing_imports = true
 
 enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
 
 [[tool.mypy.overrides]]
-module = [
-    "napari_matplotlib/tests/*",
-]
+module = ["napari_matplotlib/tests/*"]
 disallow_untyped_defs = false
diff --git a/setup.cfg b/setup.cfg
index 073478a3..a3709e66 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -28,7 +28,7 @@ project_urls =
 packages = find:
 install_requires =
     matplotlib
-    napari<0.5
+    napari>=0.5
     numpy>=1.23
     tinycss2
 python_requires = >=3.10
diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py
index 720333e4..ca69a548 100644
--- a/src/napari_matplotlib/base.py
+++ b/src/napari_matplotlib/base.py
@@ -42,7 +42,7 @@ def __init__(
         super().__init__(parent=parent)
         self.viewer = napari_viewer
         self.napari_theme_style_sheet = style_sheet_from_theme(
-            get_theme(napari_viewer.theme, as_dict=False)
+            get_theme(napari_viewer.theme)
         )
 
         # Sets figure.* style
@@ -84,7 +84,7 @@ def _on_napari_theme_changed(self, event: Event) -> None:
             Event that triggered the callback.
         """
         self.napari_theme_style_sheet = style_sheet_from_theme(
-            get_theme(event.value, as_dict=False)
+            get_theme(event.value)
         )
         self._replace_toolbar_icons()
 
@@ -97,7 +97,7 @@ def _napari_theme_has_light_bg(self) -> bool:
         bool
             True if theme's background colour has hsl lighter than 50%, False if darker.
         """
-        theme = napari.utils.theme.get_theme(self.viewer.theme, as_dict=False)
+        theme = napari.utils.theme.get_theme(self.viewer.theme)
         _, _, bg_lightness = theme.background.as_hsl_tuple()
         return bg_lightness > 0.5
 
diff --git a/src/napari_matplotlib/tests/scatter/test_scatter.py b/src/napari_matplotlib/tests/scatter/test_scatter.py
index a225863d..0c60660c 100644
--- a/src/napari_matplotlib/tests/scatter/test_scatter.py
+++ b/src/napari_matplotlib/tests/scatter/test_scatter.py
@@ -15,7 +15,9 @@ def test_scatter_2D(make_napari_viewer, astronaut_data):
     viewer.add_image(astronaut_data[0], **astronaut_data[1], name="astronaut")
 
     viewer.add_image(
-        astronaut_data[0] * -1, **astronaut_data[1], name="astronaut_reversed"
+        astronaut_data[0] * -1.0,
+        **astronaut_data[1],
+        name="astronaut_reversed",
     )
     # De-select existing selection
     viewer.layers.selection.clear()
@@ -36,7 +38,7 @@ def test_scatter_3D(make_napari_viewer, brain_data):
     viewer.add_image(brain_data[0], **brain_data[1], name="brain")
 
     viewer.add_image(
-        brain_data[0] * -1, **brain_data[1], name="brain_reversed"
+        brain_data[0] * -1.0, **brain_data[1], name="brain_reversed"
     )
     # De-select existing selection
     viewer.layers.selection.clear()
diff --git a/src/napari_matplotlib/tests/test_theme.py b/src/napari_matplotlib/tests/test_theme.py
index 2310f32f..5fedc43d 100644
--- a/src/napari_matplotlib/tests/test_theme.py
+++ b/src/napari_matplotlib/tests/test_theme.py
@@ -29,7 +29,7 @@ def _mock_up_theme() -> None:
     Based on:
     https://napari.org/stable/gallery/new_theme.html
     """
-    blue_theme = napari.utils.theme.get_theme("dark", False)
+    blue_theme = napari.utils.theme.get_theme("dark")
     blue_theme.label = "blue"
     blue_theme.background = "#4169e1"  # my favourite shade of blue
     napari.utils.theme.register_theme(

From 37fce7f0cbeee2ff149196b574f02398b5d73b19 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 7 Oct 2024 20:37:49 +0200
Subject: [PATCH 215/238] [pre-commit.ci] pre-commit autoupdate (#289)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0)
- [github.com/astral-sh/ruff-pre-commit: v0.6.8 → v0.6.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.8...v0.6.9)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e8079098..924bc64c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.6.0
+    rev: v5.0.0
     hooks:
       - id: check-docstring-first
       - id: end-of-file-fixer
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.6.8'
+    rev: 'v0.6.9'
     hooks:
       - id: ruff
 

From 9f49b7acc7bc9fbcb958b105272e4fe384bcaeca Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 14 Oct 2024 19:04:30 +0100
Subject: [PATCH 216/238] [pre-commit.ci] pre-commit autoupdate (#290)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/psf/black-pre-commit-mirror: 24.8.0 → 24.10.0](https://github.com/psf/black-pre-commit-mirror/compare/24.8.0...24.10.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 924bc64c..ff538b96 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -7,7 +7,7 @@ repos:
       - id: trailing-whitespace
 
   - repo: https://github.com/psf/black-pre-commit-mirror
-    rev: 24.8.0
+    rev: 24.10.0
     hooks:
       - id: black
 

From b5178753a74158fb256852edf8a851a8598e3fe0 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 28 Oct 2024 18:33:49 +0000
Subject: [PATCH 217/238] [pre-commit.ci] pre-commit autoupdate (#291)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/pre-commit/mirrors-mypy: v1.11.2 → v1.13.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.2...v1.13.0)
- [github.com/astral-sh/ruff-pre-commit: v0.6.9 → v0.7.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.9...v0.7.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ff538b96..761bf7b8 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -17,14 +17,14 @@ repos:
       - id: napari-plugin-checks
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.11.2
+    rev: v1.13.0
     hooks:
      - id: mypy
        additional_dependencies: [numpy, matplotlib]
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.6.9'
+    rev: 'v0.7.1'
     hooks:
       - id: ruff
 

From 20cef6e780527a9d943f9306d79f7a51a90a5acd Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 4 Nov 2024 18:54:57 +0000
Subject: [PATCH 218/238] [pre-commit.ci] pre-commit autoupdate (#292)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.7.1 → v0.7.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.1...v0.7.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 761bf7b8..dea49b69 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.7.1'
+    rev: 'v0.7.2'
     hooks:
       - id: ruff
 

From ced5d9da6eaf47d00d66cfd7f0b3c4273ed73150 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 11 Nov 2024 17:48:49 +0000
Subject: [PATCH 219/238] [pre-commit.ci] pre-commit autoupdate (#293)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.7.2 → v0.7.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.2...v0.7.3)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index dea49b69..3e5d6796 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.7.2'
+    rev: 'v0.7.3'
     hooks:
       - id: ruff
 

From 89749f3f7b24be038fafe2e224d70274a741dabd Mon Sep 17 00:00:00 2001
From: Sam Cunliffe <samcunliffe@users.noreply.github.com>
Date: Tue, 4 Feb 2025 14:42:30 +0000
Subject: [PATCH 220/238] Update actions to latest versions.

---
 .github/workflows/docs.yml            | 4 ++--
 .github/workflows/test_and_deploy.yml | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index e1a86215..965463c2 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -17,9 +17,9 @@ jobs:
     name: Build & Upload Artifact
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
 
-      - uses: actions/setup-python@v3
+      - uses: actions/setup-python@v5
         with:
           python-version: "3.10"
 
diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml
index 2a8b7317..3709ff58 100644
--- a/.github/workflows/test_and_deploy.yml
+++ b/.github/workflows/test_and_deploy.yml
@@ -27,10 +27,10 @@ jobs:
         python-version: ['3.10', '3.11', '3.12']
 
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
 
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v3
+        uses: actions/setup-python@v5
         with:
           python-version: ${{ matrix.python-version }}
 

From 71280f645904d19d16437ab648efe7efe3a27509 Mon Sep 17 00:00:00 2001
From: Sam Cunliffe <samcunliffe@users.noreply.github.com>
Date: Tue, 4 Feb 2025 14:45:03 +0000
Subject: [PATCH 221/238] Missed a few.

---
 .github/workflows/docs.yml            | 2 +-
 .github/workflows/test_and_deploy.yml | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 965463c2..fda82b66 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -49,7 +49,7 @@ jobs:
     needs: build-docs
     if: contains(github.ref, 'tags')
     steps:
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@v4
     - uses: actions/download-artifact@v4.1.7
       with:
         name: docs
diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml
index 3709ff58..69e2e2a0 100644
--- a/.github/workflows/test_and_deploy.yml
+++ b/.github/workflows/test_and_deploy.yml
@@ -54,7 +54,7 @@ jobs:
         run: python -m tox
 
       - name: Upload pytest test results
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: pytest-results-${{ matrix.platform }} py${{ matrix.python-version }}
           path: reports/
@@ -83,9 +83,9 @@ jobs:
     permissions:
       id-token: write
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
       - name: Set up Python
-        uses: actions/setup-python@v2
+        uses: actions/setup-python@v5
         with:
           python-version: "3.x"
       - name: Install build

From 72a4a7f6cbcf420a2441a8d000f3c1a7c2f45fea Mon Sep 17 00:00:00 2001
From: Sam Cunliffe <samcunliffe@users.noreply.github.com>
Date: Tue, 4 Feb 2025 14:46:35 +0000
Subject: [PATCH 222/238] Upload docs.

---
 .github/workflows/docs.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index fda82b66..67dd1583 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -38,7 +38,7 @@ jobs:
           working-directory: ./docs
 
       - name: Upload artifact
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: docs
           path: docs/_build

From 80c782af77ff9334f92bbb3a2f1f11a9a33c546b Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 3 Mar 2025 17:29:38 +0000
Subject: [PATCH 223/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/psf/black-pre-commit-mirror: 24.10.0 → 25.1.0](https://github.com/psf/black-pre-commit-mirror/compare/24.10.0...25.1.0)
- [github.com/pre-commit/mirrors-mypy: v1.13.0 → v1.15.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.13.0...v1.15.0)
- [github.com/astral-sh/ruff-pre-commit: v0.7.3 → v0.9.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.3...v0.9.9)
---
 .pre-commit-config.yaml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3e5d6796..fb1a8ea2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -7,7 +7,7 @@ repos:
       - id: trailing-whitespace
 
   - repo: https://github.com/psf/black-pre-commit-mirror
-    rev: 24.10.0
+    rev: 25.1.0
     hooks:
       - id: black
 
@@ -17,14 +17,14 @@ repos:
       - id: napari-plugin-checks
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.13.0
+    rev: v1.15.0
     hooks:
      - id: mypy
        additional_dependencies: [numpy, matplotlib]
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.7.3'
+    rev: 'v0.9.9'
     hooks:
       - id: ruff
 

From 5f1129e441d004d636a5067d1107e3e42b90241b Mon Sep 17 00:00:00 2001
From: Sam Cunliffe <s.cunliffe@ucl.ac.uk>
Date: Tue, 4 Mar 2025 16:47:48 +0000
Subject: [PATCH 224/238] Ignore typing for now.

---
 src/napari_matplotlib/histogram.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index acdd8406..f874f0c5 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -28,7 +28,7 @@
 def _get_bins(
     data: npt.NDArray[Any],
     num_bins: int = 100,
-) -> npt.NDArray[Any]:
+) -> npt.NDArray[np.floating]:
     """Create evenly spaced bins with a given interval.
 
     Parameters
@@ -161,13 +161,13 @@ def draw(self) -> None:
             for i, c in enumerate("rgb"):
                 self.axes.hist(
                     data[..., i].ravel(),
-                    bins=bins.tolist(),
+                    bins=bins.tolist(),  # type: ignore[arg-type]
                     label=c,
                     histtype="step",
                     color=_COLORS[c],
                 )
         else:
-            self.axes.hist(data.ravel(), bins=bins.tolist(), label=layer.name)
+            self.axes.hist(data.ravel(), bins=bins.tolist(), label=layer.name)  # type: ignore[arg-type]
 
         self._contrast_lines = [
             self.axes.axvline(lim, color="white")
@@ -297,7 +297,7 @@ def draw(self) -> None:
 
         bins = _get_bins(data)
 
-        _, bins, patches = self.axes.hist(data, bins=bins.tolist())
+        _, bins, patches = self.axes.hist(data, bins=bins.tolist())  # type: ignore[arg-type]
         patches = cast(BarContainer, patches)
 
         # recolor the histogram plot

From 3dd313761ef430016d8a90cd0e70ce54eb9045fe Mon Sep 17 00:00:00 2001
From: Sam Cunliffe <samcunliffe@users.noreply.github.com>
Date: Tue, 4 Mar 2025 17:16:23 +0000
Subject: [PATCH 225/238] Create dependabot.yml

---
 .github/dependabot.yml | 7 +++++++
 1 file changed, 7 insertions(+)
 create mode 100644 .github/dependabot.yml

diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..64284b90
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,7 @@
+---
+version: 2
+updates:
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "monthly"

From 439dc67314d483d022b33eb4d544c8efdab00ec9 Mon Sep 17 00:00:00 2001
From: Sam Cunliffe <samcunliffe@users.noreply.github.com>
Date: Tue, 4 Mar 2025 17:27:30 +0000
Subject: [PATCH 226/238] Add group so all updates come in a single PR.

---
 .github/dependabot.yml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 64284b90..af3b9f02 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -5,3 +5,7 @@ updates:
     directory: "/"
     schedule:
       interval: "monthly"
+    groups:
+      github-actions:
+        patterns:
+          - "*"

From 64e5c7095ee22f071d316e349a4d0e8f59654ac0 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 4 Mar 2025 17:33:13 +0000
Subject: [PATCH 227/238] Bump the github-actions group with 2 updates

Bumps the github-actions group with 2 updates: [actions/download-artifact](https://github.com/actions/download-artifact) and [codecov/codecov-action](https://github.com/codecov/codecov-action).


Updates `actions/download-artifact` from 4.1.7 to 4.1.9
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4.1.7...v4.1.9)

Updates `codecov/codecov-action` from 4 to 5
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
---
 .github/workflows/docs.yml            | 2 +-
 .github/workflows/test_and_deploy.yml | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 67dd1583..e0e477a2 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -50,7 +50,7 @@ jobs:
     if: contains(github.ref, 'tags')
     steps:
     - uses: actions/checkout@v4
-    - uses: actions/download-artifact@v4.1.7
+    - uses: actions/download-artifact@v4.1.9
       with:
         name: docs
 
diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml
index 69e2e2a0..77853f7e 100644
--- a/.github/workflows/test_and_deploy.yml
+++ b/.github/workflows/test_and_deploy.yml
@@ -62,7 +62,7 @@ jobs:
         if: ${{ always() }}
 
       - name: Coverage
-        uses: codecov/codecov-action@v4
+        uses: codecov/codecov-action@v5
         # Don't run coverage on merge queue CI to avoid duplicating reports
         # to codecov. See https://github.com/matplotlib/napari-matplotlib/issues/155
         if: github.event_name != 'merge_group'

From 067fd10afa65c3f7379ffe7a6f701cee517acff4 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 10 Mar 2025 17:22:39 +0000
Subject: [PATCH 228/238] [pre-commit.ci] pre-commit autoupdate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.9.9 → v0.9.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.9...v0.9.10)
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index fb1a8ea2..0547e1d2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.9.9'
+    rev: 'v0.9.10'
     hooks:
       - id: ruff
 

From 228458957c7fa2fd0afcbef250a1627001a08ce7 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 17 Mar 2025 17:32:41 +0000
Subject: [PATCH 229/238] [pre-commit.ci] pre-commit autoupdate (#299)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.9.10 → v0.11.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.10...v0.11.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0547e1d2..cb440b3c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.9.10'
+    rev: 'v0.11.0'
     hooks:
       - id: ruff
 

From 2d6a88650595456cc6c8901d03281c9cf99351cc Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 24 Mar 2025 18:13:32 +0000
Subject: [PATCH 230/238] [pre-commit.ci] pre-commit autoupdate (#300)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.0 → v0.11.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.0...v0.11.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index cb440b3c..733413d5 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.11.0'
+    rev: 'v0.11.2'
     hooks:
       - id: ruff
 

From 1fb935f7a09e22db18f3dbd6f22ab6d6b393b9f0 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 1 Apr 2025 13:03:27 +0100
Subject: [PATCH 231/238] Bump actions/download-artifact in the github-actions
 group (#301)

Bumps the github-actions group with 1 update: [actions/download-artifact](https://github.com/actions/download-artifact).


Updates `actions/download-artifact` from 4.1.9 to 4.2.1
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4.1.9...v4.2.1)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 4.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 .github/workflows/docs.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index e0e477a2..b26b4990 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -50,7 +50,7 @@ jobs:
     if: contains(github.ref, 'tags')
     steps:
     - uses: actions/checkout@v4
-    - uses: actions/download-artifact@v4.1.9
+    - uses: actions/download-artifact@v4.2.1
       with:
         name: docs
 

From b81aa2d3a7e14efce9089e5a4d26a88c95ef72ae Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 7 Apr 2025 21:23:51 +0100
Subject: [PATCH 232/238] [pre-commit.ci] pre-commit autoupdate (#302)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.2 → v0.11.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.2...v0.11.4)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 733413d5..a172dabd 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.11.2'
+    rev: 'v0.11.4'
     hooks:
       - id: ruff
 

From e13454a105c55f1f489222cb5bf247d21cf5e536 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Tue, 22 Apr 2025 10:42:39 +0100
Subject: [PATCH 233/238] [pre-commit.ci] pre-commit autoupdate (#303)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.4 → v0.11.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.4...v0.11.6)

* Remove type:ignore flags for bins in histogram.py.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Sam Cunliffe <sam.cunliffe@gmail.com>
---
 .pre-commit-config.yaml            | 2 +-
 src/napari_matplotlib/histogram.py | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a172dabd..bd929a1a 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.11.4'
+    rev: 'v0.11.6'
     hooks:
       - id: ruff
 
diff --git a/src/napari_matplotlib/histogram.py b/src/napari_matplotlib/histogram.py
index f874f0c5..85bba9d2 100644
--- a/src/napari_matplotlib/histogram.py
+++ b/src/napari_matplotlib/histogram.py
@@ -161,13 +161,13 @@ def draw(self) -> None:
             for i, c in enumerate("rgb"):
                 self.axes.hist(
                     data[..., i].ravel(),
-                    bins=bins.tolist(),  # type: ignore[arg-type]
+                    bins=bins.tolist(),
                     label=c,
                     histtype="step",
                     color=_COLORS[c],
                 )
         else:
-            self.axes.hist(data.ravel(), bins=bins.tolist(), label=layer.name)  # type: ignore[arg-type]
+            self.axes.hist(data.ravel(), bins=bins.tolist(), label=layer.name)
 
         self._contrast_lines = [
             self.axes.axvline(lim, color="white")
@@ -297,7 +297,7 @@ def draw(self) -> None:
 
         bins = _get_bins(data)
 
-        _, bins, patches = self.axes.hist(data, bins=bins.tolist())  # type: ignore[arg-type]
+        _, bins, patches = self.axes.hist(data, bins=bins.tolist())
         patches = cast(BarContainer, patches)
 
         # recolor the histogram plot

From c8fe163f25a3b31d4aa2f640e3756fc0302c45e3 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Tue, 29 Apr 2025 06:41:45 +0100
Subject: [PATCH 234/238] [pre-commit.ci] pre-commit autoupdate (#304)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.6 → v0.11.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.6...v0.11.7)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index bd929a1a..81613d04 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.11.6'
+    rev: 'v0.11.7'
     hooks:
       - id: ruff
 

From f7a33364e2367334404ef3fc7d94a119f586c1d5 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 1 May 2025 10:52:55 +0100
Subject: [PATCH 235/238] Bump actions/download-artifact in the github-actions
 group (#305)

Bumps the github-actions group with 1 update: [actions/download-artifact](https://github.com/actions/download-artifact).


Updates `actions/download-artifact` from 4.2.1 to 4.3.0
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4.2.1...v4.3.0)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: 4.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 .github/workflows/docs.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index b26b4990..55eb1aa2 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -50,7 +50,7 @@ jobs:
     if: contains(github.ref, 'tags')
     steps:
     - uses: actions/checkout@v4
-    - uses: actions/download-artifact@v4.2.1
+    - uses: actions/download-artifact@v4.3.0
       with:
         name: docs
 

From 912ede93ae95936c5e8e5b654a10dd811c290933 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Tue, 6 May 2025 09:26:19 +0100
Subject: [PATCH 236/238] [pre-commit.ci] pre-commit autoupdate (#306)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.7 → v0.11.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.7...v0.11.8)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 81613d04..e1b0baf4 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.11.7'
+    rev: 'v0.11.8'
     hooks:
       - id: ruff
 

From db2be01b230dcfcec984660ba42625e165c907de Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Tue, 13 May 2025 06:13:27 +0100
Subject: [PATCH 237/238] [pre-commit.ci] pre-commit autoupdate (#307)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.8 → v0.11.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.8...v0.11.9)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e1b0baf4..e592dea1 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,7 +24,7 @@ repos:
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
     # Ruff version.
-    rev: 'v0.11.8'
+    rev: 'v0.11.9'
     hooks:
       - id: ruff
 

From 64edb23935c1bfc1c3bdbbf5d6d947c2abe2b02f Mon Sep 17 00:00:00 2001
From: Thomas A Caswell <tcaswell@gmail.com>
Date: Sun, 20 Jul 2025 16:49:40 -0400
Subject: [PATCH 238/238] CI: Harden GHA configuration (#308)

* CI: pin actions by SHA

This eliminates the possibility of a tag being changed under
us.

* CI: pin actions by SHA

This eliminates the possibility of a tag being changed under
us.

* CI: auto-fix via zizmor

May include:

- Avoids risky string interpolation.
- Prevents checkout premissions from leaking

* CI: Restrict default permissions

Reduces risk of arbitrary code is run by attacker.

* CI: run pre-commit on GHA
---
 .github/workflows/docs.yml            | 12 +++++++++---
 .github/workflows/test_and_deploy.yml | 25 ++++++++++++++++++++++---
 2 files changed, 31 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 55eb1aa2..6a5d182f 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -1,4 +1,6 @@
 name: Build docs
+permissions:
+  contents: read
 
 on:
 
@@ -18,12 +20,14 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
 
       - uses: actions/setup-python@v5
         with:
           python-version: "3.10"
 
-      - uses: tlambert03/setup-qt-libs@v1
+      - uses: tlambert03/setup-qt-libs@19e4ef2d781d81f5f067182e228b54ec90d23b76 # v1
 
       - name: Install Dependencies
         run: |
@@ -32,7 +36,7 @@ jobs:
           sudo apt install graphviz --yes
 
       - name: Build Docs
-        uses: aganders3/headless-gui@v2
+        uses: aganders3/headless-gui@f85dd6316993505dfc5f21839d520ae440c84816 # v2
         with:
           run: make html
           working-directory: ./docs
@@ -50,12 +54,14 @@ jobs:
     if: contains(github.ref, 'tags')
     steps:
     - uses: actions/checkout@v4
+      with:
+        persist-credentials: false
     - uses: actions/download-artifact@v4.3.0
       with:
         name: docs
 
     - name: Push to GitHub pages
-      uses: JamesIves/github-pages-deploy-action@v4
+      uses: JamesIves/github-pages-deploy-action@6c2d9db40f9296374acc17b90404b6e8864128c8 # v4
       with:
         folder: html
         ssh-key: ${{ secrets.DEPLOY_KEY }}
diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml
index 77853f7e..0521c9e6 100644
--- a/.github/workflows/test_and_deploy.yml
+++ b/.github/workflows/test_and_deploy.yml
@@ -1,4 +1,6 @@
 name: tests
+permissions:
+  contents: read
 
 on:
   push:
@@ -17,6 +19,19 @@ concurrency:
     cancel-in-progress: true
 
 jobs:
+  pre-commit:
+    name: precommit
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2
+        with:
+          fetch-depth: 0
+      - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55  # v5.5.0
+        with:
+          python-version: "3.x"
+      - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd  # v3.0.1
+        with:
+          extra_args: --hook-stage manual --all-files
   test:
     name: ${{ matrix.platform }} py${{ matrix.python-version }}
     runs-on: ${{ matrix.platform }}
@@ -28,6 +43,8 @@ jobs:
 
     steps:
       - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
 
       - name: Set up Python ${{ matrix.python-version }}
         uses: actions/setup-python@v5
@@ -35,7 +52,7 @@ jobs:
           python-version: ${{ matrix.python-version }}
 
       # these libraries enable testing on Qt on linux
-      - uses: tlambert03/setup-qt-libs@v1
+      - uses: tlambert03/setup-qt-libs@19e4ef2d781d81f5f067182e228b54ec90d23b76 # v1
 
       # strategy borrowed from vispy for installing opengl libs on windows
       - name: Install Windows OpenGL
@@ -62,7 +79,7 @@ jobs:
         if: ${{ always() }}
 
       - name: Coverage
-        uses: codecov/codecov-action@v5
+        uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5
         # Don't run coverage on merge queue CI to avoid duplicating reports
         # to codecov. See https://github.com/matplotlib/napari-matplotlib/issues/155
         if: github.event_name != 'merge_group'
@@ -84,6 +101,8 @@ jobs:
       id-token: write
     steps:
       - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
       - name: Set up Python
         uses: actions/setup-python@v5
         with:
@@ -99,4 +118,4 @@ jobs:
           python -m build .
 
       - name: Publish package
-        uses: pypa/gh-action-pypi-publish@release/v1
+        uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
