Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: add reusable matplotlib stylesheet #4343

Merged
merged 6 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ recursive-include yt/visualization/volume_rendering/shaders *.fragmentshader *.v
include yt/sample_data_registry.json
include conftest.py
include yt/py.typed
include yt/default.mplstyle

prune yt/frontends/_skeleton
recursive-include yt/frontends/amrvac *.par
exclude .codecov.yml .coveragerc .git-blame-ignore-revs .gitmodules .hgchurn .mailmap
Expand Down
2 changes: 1 addition & 1 deletion conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def pytest_configure(config):
r"Use Palette\.ADAPTIVE instead\.:DeprecationWarning",
)

if NUMPY_VERSION < Version("1.19") and MPL_VERSION < Version("3.3"):
if NUMPY_VERSION < Version("1.19") and MPL_VERSION < Version("3.4"):
# This warning is triggered from matplotlib in exactly one test at the time of writing
# and exclusively on the minimal test env. Upgrading numpy or matplotlib resolves
# the issue, so we can afford to ignore it.
Expand Down
77 changes: 77 additions & 0 deletions doc/source/visualizing/plots.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2419,3 +2419,80 @@ an example that includes slices and phase plots:
)

mp.save_fig("multi_slice_phase")


Using yt's style with matplotlib
--------------------------------

It is possible to use yt's plot style in outside of yt itself, with the
:func:`~yt.funcs.matplotlib_style_context` context manager

.. code-block:: python

import matplotlib.pyplot as plt
import numpy as np
import yt

plt.rcParams["font.size"] = 14

x = np.linspace(-np.pi, np.pi, 100)
y = np.sin(x)

with yt.funcs.matplotlib_style_context():
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set(
xlabel=r"$x$",
ylabel=r"$y$",
title="A yt-styled matplotlib figure",
)

Note that :func:`~yt.funcs.matplotlib_style_context` doesn't control the font
size, so we adjust it manually in the preamble.

With matplotlib 3.7 and newer, you can avoid importing yt altogether

.. code-block:: python

# requires matplotlib>=3.7
import matplotlib.pyplot as plt
import numpy as np

plt.rcParams["font.size"] = 14

x = np.linspace(-np.pi, np.pi, 100)
y = np.sin(x)

with plt.style.context("yt.default"):
fig, ax = plt.subplots()
ax.plot(x, y)
ax.set(
xlabel=r"$x$",
ylabel=r"$y$",
title="A yt-styled matplotlib figure",
)

and you can also enable yt's style without a context manager as

.. code-block:: python

# requires matplotlib>=3.7
import matplotlib.pyplot as plt
import numpy as np

plt.style.use("yt.default")
plt.rcParams["font.size"] = 14

x = np.linspace(-np.pi, np.pi, 100)
y = np.sin(x)

fig, ax = plt.subplots()
ax.plot(x, y)
ax.set(
xlabel=r"$x$",
ylabel=r"$y$",
title="A yt-styled matplotlib figure",
)

For more details, see `matplotlib's documentation
<https://matplotlib.org/stable/tutorials/introductory/customizing.html#customizing-with-style-sheets>_`
14 changes: 14 additions & 0 deletions yt/default.mplstyle
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# basic usage (requires matplotlib 3.7+)
# >>> import matplotlib as mpl
# >>> mpl.style.use("yt.default")

xtick.top: True
ytick.right: True
xtick.minor.visible: True
ytick.minor.visible: True
xtick.direction: in
ytick.direction: in

font.family: stixgeneral

mathtext.fontset: cm
33 changes: 10 additions & 23 deletions yt/funcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -994,35 +994,22 @@ def get_brewer_cmap(cmap):
return bmap.get_mpl_colormap(N=cmap[2])


@contextlib.contextmanager
def dummy_context_manager(*args, **kwargs):
yield


def matplotlib_style_context(style_name=None, after_reset=False):
def matplotlib_style_context(style="yt.default", after_reset=False):
"""Returns a context manager for controlling matplotlib style.

Arguments are passed to matplotlib.style.context() if specified. Defaults
to setting "classic" style, after resetting to the default config parameters.

On older matplotlib versions (<=1.5.0) where matplotlib.style isn't
available, returns a dummy context manager.
to setting yt's "yt.default" style, after resetting to the default config parameters.
"""
if style_name is None:
import matplotlib
# FUTURE: this function should be deprecated in favour of matplotlib.style.context
# after support for matplotlib 3.6 and older versions is dropped.
import matplotlib.style

style_name = {"mathtext.fontset": "cm"}
if Version(matplotlib.__version__) >= Version("3.3.0"):
style_name["mathtext.fallback"] = "cm"
else:
style_name["mathtext.fallback_to_cm"] = True
try:
import matplotlib.style
from yt.visualization._commons import MPL_VERSION

return matplotlib.style.context(style_name, after_reset=after_reset)
except ImportError:
pass
return dummy_context_manager()
if style == "yt.default" and MPL_VERSION < Version("3.7"):
style = importlib_resources.files("yt") / "default.mplstyle"

return matplotlib.style.context(style, after_reset=after_reset)


interactivity = False
Expand Down
19 changes: 13 additions & 6 deletions yt/visualization/_commons.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@
from importlib.metadata import version
from typing import TYPE_CHECKING, Optional, Type, TypeVar

import matplotlib as mpl
import numpy as np
from more_itertools import always_iterable
from packaging.version import Version

from yt.config import ytcfg

if sys.version_info >= (3, 9):
import importlib.resources as importlib_resources
else:
import importlib_resources

if sys.version_info >= (3, 10):
pass
else:
Expand All @@ -20,15 +26,16 @@
from ._mpl_imports import FigureCanvasBase


DEFAULT_FONT_PROPERTIES = {
"family": "stixgeneral",
"size": 18,
}

MPL_VERSION = Version(version("matplotlib"))

_yt_style = mpl.rc_params_from_file(
importlib_resources.files("yt") / "default.mplstyle", use_default_template=False
)
DEFAULT_FONT_PROPERTIES = {"family": _yt_style["font.family"][0]}

if MPL_VERSION >= Version("3.4"):
DEFAULT_FONT_PROPERTIES["math_fontfamily"] = "cm"
DEFAULT_FONT_PROPERTIES["math_fontfamily"] = _yt_style["mathtext.fontset"]
del _yt_style


def _get_supported_image_file_formats():
Expand Down
7 changes: 4 additions & 3 deletions yt/visualization/plot_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def __init__(self, data_source, figure_size=None, fontsize: Optional[float] = No
if sys.version_info >= (3, 9):
font_dict = DEFAULT_FONT_PROPERTIES | {"size": fontsize}
else:
font_dict = {**DEFAULT_FONT_PROPERTIES, "size": fontsize} # type:ignore
font_dict = {**DEFAULT_FONT_PROPERTIES, "size": fontsize}

self._font_properties = FontProperties(**font_dict)
self._font_color = None
Expand Down Expand Up @@ -451,10 +451,11 @@ def set_font(self, font_dict=None):
self._font_color = font_dict.pop("color")
# Set default values if the user does not explicitly set them.
# this prevents reverting to the matplotlib defaults.
_default_size = {"size": self.__class__._default_font_size}
if sys.version_info >= (3, 9):
font_dict = DEFAULT_FONT_PROPERTIES | font_dict
font_dict = DEFAULT_FONT_PROPERTIES | _default_size | font_dict
else:
font_dict = {**DEFAULT_FONT_PROPERTIES, **font_dict}
font_dict = {**DEFAULT_FONT_PROPERTIES, **_default_size, **font_dict}
self._font_properties = FontProperties(**font_dict)
return self

Expand Down