Skip to content

Commit 6512e58

Browse files
authored
Merge branch 'main' into enable-gallery-melissa
2 parents 2d69416 + 231d77d commit 6512e58

File tree

7 files changed

+117
-64
lines changed

7 files changed

+117
-64
lines changed

.github/workflows/test_and_deploy.yml

+8
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ jobs:
4848
- name: Test with tox
4949
run: python -m tox
5050

51+
- name: Upload pytest test results
52+
uses: actions/upload-artifact@v3
53+
with:
54+
name: pytest-results-${{ matrix.platform }} py${{ matrix.python-version }}
55+
path: reports/
56+
# Use always() to always run this step to publish test results when there are test failures
57+
if: ${{ always() }}
58+
5159
- name: Coverage
5260
uses: codecov/codecov-action@v2
5361

docs/changelog.rst

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ Changelog
44
0.3.0
55
-----
66

7+
New features
8+
~~~~~~~~~~~~
9+
- Added `MPLWidget` as a widget containing just a Matplotlib canvas
10+
without any association with a napari viewer.
11+
712
Visual improvements
813
~~~~~~~~~~~~~~~~~~~
914
- The background of ``napari-matplotlib`` figures and axes is now transparent.
@@ -18,6 +23,9 @@ Changes
1823
you would be interested in please open an issue at https://github.com/matplotlib/napari-matplotlib.
1924
- Labels plotting with the features scatter widget no longer have underscores
2025
replaced with spaces.
26+
- ``NapariMPLWidget.update_layers()`` has been removed as it is intended to be
27+
private API. Use `NapariMPLWidget.on_update_layers` instead to implement
28+
funcitonality when layer selection is changed.
2129

2230
Bug fixes
2331
~~~~~~~~~

src/napari_matplotlib/base.py

+90-59
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
# Icons modified from
1818
# https://github.com/matplotlib/matplotlib/tree/main/lib/matplotlib/mpl-data/images
1919
ICON_ROOT = Path(__file__).parent / "icons"
20-
__all__ = ["NapariMPLWidget"]
20+
__all__ = ["MPLWidget", "NapariMPLWidget"]
2121

2222

