88 FigureCanvas ,
99 NavigationToolbar2QT ,
1010)
11+ import matplotlib .style as mplstyle
1112from matplotlib .figure import Figure
1213from qtpy .QtGui import QIcon
1314from qtpy .QtWidgets import QLabel , QVBoxLayout , QWidget
@@ -40,8 +41,11 @@ def __init__(
4041 ):
4142 super ().__init__ (parent = parent )
4243 self .viewer = napari_viewer
44+ self ._mpl_style_sheet_path : Optional [Path ] = None
4345
44- self .canvas = FigureCanvas ()
46+ # Sets figure.* style
47+ with mplstyle .context (self .mpl_style_sheet_path ):
48+ self .canvas = FigureCanvas ()
4549
4650 self .canvas .figure .patch .set_facecolor ("none" )
4751 self .canvas .figure .set_layout_engine ("constrained" )
@@ -52,7 +56,7 @@ def __init__(
5256 # callback to update when napari theme changed
5357 # TODO: this isn't working completely (see issue #140)
5458 # 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 )
5660
5761 self .setLayout (QVBoxLayout ())
5862 self .layout ().addWidget (self .toolbar )
@@ -63,47 +67,40 @@ def figure(self) -> Figure:
6367 """Matplotlib figure."""
6468 return self .canvas .figure
6569
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+
6686 def add_single_axes (self ) -> None :
6787 """
6888 Add a single Axes to the figure.
6989
7090 The Axes is saved on the ``.axes`` attribute for later access.
7191 """
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 ()
9896
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.
101100 """
102101 self ._replace_toolbar_icons ()
103- if self .figure .gca ():
104- self .apply_napari_colorscheme (self .figure .gca ())
105102
106- def _theme_has_light_bg (self ) -> bool :
103+ def _napari_theme_has_light_bg (self ) -> bool :
107104 """
108105 Does this theme have a light background?
109106
@@ -124,7 +121,7 @@ def _get_path_to_icon(self) -> Path:
124121 https://github.com/matplotlib/matplotlib/tree/main/lib/matplotlib/mpl-data/images
125122 """
126123 icon_root = Path (__file__ ).parent / "icons"
127- if self ._theme_has_light_bg ():
124+ if self ._napari_theme_has_light_bg ():
128125 return icon_root / "black"
129126 else :
130127 return icon_root / "white"
@@ -211,6 +208,17 @@ def current_z(self) -> int:
211208 """
212209 return self .viewer .dims .current_step [0 ]
213210
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+
214222 def _setup_callbacks (self ) -> None :
215223 """
216224 Sets up callbacks.
@@ -240,12 +248,14 @@ def _draw(self) -> None:
240248 Clear current figure, check selected layers are correct, and draw new
241249 figure if so.
242250 """
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 ()
244255 if self .n_selected_layers in self .n_layers_input and all (
245256 isinstance (layer , self .input_layer_types ) for layer in self .layers
246257 ):
247258 self .draw ()
248- self .apply_napari_colorscheme (self .figure .gca ())
249259 self .canvas .draw ()
250260
251261 def clear (self ) -> None :
@@ -288,7 +298,8 @@ def clear(self) -> None:
288298 """
289299 Clear the axes.
290300 """
291- self .axes .clear ()
301+ with mplstyle .context (self .mpl_style_sheet_path ):
302+ self .axes .clear ()
292303
293304
294305class NapariNavigationToolbar (NavigationToolbar2QT ):
0 commit comments