Okay so I had a bit of a play around and came across something interesting (for me).
The following version adds a DynamicMap callback that listens to page.dark_theme changes.
I have commented the additional code and also made the background colours more distinct.
Is there an explanation why, in the first âclassicâ plot, the background doesnât change when the theme is toggled, whereas in the second plot/dmap it only changes after the first toggle?
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": "#3e3ebd", # adapted to blue
"border": "#11111a",
"line": "#FF4F00",
"grid": "#33333a",
"text": "#e0e0e0",
},
False: { # light
"bg": "#f98a8a", # adapted to red
"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=400,
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",
),
],
)
# NOTE: additional code starts here
curve_for_dmap = hv.Curve((x, y))
def plot_dmap(dark_theme: bool):
colors = PLOT_COLORS[bool(dark_theme)]
return curve_for_dmap.opts(
responsive=True,
height=400,
color=colors["line"],
line_width=2,
bgcolor=colors["bg"],
hooks=[_style_hook(colors)],
)
theme_stream = hv.streams.Stream.define("Theme", dark_theme=False)()
dmap = hv.DynamicMap(plot_dmap, streams=[theme_stream])
page.param.watch(lambda _: theme_stream.event(dark_theme=bool(page.dark_theme)), "dark_theme")
# 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)),
dmap
]
page.servable()