23-
class NapariMPLWidget(QWidget):
23+
class MPLWidget(QWidget):
2424
"""
2525
Widget containing a Matplotlib canvas and toolbar.
2626
@@ -47,12 +47,10 @@ class NapariMPLWidget(QWidget):
4747

4848
def __init__(
4949
self,
50-
napari_viewer: napari.viewer.Viewer,
5150
parent: Optional[QWidget] = None,
5251
):
5352
super().__init__(parent=parent)
5453

55-
self.viewer = napari_viewer
5654
self.canvas = FigureCanvas()
5755

5856
self.canvas.figure.patch.set_facecolor("none")
@@ -66,6 +64,87 @@ def __init__(
6664
self.layout().addWidget(self.toolbar)
6765
self.layout().addWidget(self.canvas)
6866

67+
@property
68+
def figure(self) -> Figure:
69+
"""Matplotlib figure."""
70+
return self.canvas.figure
71+
72+
def add_single_axes(self) -> None:
73+
"""
74+
Add a single Axes to the figure.
75+
76+
The Axes is saved on the ``.axes`` attribute for later access.
77+
"""
78+
self.axes = self.figure.subplots()
79+
self.apply_napari_colorscheme(self.axes)
80+
81+
@staticmethod
82+
def apply_napari_colorscheme(ax: Axes) -> None:
83+
"""Apply napari-compatible colorscheme to an Axes."""
84+
# changing color of axes background to transparent
85+
ax.set_facecolor("none")
86+
87+
# changing colors of all axes
88+
for spine in ax.spines:
89+
ax.spines[spine].set_color("white")
90+
91+
ax.xaxis.label.set_color("white")
92+
ax.yaxis.label.set_color("white")
93+
94+
# changing colors of axes labels
95+
ax.tick_params(axis="x", colors="white")
96+
ax.tick_params(axis="y", colors="white")
97+
98+
def _replace_toolbar_icons(self) -> None:
99+
# Modify toolbar icons and some tooltips
100+
for action in self.toolbar.actions():
101+
text = action.text()
102+
if text == "Pan":
103+
action.setToolTip(
104+
"Pan/Zoom: Left button pans; Right button zooms; "
105+
"Click once to activate; Click again to deactivate"
106+
)
107+
if text == "Zoom":
108+
action.setToolTip(
109+
"Zoom to rectangle; Click once to activate; "
110+
"Click again to deactivate"
111+
)
112+
if len(text) > 0: # i.e. not a separator item
113+
icon_path = os.path.join(ICON_ROOT, text + ".png")
114+
action.setIcon(QIcon(icon_path))
115+
116+
117+
class NapariMPLWidget(MPLWidget):
118+
"""
119+
Widget containing a Matplotlib canvas and toolbar.
120+
121+
In addition to `BaseNapariMPLWidget`, this class handles callbacks
122+
to automatically update figures when the layer selection or z-step
123+
is changed in the napari viewer. To take advantage of this sub-classes
124+
should implement the ``clear()`` and ``draw()`` methods.
125+
126+
When both the z-step and layer selection is changed, ``clear()`` is called
127+
and if the number a type of selected layers are valid for the widget
128+
``draw()`` is then called. When layer selection is changed ``on_update_layers()``
129+
is also called, which can be useful e.g. for updating a layer list in a
130+
selection widget.
131+
132+
Attributes
133+
----------
134+
viewer : `napari.Viewer`
135+
Main napari viewer.
136+
layers : `list`
137+
List of currently selected napari layers.
138+
"""
139+
140+
def __init__(
141+
self,
142+
napari_viewer: napari.viewer.Viewer,
143+
parent: Optional[QWidget] = None,
144+
):
145+
super().__init__(parent=parent)
146+
147+
self.viewer = napari_viewer
69148
self._setup_callbacks()
70149
self.layers: List[napari.layers.Layer] = []
71150

@@ -74,11 +153,6 @@ def __init__(
74153
#: Type of layer taken as input
75154
input_layer_types: Tuple[napari.layers.Layer, ...] = (napari.layers.Layer,)
76155

77-
@property
78-
def figure(self) -> Figure:
79-
"""Matplotlib figure."""
80-
return self.canvas.figure
81-
82156
@property
83157
def n_selected_layers(self) -> int:
84158
"""
@@ -104,14 +178,16 @@ def _setup_callbacks(self) -> None:
104178
# z-step changed in viewer
105179
self.viewer.dims.events.current_step.connect(self._draw)
106180
# Layer selection changed in viewer
107-
self.viewer.layers.selection.events.changed.connect(self.update_layers)
181+
self.viewer.layers.selection.events.changed.connect(
182+
self._update_layers
183+
)
108184

109-
def update_layers(self, event: napari.utils.events.Event) -> None:
185+
def _update_layers(self, event: napari.utils.events.Event) -> None:
110186
"""
111187
Update the ``layers`` attribute with currently selected layers and re-draw.
112188
"""
113189
self.layers = list(self.viewer.layers.selection)
114-
self._on_update_layers()
190+
self.on_update_layers()
115191
self._draw()
116192

