Skip to content

Commit 20bc846

Browse files
authored
Merge pull request #125 from dstansby/refactor
Factor out layer selecton and MPL figure creation
2 parents 51b6b03 + 0e2f047 commit 20bc846

File tree

2 files changed

+82
-67
lines changed

2 files changed

+82
-67
lines changed

docs/changelog.rst

+5
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.

src/napari_matplotlib/base.py

+77-67
Original file line numberDiff line numberDiff line change
@@ -17,41 +17,25 @@
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
2727
This creates a single FigureCanvas, which contains a single
2828
`~matplotlib.figure.Figure`, and an associated toolbar.
2929
It is not responsible for creating any Axes, because different
3030
widgets may want to implement different subplot layouts.
31-
32-
This class also handles callbacks to automatically update figures when
33-
the layer selection or z-step is changed in the napari viewer. To take
34-
advantage of this sub-classes should implement the ``clear()`` and
35-
``draw()`` methods.
36-
37-
Attributes
38-
----------
39-
viewer : `napari.Viewer`
40-
Main napari viewer.
41-
canvas : matplotlib.backends.backend_qt5agg.FigureCanvas
42-
Matplotlib canvas.
43-
layers : `list`
44-
List of currently selected napari layers.
4531
"""
4632

4733
def __init__(
4834
self,
49-
napari_viewer: napari.viewer.Viewer,
5035
parent: Optional[QWidget] = None,
5136
):
5237
super().__init__(parent=parent)
5338

54-
self.viewer = napari_viewer
5539
self.canvas = FigureCanvas()
5640

5741
self.canvas.figure.patch.set_facecolor("none")
@@ -65,6 +49,81 @@ def __init__(
6549
self.layout().addWidget(self.toolbar)
6650
self.layout().addWidget(self.canvas)
6751

52+
@property
53+
def figure(self) -> Figure:
54+
"""Matplotlib figure."""
55+
return self.canvas.figure
56+
57+
def add_single_axes(self) -> None:
58+
"""
59+
Add a single Axes to the figure.
60+
61+
The Axes is saved on the ``.axes`` attribute for later access.
62+
"""
63+
self.axes = self.figure.subplots()
64+
self.apply_napari_colorscheme(self.axes)
65+
66+
@staticmethod
67+
def apply_napari_colorscheme(ax: Axes) -> None:
68+
"""Apply napari-compatible colorscheme to an Axes."""
69+
# changing color of axes background to transparent
70+
ax.set_facecolor("none")
71+
72+
# changing colors of all axes
73+
for spine in ax.spines:
74+
ax.spines[spine].set_color("white")
75+
76+
ax.xaxis.label.set_color("white")
77+
ax.yaxis.label.set_color("white")
78+
79+
# changing colors of axes labels
80+
ax.tick_params(axis="x", colors="white")
81+
ax.tick_params(axis="y", colors="white")
82+
83+
def _replace_toolbar_icons(self) -> None:
84+
# Modify toolbar icons and some tooltips
85+
for action in self.toolbar.actions():
86+
text = action.text()
87+
if text == "Pan":
88+
action.setToolTip(
89+
"Pan/Zoom: Left button pans; Right button zooms; "
90+
"Click once to activate; Click again to deactivate"
91+
)
92+
if text == "Zoom":
93+
action.setToolTip(
94+
"Zoom to rectangle; Click once to activate; "
95+
"Click again to deactivate"
96+
)
97+
if len(text) > 0: # i.e. not a separator item
98+
icon_path = os.path.join(ICON_ROOT, text + ".png")
99+
action.setIcon(QIcon(icon_path))
100+
101+
102+
class NapariMPLWidget(MPLWidget):
103+
"""
104+
Widget containing a Matplotlib canvas and toolbar.
105+
106+
In addition to `BaseNapariMPLWidget`, this class handles callbacks
107+
to automatically update figures when the layer selection or z-step
108+
is changed in the napari viewer. To take advantage of this sub-classes
109+
should implement the ``clear()`` and ``draw()`` methods.
110+
111+
Attributes
112+
----------
113+
viewer : `napari.Viewer`
114+
Main napari viewer.
115+
layers : `list`
116+
List of currently selected napari layers.
117+
"""
118+
119+
def __init__(
120+
self,
121+
napari_viewer: napari.viewer.Viewer,
122+
parent: Optional[QWidget] = None,
123+
):
124+
super().__init__(parent=parent)
125+
126+
self.viewer = napari_viewer
68127
self._setup_callbacks()
69128
self.layers: List[napari.layers.Layer] = []
70129

@@ -73,11 +132,6 @@ def __init__(
73132
#: Type of layer taken as input
74133
input_layer_types: Tuple[napari.layers.Layer, ...] = (napari.layers.Layer,)
75134

76-
@property
77-
def figure(self) -> Figure:
78-
"""Matplotlib figure."""
79-
return self.canvas.figure
80-
81135
@property
82136
def n_selected_layers(self) -> int:
83137
"""
@@ -139,32 +193,6 @@ def draw(self) -> None:
139193
This is a no-op, and is intended for derived classes to override.
140194
"""
141195

142-
def add_single_axes(self) -> None:
143-
"""
144-
Add a single Axes to the figure.
145-
146-
The Axes is saved on the ``.axes`` attribute for later access.
147-
"""
148-
self.axes = self.figure.subplots()
149-
self.apply_napari_colorscheme(self.axes)
150-
151-
@staticmethod
152-
def apply_napari_colorscheme(ax: Axes) -> None:
153-
"""Apply napari-compatible colorscheme to an Axes."""
154-
# changing color of axes background to transparent
155-
ax.set_facecolor("none")
156-
157-
# changing colors of all axes
158-
for spine in ax.spines:
159-
ax.spines[spine].set_color("white")
160-
161-
ax.xaxis.label.set_color("white")
162-
ax.yaxis.label.set_color("white")
163-
164-
# changing colors of axes labels
165-
ax.tick_params(axis="x", colors="white")
166-
ax.tick_params(axis="y", colors="white")
167-
168196
def _on_update_layers(self) -> None:
169197
"""
170198
Function is called when self.layers is updated via
@@ -173,24 +201,6 @@ def _on_update_layers(self) -> None:
173201
This is a no-op, and is intended for derived classes to override.
174202
"""
175203

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

195205
class NapariNavigationToolbar(NavigationToolbar2QT):
196206
"""Custom Toolbar style for Napari."""

0 commit comments

Comments
 (0)