diff --git a/docs/user_guide.rst b/docs/user_guide.rst index 15b44f44..0872e540 100644 --- a/docs/user_guide.rst +++ b/docs/user_guide.rst @@ -36,3 +36,14 @@ To use these: 1. Open the desired widget using the ``Plugins > napari-matplotlib`` menu in napari. 2. Select a single layer that has a features table using the napari layers list in the bottom left-hand side of the window. 3. Use the drop down menu(s) under the Matplotlib figure to select the feature(s) to plot. + +Customising plots +----------------- +`Matplotlib style sheets `__ can be used to customise +the plots generated by ``napari-matplotlib``. +To use a custom style sheet: + +1. Save it as ``napari-matplotlib.mplstyle`` +2. Put it in the Matplotlib configuration directory. + The location of this directory varies on different computers, + and can be found by calling :func:`matplotlib.get_configdir()`. diff --git a/src/napari_matplotlib/base.py b/src/napari_matplotlib/base.py index 89c6d3de..792b5aff 100644 --- a/src/napari_matplotlib/base.py +++ b/src/napari_matplotlib/base.py @@ -2,6 +2,7 @@ from pathlib import Path from typing import List, Optional, Tuple +import matplotlib import matplotlib.style as mplstyle import napari from matplotlib.backends.backend_qtagg import ( @@ -16,6 +17,10 @@ __all__ = ["BaseNapariMPLWidget", "NapariMPLWidget", "SingleAxesWidget"] +_CUSTOM_STYLE_PATH = ( + Path(matplotlib.get_configdir()) / "napari-matplotlib.mplstyle" +) + class BaseNapariMPLWidget(QWidget): """ @@ -46,7 +51,6 @@ def __init__( with mplstyle.context(self.mpl_style_sheet_path): self.canvas = FigureCanvas() - self.canvas.figure.patch.set_facecolor("none") self.canvas.figure.set_layout_engine("constrained") self.toolbar = NapariNavigationToolbar( self.canvas, parent=self @@ -73,6 +77,8 @@ def mpl_style_sheet_path(self) -> Path: """ if self._mpl_style_sheet_path is not None: return self._mpl_style_sheet_path + elif (_CUSTOM_STYLE_PATH).exists(): + return _CUSTOM_STYLE_PATH elif self._napari_theme_has_light_bg(): return Path(__file__).parent / "styles" / "light.mplstyle" else: diff --git a/src/napari_matplotlib/tests/test_theme.py b/src/napari_matplotlib/tests/test_theme.py index 7af61f17..a3642f8f 100644 --- a/src/napari_matplotlib/tests/test_theme.py +++ b/src/napari_matplotlib/tests/test_theme.py @@ -1,10 +1,15 @@ +import os +import shutil from copy import deepcopy +from pathlib import Path +import matplotlib import napari import numpy as np import pytest +from matplotlib.colors import to_rgba -from napari_matplotlib import ScatterWidget +from napari_matplotlib import HistogramWidget, ScatterWidget from napari_matplotlib.base import NapariMPLWidget @@ -140,3 +145,46 @@ def test_custom_theme(make_napari_viewer, theme_path, brain_data): viewer.layers.selection.add(viewer.layers[1]) return deepcopy(widget.figure) + + +def find_mpl_stylesheet(name: str) -> Path: + """Find the built-in matplotlib stylesheet.""" + return Path(matplotlib.__path__[0]) / f"mpl-data/stylelib/{name}.mplstyle" + + +def test_custom_stylesheet(make_napari_viewer, image_data): + """ + Test that a stylesheet in the current directory is given precidence. + + Do this by copying over a stylesheet from matplotlib's built in styles, + naming it correctly, and checking the colours are as expected. + """ + # Copy Solarize_Light2 as if it was a user-overriden stylesheet. + style_sheet_path = ( + Path(matplotlib.get_configdir()) / "napari-matplotlib.mplstyle" + ) + if style_sheet_path.exists(): + pytest.skip("Won't ovewrite existing custom style sheet.") + shutil.copy( + find_mpl_stylesheet("Solarize_Light2"), + style_sheet_path, + ) + + try: + viewer = make_napari_viewer() + viewer.add_image(image_data[0], **image_data[1]) + widget = HistogramWidget(viewer) + assert widget.mpl_style_sheet_path == style_sheet_path + ax = widget.figure.gca() + + # The axes should have a light brownish grey background: + assert ax.get_facecolor() == to_rgba("#eee8d5") + assert ax.patch.get_facecolor() == to_rgba("#eee8d5") + + # The figure background and axis gridlines are light yellow: + assert widget.figure.patch.get_facecolor() == to_rgba("#fdf6e3") + for gridline in ax.get_xgridlines() + ax.get_ygridlines(): + assert gridline.get_visible() is True + assert gridline.get_color() == "#fdf6e3" + finally: + os.remove(style_sheet_path)