117193
def _draw(self) -> None:
@@ -140,58 +216,13 @@ def draw(self) -> None:
140216
This is a no-op, and is intended for derived classes to override.
141217
"""
142218

143-
def add_single_axes(self) -> None:
144-
"""
145-
Add a single Axes to the figure.
146-
147-
The Axes is saved on the ``.axes`` attribute for later access.
219+
def on_update_layers(self) -> None:
148220
"""
149-
self.axes = self.figure.subplots()
150-
self.apply_napari_colorscheme(self.axes)
151-
152-
@staticmethod
153-
def apply_napari_colorscheme(ax: Axes) -> None:
154-
"""Apply napari-compatible colorscheme to an Axes."""
155-
# changing color of axes background to transparent
156-
ax.set_facecolor("none")
157-
158-
# changing colors of all axes
159-
for spine in ax.spines:
160-
ax.spines[spine].set_color("white")
161-
162-
ax.xaxis.label.set_color("white")
163-
ax.yaxis.label.set_color("white")
164-
165-
# changing colors of axes labels
166-
ax.tick_params(axis="x", colors="white")
167-
ax.tick_params(axis="y", colors="white")
168-
169-
def _on_update_layers(self) -> None:
170-
"""
171-
Function is called when self.layers is updated via
172-
``self.update_layers()``.
221+
Called when the selected layers are updated.
173222
174223
This is a no-op, and is intended for derived classes to override.
175224
"""
176225

177-
def _replace_toolbar_icons(self) -> None:
178-
# Modify toolbar icons and some tooltips
179-
for action in self.toolbar.actions():
180-
text = action.text()
181-
if text == "Pan":
182-
action.setToolTip(
183-
"Pan/Zoom: Left button pans; Right button zooms; "
184-
"Click once to activate; Click again to deactivate"
185-
)
186-
if text == "Zoom":
187-
action.setToolTip(
188-
"Zoom to rectangle; Click once to activate; "
189-
"Click again to deactivate"
190-
)
191-
if len(text) > 0: # i.e. not a separator item
192-
icon_path = os.path.join(ICON_ROOT, text + ".png")
193-
action.setIcon(QIcon(icon_path))
194-
195226

196227
class NapariNavigationToolbar(NavigationToolbar2QT):
197228
"""Custom Toolbar style for Napari."""

src/napari_matplotlib/histogram.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def __init__(
2727
):
2828
super().__init__(napari_viewer, parent=parent)
2929
self.add_single_axes()
30-
self.update_layers(None)
30+
self._update_layers(None)
3131

3232
def clear(self) -> None:
3333
"""

src/napari_matplotlib/scatter.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def __init__(
132132
self.layout().addWidget(QLabel(f"{dim}-axis:"))
133133
self.layout().addWidget(self._selectors[dim])
134134

135-
self.update_layers(None)
135+
self._update_layers(None)
136136

137137
@property
138138
def x_axis_key(self) -> Union[str, None]:
@@ -230,7 +230,7 @@ def _get_data(self) -> Tuple[npt.NDArray[Any], npt.NDArray[Any], str, str]:
230230

231231
return x, y, x_axis_name, y_axis_name
232232

233-
def _on_update_layers(self) -> None:
233+
def on_update_layers(self) -> None:
234234
"""
235235
Called when the layer selection changes by ``self.update_layers()``.
236236
"""

src/napari_matplotlib/slice.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def __init__(
5151
for d in _dims_sel:
5252
self.slice_selectors[d].textChanged.connect(self._draw)
5353

54-
self.update_layers(None)
54+
self._update_layers(None)
5555

5656
@property
5757
def _layer(self) -> napari.layers.Layer:

tox.ini

+7-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ python =
1111

1212
[testenv]
1313
extras = testing
14+
allowlist_externals =
15+
cp
16+
ls
17+
tree
1418
commands =
19+
cp -R {toxinidir}/src/napari_matplotlib/tests/baseline {envdir}/baseline
20+
ls {toxinidir}/src/napari_matplotlib/tests/baseline
1521
python -c 'from skimage import data; data.brain()'
16-
python -m pytest --mpl -v --color=yes --cov=napari_matplotlib --cov-report=xml
22+
python -m pytest --mpl --mpl-generate-summary=html --mpl-results-path={toxinidir}/reports -v --color=yes --cov=napari_matplotlib --cov-report=xml

0 commit comments

Comments
 (0)