Skip to content

Commit abd00bd

Browse files
committed
MVP for using style sheets
1 parent 14b2b3e commit abd00bd

File tree

7 files changed

+80
-41
lines changed

7 files changed

+80
-41
lines changed

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
include LICENSE
22
include README.md
3+
recursive-include * *.mplstyle
34

45
recursive-exclude * __pycache__
56
recursive-exclude * *.py[co]

src/napari_matplotlib/base.py

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
from pathlib import Path
33
from typing import List, Optional, Tuple
44

5+
import matplotlib.style as mplstyle
56
import napari
6-
from matplotlib.axes import Axes
77
from matplotlib.backends.backend_qtagg import (
88
FigureCanvas,
99
NavigationToolbar2QT,
@@ -40,8 +40,11 @@ def __init__(
4040
):
4141
super().__init__(parent=parent)
4242
self.viewer = napari_viewer
43+
self._mpl_style_sheet_path: Optional[Path] = None
4344

44-
self.canvas = FigureCanvas()
45+
# Sets figure.* style
46+
with mplstyle.context(self.mpl_style_sheet_path):
47+
self.canvas = FigureCanvas()
4548

4649
self.canvas.figure.patch.set_facecolor("none")
4750
self.canvas.figure.set_layout_engine("constrained")
@@ -52,7 +55,7 @@ def __init__(
5255
# callback to update when napari theme changed
5356
# TODO: this isn't working completely (see issue #140)
5457
# most of our styling respects the theme change but not all
55-
self.viewer.events.theme.connect(self._on_theme_change)
58+
self.viewer.events.theme.connect(self._on_napari_theme_changed)
5659

5760
self.setLayout(QVBoxLayout())
5861
self.layout().addWidget(self.toolbar)
@@ -63,47 +66,40 @@ def figure(self) -> Figure:
6366
"""Matplotlib figure."""
6467
return self.canvas.figure
6568

69+
@property
70+
def mpl_style_sheet_path(self) -> Path:
71+
"""
72+
Path to the set Matplotlib style sheet.
73+
"""
74+
if self._mpl_style_sheet_path is not None:
75+
return self._mpl_style_sheet_path
76+
elif self._napari_theme_has_light_bg():
77+
return Path(__file__).parent / "styles" / "light.mplstyle"
78+
else:
79+
return Path(__file__).parent / "styles" / "dark.mplstyle"
80+
81+
@mpl_style_sheet_path.setter
82+
def mpl_style_sheet_path(self, path: Path) -> None:
83+
self._mpl_style_sheet_path = Path(path)
84+
6685
def add_single_axes(self) -> None:
6786
"""
6887
Add a single Axes to the figure.
6988
7089
The Axes is saved on the ``.axes`` attribute for later access.
7190
"""
72-
self.axes = self.figure.subplots()
73-
self.apply_napari_colorscheme(self.axes)
91+
# Sets axes.* style.
92+
# Does not set any text styling set by axes.* keys
93+
with mplstyle.context(self.mpl_style_sheet_path):
94+
self.axes = self.figure.subplots()
7495

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.
98-
99-
Note:
100-
At the moment we only handle the default 'light' and 'dark' napari themes.
96+
def _on_napari_theme_changed(self) -> None:
97+
"""
98+
Called when the napari theme is changed.
10199
"""
102100
self._replace_toolbar_icons()
103-
if self.figure.gca():
104-
self.apply_napari_colorscheme(self.figure.gca())
105101

106-
def _theme_has_light_bg(self) -> bool:
102+
def _napari_theme_has_light_bg(self) -> bool:
107103
"""
108104
Does this theme have a light background?
109105
@@ -124,7 +120,7 @@ def _get_path_to_icon(self) -> Path:
124120
https://github.com/matplotlib/matplotlib/tree/main/lib/matplotlib/mpl-data/images
125121
"""
126122
icon_root = Path(__file__).parent / "icons"
127-
if self._theme_has_light_bg():
123+
if self._napari_theme_has_light_bg():
128124
return icon_root / "black"
129125
else:
130126
return icon_root / "white"
@@ -211,6 +207,16 @@ def current_z(self) -> int:
211207
"""
212208
return self.viewer.dims.current_step[0]
213209

210+
def _on_napari_theme_changed(self) -> None:
211+
"""Update MPL toolbar and axis styling when `napari.Viewer.theme` is changed.
212+
213+
Note:
214+
At the moment we only handle the default 'light' and 'dark' napari themes.
215+
"""
216+
super()._on_napari_theme_changed()
217+
self.clear()
218+
self.draw()
219+
214220
def _setup_callbacks(self) -> None:
215221
"""
216222
Sets up callbacks.
@@ -240,12 +246,14 @@ def _draw(self) -> None:
240246
Clear current figure, check selected layers are correct, and draw new
241247
figure if so.
242248
"""
243-
self.clear()
249+
# Clearing axes sets new defaults, so need to make sure style is applied when
250+
# this happens
251+
with mplstyle.context(self.mpl_style_sheet_path):
252+
self.clear()
244253
if self.n_selected_layers in self.n_layers_input and all(
245254
isinstance(layer, self.input_layer_types) for layer in self.layers
246255
):
247256
self.draw()
248-
self.apply_napari_colorscheme(self.figure.gca())
249257
self.canvas.draw()
250258

251259
def clear(self) -> None:
@@ -288,7 +296,8 @@ def clear(self) -> None:
288296
"""
289297
Clear the axes.
290298
"""
291-
self.axes.clear()
299+
with mplstyle.context(self.mpl_style_sheet_path):
300+
self.axes.clear()
292301

293302

294303
class NapariNavigationToolbar(NavigationToolbar2QT):

src/napari_matplotlib/scatter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ def draw(self) -> None:
2323
"""
2424
Scatter the currently selected layers.
2525
"""
26+
if len(self.layers) == 0:
27+
return
2628
x, y, x_axis_name, y_axis_name = self._get_data()
2729

2830
if x.size > self._threshold_to_switch_to_histogram:
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This folder contains default built-in Matplotlib style sheets.
2+
See https://matplotlib.org/stable/tutorials/introductory/customizing.html#defining-your-own-style
3+
for more info on Matplotlib style sheets.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Dark-theme napari colour scheme for matplotlib plots
2+
3+
# text (very light grey - almost white): #f0f1f2
4+
# foreground (mid grey): #414851
5+
# background (dark blue-gray): #262930
6+
7+
figure.facecolor : none
8+
axes.labelcolor : f0f1f2
9+
axes.facecolor : none
10+
axes.edgecolor : 414851
11+
xtick.color : f0f1f2
12+
ytick.color : f0f1f2
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Light-theme napari colour scheme for matplotlib plots
2+
3+
# text (): #3b3a39
4+
# foreground (): #d6d0ce
5+
# background (): #efebe9
6+
7+
figure.facecolor : none
8+
axes.labelcolor : 3b3a39
9+
axes.facecolor : none
10+
axes.edgecolor : d6d0ce
11+
xtick.color : 3b3a39
12+
ytick.color : 3b3a39

src/napari_matplotlib/tests/test_theme.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ def test_theme_background_check(make_napari_viewer):
4343
widget = NapariMPLWidget(viewer)
4444

4545
viewer.theme = "dark"
46-
assert widget._theme_has_light_bg() is False
46+
assert widget._napari_theme_has_light_bg() is False
4747

4848
viewer.theme = "light"
49-
assert widget._theme_has_light_bg() is True
49+
assert widget._napari_theme_has_light_bg() is True
5050

5151
_mock_up_theme()
5252
viewer.theme = "blue"
53-
assert widget._theme_has_light_bg() is True
53+
assert widget._napari_theme_has_light_bg() is True
5454

5555

5656
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy