holoviz / panel

Panel: The powerful data exploration & web app framework for Python
https://panel.holoviz.org
BSD 3-Clause "New" or "Revised" License
4.75k stars 517 forks source link

Easy and automated theming of Matplotlib #2588

Open MarcSkovMadsen opened 3 years ago

MarcSkovMadsen commented 3 years ago

I would like it to be easy to use Matplotlib with Panel. One of the things that is not easy (to me) is theming.

I've tried to apply the steps from the Matplotlib Customizing documentation. I seem to be able to change the theme. But only once. If I try to change the theme multiple times (for example if toggling default/ dark theme of Fast template) it does not change again.

I cannot find anything about theming in the Panel Matplotlib Reference Guide to help me.

Thus its hard to use Matplotlib with a dark themed template.

Solution

MarcSkovMadsen commented 3 years ago

Example not working

I don't get the "seaborn-dark" theme applied in the below.

https://user-images.githubusercontent.com/42288570/127454226-c80571b9-370f-480f-a7df-6c2aa348b391.mp4

import panel as pn
import numpy as np

from matplotlib.figure import Figure
from matplotlib import cm
from matplotlib.backends.backend_agg import FigureCanvas  # not needed for mpl >= 3.1
import matplotlib.pyplot as plt

pn.extension(sizing_mode="stretch_width")

MPL_CMAPS = {
    "Autumn": cm.autumn,
    'Spring': cm.spring,
    'Summer': cm.summer,
    'Winter': cm.winter,
}
MPL_THEME = {
    pn.template.DefaultTheme: "default",
    pn.template.DarkTheme: "seaborn-dark",
}

template=pn.template.FastListTemplate(
    title="Matplotlib Theming",
    main_max_width="80%",
)
plt.style.use("default") # reset
mpl_theme=MPL_THEME[template.theme]
print(template.theme, mpl_theme)
plt.style.use(mpl_theme)

def get_plot(cmap="autumn"):
    Y, X = np.mgrid[-3:3:100j, -3:3:100j]
    U = -1 - X**2 + Y
    V = 1 + X - Y**2
    speed = np.sqrt(U*U + V*V)

    fig0 = Figure(figsize=(20, 12))
    ax0 = fig0.subplots()
    # FigureCanvas(fig0)  # not needed for mpl >= 3.1

    strm = ax0.streamplot(X, Y, U, V, color=U, linewidth=2, cmap=MPL_CMAPS[cmap])
    fig0.colorbar(strm.lines)

    return fig0

select = pn.widgets.Select(name="Color Map", options=list(MPL_CMAPS.keys()))
get_plot_interactive=pn.bind(get_plot, cmap=select)

component=pn.Column(
    select,
    pn.panel(get_plot_interactive, sizing_mode="stretch_both", loading_indicator=True),
    sizing_mode="stretch_both"
)
template.main.append(component)
template.servable()
MarcSkovMadsen commented 3 years ago

Example that works

This one seems to work. It's just the seaborn-dark that does not work apparently.

https://user-images.githubusercontent.com/42288570/127457503-3b8cff40-7417-4ba7-ad55-95ac07f85263.mp4

import panel as pn
import numpy as np

from matplotlib.figure import Figure
from matplotlib import cm
from matplotlib.backends.backend_agg import FigureCanvas  # not needed for mpl >= 3.1
import matplotlib.pyplot as plt

pn.extension(sizing_mode="stretch_width")

MPL_CMAPS = {
    "Autumn": cm.autumn,
    'Spring': cm.spring,
    'Summer': cm.summer,
    'Winter': cm.winter,
}
MPL_THEME = {
    pn.template.DefaultTheme: "default",
    pn.template.DarkTheme: "dark_background",
}

template=pn.template.FastListTemplate(
    title="Matplotlib Theming",
    main_max_width="80%",
)
plt.style.use("default") # reset
plt.style.use(MPL_THEME[template.theme])

def get_plot(cmap="autumn"):
    Y, X = np.mgrid[-3:3:100j, -3:3:100j]
    U = -1 - X**2 + Y
    V = 1 + X - Y**2
    speed = np.sqrt(U*U + V*V)

    fig0 = Figure(figsize=(20, 12))
    ax0 = fig0.subplots()
    # FigureCanvas(fig0)  # not needed for mpl >= 3.1

    strm = ax0.streamplot(X, Y, U, V, color=U, linewidth=2, cmap=MPL_CMAPS[cmap])
    fig0.colorbar(strm.lines)

    return fig0

select = pn.widgets.Select(name="Color Map", options=list(MPL_CMAPS.keys()))
get_plot_interactive=pn.bind(get_plot, cmap=select)

component=pn.Column(
    select,
    pn.panel(get_plot_interactive, sizing_mode="stretch_both", loading_indicator=True),
    sizing_mode="stretch_both"
)
template.main.append(component)
template.servable()
MarcSkovMadsen commented 3 years ago

Even though I can get something working I think the proposed solution would still be quite valuable.

philippjfr commented 3 years ago

Just looked at this and unfortunately there doesn't seem to be a nice way to do this because of the way Matplotlib theming works. Matplotlib themes work by setting the rcParams, which then determine the defaults during figure and artist creation, once created it is not easily possible to apply a new theme. So to be able to set and unset the theme we would have use context managers around the user code or manually, record, set and restore the rcParams.

The only way I can possibly see this working is:

  1. During the creation of a server session record the current state of matplotlib rcParams
  2. During the instantiation of a template/theme fire off a callback that sets the current matplotlib theme
  3. Once the server session is created restore the original matplotlib rcParams
  4. Whenever a callback fires on a server session do the same record, set and restore steps

While possible that's pretty awful to implement.