8
8
FigureCanvas ,
9
9
NavigationToolbar2QT ,
10
10
)
11
+ import matplotlib .style as mplstyle
11
12
from matplotlib .figure import Figure
12
13
from qtpy .QtGui import QIcon
13
14
from qtpy .QtWidgets import QLabel , QVBoxLayout , QWidget
@@ -40,8 +41,11 @@ def __init__(
40
41
):
41
42
super ().__init__ (parent = parent )
42
43
self .viewer = napari_viewer
44
+ self ._mpl_style_sheet_path : Optional [Path ] = None
43
45
44
- self .canvas = FigureCanvas ()
46
+ # Sets figure.* style
47
+ with mplstyle .context (self .mpl_style_sheet_path ):
48
+ self .canvas = FigureCanvas ()
45
49
46
50
self .canvas .figure .patch .set_facecolor ("none" )
47
51
self .canvas .figure .set_layout_engine ("constrained" )
@@ -52,7 +56,7 @@ def __init__(
52
56
# callback to update when napari theme changed
53
57
# TODO: this isn't working completely (see issue #140)
54
58
# most of our styling respects the theme change but not all
55
- self .viewer .events .theme .connect (self ._on_theme_change )
59
+ self .viewer .events .theme .connect (self ._on_napari_theme_changed )
56
60
57
61
self .setLayout (QVBoxLayout ())
58
62
self .layout ().addWidget (self .toolbar )
@@ -63,47 +67,40 @@ def figure(self) -> Figure:
63
67
"""Matplotlib figure."""
64
68
return self .canvas .figure
65
69
70
+ @property
71
+ def mpl_style_sheet_path (self ) -> Path :
72
+ """
73
+ Path to the set Matplotlib style sheet.
74
+ """
75
+ if self ._mpl_style_sheet_path is not None :
76
+ return self ._mpl_style_sheet_path
77
+ elif self ._napari_theme_has_light_bg ():
78
+ return Path (__file__ ).parent / "styles" / "light.mplstyle"
79
+ else :
80
+ return Path (__file__ ).parent / "styles" / "dark.mplstyle"
81
+
82
+ @mpl_style_sheet_path .setter
83
+ def mpl_style_sheet_path (self , path : Path ) -> None :
84
+ self ._mpl_style_sheet_path = Path (path )
85
+
66
86
def add_single_axes (self ) -> None :
67
87
"""
68
88
Add a single Axes to the figure.
69
89
70
90
The Axes is saved on the ``.axes`` attribute for later access.
71
91
"""
72
- self .axes = self .figure .subplots ()
73
- self .apply_napari_colorscheme (self .axes )
74
-
75
- def apply_napari_colorscheme (self , ax : Axes ) -> None :
76
- """Apply napari-compatible colorscheme to an Axes."""
77
- # get the foreground colours from current theme
78
- theme = napari .utils .theme .get_theme (self .viewer .theme , as_dict = False )
79
- fg_colour = theme .foreground .as_hex () # fg is a muted contrast to bg
80
- text_colour = theme .text .as_hex () # text is high contrast to bg
81
-
82
- # changing color of axes background to transparent
83
- ax .set_facecolor ("none" )
84
-
85
- # changing colors of all axes
86
- for spine in ax .spines :
87
- ax .spines [spine ].set_color (fg_colour )
88
-
89
- ax .xaxis .label .set_color (text_colour )
90
- ax .yaxis .label .set_color (text_colour )
91
-
92
- # changing colors of axes labels
93
- ax .tick_params (axis = "x" , colors = text_colour )
94
- ax .tick_params (axis = "y" , colors = text_colour )
95
-
96
- def _on_theme_change (self ) -> None :
97
- """Update MPL toolbar and axis styling when `napari.Viewer.theme` is changed.
92
+ # Sets axes.* style.
93
+ # Does not set any text styling set by axes.* keys
94
+ with mplstyle .context (self .mpl_style_sheet_path ):
95
+ self .axes = self .figure .subplots ()
98
96
99
- Note:
100
- At the moment we only handle the default 'light' and 'dark' napari themes.
97
+ def _on_napari_theme_changed (self ):
98
+ """
99
+ Called when the napari theme is changed.
101
100
"""
102
101
self ._replace_toolbar_icons ()
103
- if self .figure .gca ():
104
- self .apply_napari_colorscheme (self .figure .gca ())
105
102
106
- def _theme_has_light_bg (self ) -> bool :
103
+ def _napari_theme_has_light_bg (self ) -> bool :
107
104
"""
108
105
Does this theme have a light background?
109
106
@@ -124,7 +121,7 @@ def _get_path_to_icon(self) -> Path:
124
121
https://github.com/matplotlib/matplotlib/tree/main/lib/matplotlib/mpl-data/images
125
122
"""
126
123
icon_root = Path (__file__ ).parent / "icons"
127
- if self ._theme_has_light_bg ():
124
+ if self ._napari_theme_has_light_bg ():
128
125
return icon_root / "black"
129
126
else :
130
127
return icon_root / "white"
@@ -211,6 +208,17 @@ def current_z(self) -> int:
211
208
"""
212
209
return self .viewer .dims .current_step [0 ]
213
210
211
+ def _on_napari_theme_changed (self ) -> None :
212
+ """Update MPL toolbar and axis styling when `napari.Viewer.theme` is changed.
213
+
214
+ Note:
215
+ At the moment we only handle the default 'light' and 'dark' napari themes.
216
+ """
217
+ super ()._on_napari_theme_changed ()
218
+ self .clear ()
219
+ self .draw ()
220
+
221
+
214
222
def _setup_callbacks (self ) -> None :
215
223
"""
216
224
Sets up callbacks.
@@ -240,12 +248,14 @@ def _draw(self) -> None:
240
248
Clear current figure, check selected layers are correct, and draw new
241
249
figure if so.
242
250
"""
243
- self .clear ()
251
+ # Clearing axes sets new defaults, so need to make sure style is applied when
252
+ # this happens
253
+ with mplstyle .context (self .mpl_style_sheet_path ):
254
+ self .clear ()
244
255
if self .n_selected_layers in self .n_layers_input and all (
245
256
isinstance (layer , self .input_layer_types ) for layer in self .layers
246
257
):
247
258
self .draw ()
248
- self .apply_napari_colorscheme (self .figure .gca ())
249
259
self .canvas .draw ()
250
260
251
261
def clear (self ) -> None :
@@ -288,7 +298,8 @@ def clear(self) -> None:
288
298
"""
289
299
Clear the axes.
290
300
"""
291
- self .axes .clear ()
301
+ with mplstyle .context (self .mpl_style_sheet_path ):
302
+ self .axes .clear ()
292
303
293
304
294
305
class NapariNavigationToolbar (NavigationToolbar2QT ):
0 commit comments