简体   繁体   中英

Is there a way to change the stylesheet when saving a figure in matplotlib?

Is it possible to temporarily change the style sheet of a figure when saving it, without having to replot it (twice)? I know that you can pass the facecolor and edgecolor of the figure as parameters to savefig , but I don't understand why it would be limited to only these two parameters. You don't seem to have any influence on the rcParams of the axes instance(s). Matplotlib's style sheet context manager only affects the style sheet of the figure. Here is an MWE to play around with:

import matplotlib.pyplot as plt


fig, ax = plt.subplots()
ax.plot((1, 2), (0, 0), '--')

with plt.style.context('dark_background'):
    # if you replot it, it works, however, this messses up the style outside
    # this context as well
    # fig.clf()
    # ax = fig.add_subplot(111)
    # ax.plot((1, 2), (0, 0), '--')
    fig.savefig('test.png')

# you would need to replot everything again for the style to be correct again
# fig.clf()
# ax = fig.add_subplot(111)
# ax.plot((1, 2), (0, 0), '--')
fig.show()

Ok, so after a whole lot of research ad trial and error, I have finally found a way that works, although complex. The basic idea is to write your own ('smart') context manager, that understands what you want to do on an abstract level, and then makes the appropriate changes. Eg if you want to change the axes edgecolor of a figure it knows which color changes are associated with it.

In the case of changing the edgecolor , there are quite a few obstacles that you need to be aware of, since changing the (edge-)color of the associated artists unfortunately works differently for different artists . Here are some of the caveats that I came across, the list is of course by no means comprehensive, since I am sore for more complex figures, there are also other things that you need to take into consideration:

I'm sure there are good reasons why the things work the way they do, but from the simple idea of wanting to change the edgecolor, it seems unnecessarily arbitrary.

Below is the code of the context manager applied to a simple example figure. I am sure there are many ways of how to improve this approach though.

import matplotlib.pyplot as plt
from matplotlib.spines import Spine
from matplotlib.patches import Polygon


class StyleChange:
    def __init__(self, color):
        self._color = color
        self._original_colors = {}

    def apply(self, fig):
        pass

    def revert(self):
        pass


class AxesEdgeColorChange(StyleChange):
    def apply(self, fig):
        for ax in fig.axes:
            self._original_colors[ax] = {}
            ticks = [*ax.get_xticklines(), *ax.get_yticklines()]
            mticks = [
                *ax.get_xaxis().get_minor_ticks(),
                *ax.get_yaxis().get_minor_ticks(),
            ]
            labels = [*ax.get_xticklabels(), *ax.get_yticklabels()]
            spines = ax.spines.values()
            cbars = [
                im.colorbar.outline for im in ax.images
                if (im is not None and im.colorbar is not None)
            ]
            for artist in [*ticks, *mticks, *labels, *spines, *cbars]:
                self._original_colors[ax][artist] = self._get_color(artist)
                self._set_color(artist)

    def revert(self):
        for axes_artists in self._original_colors.values():
            for artist, color in axes_artists.items():
                self._set_color(artist, color)

    @staticmethod
    def _get_color(artist):
        if isinstance(artist, (Spine, Polygon)):
            return artist.get_edgecolor()

        return artist.get_color()

    def _set_color(self, artist, color=None):
        if color is None:
            color = self._color
        if isinstance(artist, Polygon):
            artist.set_edgecolor(color)
        else:
            artist.set_color(color)


class AxesFaceColorChange(StyleChange):
    def apply(self, fig):
        for ax in fig.axes:
            self._original_colors[ax] = ax.get_facecolor()
            ax.set_facecolor(self._color)

    def revert(self):
        for ax, color in self._original_colors.items():
            ax.set_facecolor(color)


class StyleChangeManager:
    def __init__(
            self, fig: plt.Figure, *style_changes: StyleChange,
            redraw: bool = False
    ):
        self._fig = fig
        self._style_changes = style_changes
        self._redraw = redraw

    def __enter__(self):
        for change in self._style_changes:
            change.apply(self._fig)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        for change in self._style_changes:
            change.revert()
        if self._redraw:
            self._fig.canvas.draw_idle()


def example():
    image = np.random.uniform(0, 1, (100, 100))
    fig, ax = plt.subplots()
    image_artist = ax.imshow(image)
    fig.colorbar(image_artist)
    ec_change = AxesEdgeColorChange('red')
    with StyleChangeManager(fig, ec_change):
        fig.show()

    fig.show()


if __name__ == '__main__':
    import numpy as np

    example()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM