From 669cdc26bb6091f1209242b367820a2a6280233a Mon Sep 17 00:00:00 2001 From: Sam Cunliffe Date: Thu, 8 Jun 2023 09:53:09 +0100 Subject: [PATCH 1/3] Test first. A TDD test to check we can set the theme from a user-defined stylesheet. For this, just use Solarized_Light2. --- src/napari_matplotlib/tests/test_theme.py | 38 ++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/napari_matplotlib/tests/test_theme.py b/src/napari_matplotlib/tests/test_theme.py index 7af61f17..fb1416fe 100644 --- a/src/napari_matplotlib/tests/test_theme.py +++ b/src/napari_matplotlib/tests/test_theme.py @@ -1,10 +1,15 @@ from copy import deepcopy +import shutil +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,34 @@ 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_stylesheet_in_cwd(tmpdir, 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. + """ + with tmpdir.as_cwd(): + # Copy Solarize_Light2 to current dir as if it was a user-overriden stylesheet. + shutil.copy(find_mpl_stylesheet("Solarize_Light2"), "./user.mplstyle") + viewer = make_napari_viewer() + viewer.add_image(image_data[0], **image_data[1]) + widget = HistogramWidget(viewer) + 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" From 13d22686764bb2badc973dca6331eafd7a15c135 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Mon, 26 Jun 2023 20:42:24 +0100 Subject: [PATCH 2/3] Add support for custom style sheets --- docs/user_guide.rst | 11 ++++++ src/napari_matplotlib/base.py | 8 +++- src/napari_matplotlib/tests/test_theme.py | 48 +++++++++++++---------- 3 files changed, 46 insertions(+), 21 deletions(-) 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 fb1416fe..49738abd 100644 --- a/src/napari_matplotlib/tests/test_theme.py +++ b/src/napari_matplotlib/tests/test_theme.py @@ -1,6 +1,5 @@ -from copy import deepcopy - import shutil +from copy import deepcopy from pathlib import Path import matplotlib @@ -152,27 +151,36 @@ def find_mpl_stylesheet(name: str) -> Path: return Path(matplotlib.__path__[0]) / f"mpl-data/stylelib/{name}.mplstyle" -def test_stylesheet_in_cwd(tmpdir, make_napari_viewer, image_data): +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. """ - with tmpdir.as_cwd(): - # Copy Solarize_Light2 to current dir as if it was a user-overriden stylesheet. - shutil.copy(find_mpl_stylesheet("Solarize_Light2"), "./user.mplstyle") - viewer = make_napari_viewer() - viewer.add_image(image_data[0], **image_data[1]) - widget = HistogramWidget(viewer) - 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" + # 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, + ) + + 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" From 3e6bf2c2ff899145c11cf056cc28c64849c26090 Mon Sep 17 00:00:00 2001 From: David Stansby Date: Mon, 26 Jun 2023 20:58:25 +0100 Subject: [PATCH 3/3] Delte style sheet after test --- src/napari_matplotlib/tests/test_theme.py | 34 +++++++++++++---------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/napari_matplotlib/tests/test_theme.py b/src/napari_matplotlib/tests/test_theme.py index 49738abd..a3642f8f 100644 --- a/src/napari_matplotlib/tests/test_theme.py +++ b/src/napari_matplotlib/tests/test_theme.py @@ -1,3 +1,4 @@ +import os import shutil from copy import deepcopy from pathlib import Path @@ -169,18 +170,21 @@ def test_custom_stylesheet(make_napari_viewer, image_data): style_sheet_path, ) - 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" + 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)