Customize theme-dependent plot colors using panel/holoviews

When using for example pmui.Page together with HoloViews (Bokeh backend), the plot appearance automatically changes when toggling between light and dark mode.

I now want to override some of the colors that HoloViews/Bokeh uses when the theme changes, i.e. define custom colors for light and dark mode, maybe use another background_fill_color etc.

What is the recommended or supported way in Panel / panel_material_ui to achieve this? Using plot hooks for light and dark mode separately, based on the theme toggle of the Page? Access the theme parameter of pn.pane.HoloViews and use something like pn.bind(update_plot, page.param.dark_theme) (see How to adjust theme on the fly)?

MRE basis:

import holoviews as hv
import numpy as np
import panel as pn
import panel_material_ui as pmui

hv.extension('bokeh')
pn.extension()

x = np.arange(100)
y = np.cumsum(np.random.randn(100))

curve = hv.Curve((x, y)).opts(
    responsive=True,
    height=500
)

hv_pane = pn.pane.HoloViews(curve)

page = pmui.Page(main=[hv_pane])

page.preview()

Not entirely sure, but maybe this can generate some insights

If that doesn’t work, feel free to raise an issue on panel / pmui

I used the holoviz-skills branded apps example (url inside the example code below) when I started testing PMUI in my dashboards.

import holoviews as hv
import numpy as np
import panel as pn
import panel_material_ui as pmui

pn.extension()
hv.extension("bokeh")

THEME = {
    "light": {
        "palette": {
            "primary": {"main": "#4099da"},
            "secondary": {"main": "#644c76"},
        },
        "typography": {
            "fontFamily": "Montserrat, sans-serif",
            "fontSize": 14,
        },
        "shape": {"borderRadius": 8},
    },
    "dark": {
        "palette": {
            "primary": {"main": "#FF4F00"},
            "secondary": {"main": "#9575cd"},
        },
        "typography": {
            "fontFamily": "Montserrat, sans-serif",
            "fontSize": 14,
        },
        "shape": {"borderRadius": 8},
    },
}

# Custom HoloViews/Bokeh plot colours per mode (keyed by dark_theme bool).
PLOT_COLORS = {
    True: {  # dark
        "bg": "#11111a",
        "border": "#11111a",
        "line": "#FF4F00",
        "grid": "#33333a",
        "text": "#e0e0e0",
    },
    False: {  # light
        "bg": "#ffffff",
        "border": "#ffffff",
        "line": "#4099da",
        "grid": "#e0e0e0",
        "text": "#222222",
    },
}

x = np.arange(100)
y = np.cumsum(np.random.randn(100))


def _style_hook(colors):
    """Return a HoloViews hook that paints the Bokeh figure with ``colors``."""

    def hook(plot, _element):
        fig = plot.handles["plot"]
        fig.background_fill_color = colors["bg"]
        fig.border_fill_color = colors["border"]
        for axis in (fig.xaxis, fig.yaxis):
            axis.axis_label_text_color = colors["text"]
            axis.major_label_text_color = colors["text"]
            axis.axis_line_color = colors["grid"]
            axis.major_tick_line_color = colors["grid"]
            axis.minor_tick_line_color = colors["grid"]
        for grid in (fig.xgrid, fig.ygrid):
            grid.grid_line_color = colors["grid"]

    return hook


def plot(dark_theme: bool):
    """Curve restyled to match the active (light/dark) Page theme."""
    colors = PLOT_COLORS[bool(dark_theme)]
    return hv.Curve((x, y)).opts(
        responsive=True,
        height=500,
        color=colors["line"],
        line_width=2,
        bgcolor=colors["bg"],
        hooks=[_style_hook(colors)],
    )


page = pmui.Page(
    title="Branded App",
    theme_config=THEME,
    sidebar=[
        pmui.Button(label="Action", icon="bolt", color="primary"),
        pmui.Button(
            label="Branding docs",
            icon="link",
            color="primary",
            variant="outlined",
            href="https://holoviz-dev.github.io/holoviz-skills/developing-with-holoviz/panel/branding-material-ui/",
            target="_blank",
        ),
    ],
)
# Bind the plot to the Page's theme toggle so its colours follow light/dark.
page.main = [
    pmui.Typography("Welcome", variant="h4"),
    pn.panel(pn.bind(plot, page.param.dark_theme)),
]
page.servable()

Hopefully this helps :slightly_smiling_face: