Theming Light vs Dark on Dash Mantine Provider

:raised_hand: Hi! I’m posting this question, which may could serve others:
I couldn’t figure out how to switch themes on the figure and I think I mixed the right way of switching from light to dark.
A couple of questions:

  1. There are only these two themes on dmc, mantine-light and mantine-dark that matches plotly_white and plotly_dark?
  2. What is the right (or simplest) way to apply the theming to the figure template?
    I tried with Patch() module as well, but it didn’t work.

Any help would be appreciated or some reading literature to clarify me. Thanks in advance!
Thanks @AnnMarieW.
JuanGerman

Below it’s the code that I was trying, but it is messy and it’s highly commented…
Data is here: resorts.csv - Google Drive

Code
from dash import Dash, dcc, html, Input, Output, State, callback, _dash_renderer, no_update, clientside_callback, ALL, Patch
# from dash.exceptions import PreventUpdate
import dash_mantine_components as dmc
from dash_iconify import DashIconify

_dash_renderer._set_react_version("18.2.0")

import plotly.express as px
import plotly.io as pio
import pandas as pd
import numpy as np

resorts = (
    pd.read_csv("../Data/Ski Resorts/resorts.csv", encoding = "ISO-8859-1")
    .query("Country in ['United States', 'Canada']")
)

app = Dash(__name__, external_stylesheets=dmc.styles.ALL)

# dmc.add_figure_templates()
dmc.add_figure_templates(default="mantine_light")
# dmc.add_figure_templates(default="mantine_dark")
# template = pio.templates["mantine_dark"]

# theme = {
#     "primaryColor": "teal",
#     "defaultRadius": "xl",
#     "components": {
#         "Card": {
#             "defaultProps": {
#                 "shadow": "xl"
#             }
#         }
#     }
# }

# theme_toggle = dmc.ActionIcon(
#     [
#         dmc.Paper(DashIconify(icon="radix-icons:sun", width=25), darkHidden=True),
#         dmc.Paper(DashIconify(icon="radix-icons:moon", width=25), lightHidden=True),
#     ],
#     variant="transparent",
#     color="yellow",
#     id="color-scheme-toggle",
#     size="lg",
#     ms="auto",
#     n_clicks=0,
# )

theme_toggle = dmc.Switch(
    offLabel=DashIconify(icon="radix-icons:sun", width=15, color=dmc.DEFAULT_THEME["colors"]["yellow"][8]),
    onLabel=DashIconify(icon="radix-icons:moon", width=15, color=dmc.DEFAULT_THEME["colors"]["yellow"][6]),
    id="color-scheme-switch",
    persistence=True,
    color="grey",
)

dmc_title = dmc.Title(id='title_id', order=3, 
                      children='',
                      )

header = dmc.Group([
    dmc_title,
    theme_toggle,
], justify="space-between")

layout = html.Div([
    header,
    html.Br(),
    dmc.RangeSlider(
        id='price_range_id',
        min=resorts['Price'].min(), # 0
        max=150, # 141
        step=5,
        marks=[{'label': str(val), 'value': val} for val in range(0, 151, 25)],
        value=[50,70],
    ),
    html.Br(),
    dcc.RadioItems(
        id='nigth_option',
        options=[
            {"label": "Has Night Skiing", "value": "Yes"},
            {"label": "No Night Skiing", "value": "No"}],
        value='Yes',
        inline=True,
    ),
    dcc.Graph(
        id='map_id',
        figure={},
    ),
])
app.layout = dmc.MantineProvider(
    [layout],
    id="mantine-provider",
    # theme=theme,
    # forceColorScheme='dark')
)
# @callback(
#     Output("mantine-provider", "forceColorScheme"),
#     Input("color-scheme-toggle", "n_clicks"),
#     State("mantine-provider", "forceColorScheme"),
#     prevent_initial_call=True,
# )
# def switch_theme(_, theme):
#     # template = pio.templates["mantine_light"] if theme == "light" else pio.templates["mantine_dark"]
#     if theme == 'light':
#         return 'dark'
#     else: return 'light'
#     # return template

clientside_callback(
    """ 
    (switchOn) => {
       document.documentElement.setAttribute('data-mantine-color-scheme', switchOn ? 'dark' : 'light');  
       return window.dash_clientside.no_update
    }
    """,
    Output("color-scheme-switch", "id"),
    Input("color-scheme-switch", "checked"),
)


@callback(
    Output('title_id', 'children'),
    Output('map_id', 'figure'),
    Input('price_range_id', 'value'),
    Input('nigth_option', 'value'),
)
def update_resorts(price, nigth):
    
    title = f'Resorts with a ticket price between ${price[0]} and ${price[1]}'
    # print(price)
    mask_price = resorts['Price'].between(price[0],price[1])
    if nigth == 'Yes':
        mask_nigth = resorts['Nightskiing'].eq('Yes')
    else: mask_nigth = resorts['Nightskiing'].eq('No')
    mask = np.logical_and(mask_nigth, mask_price)
    dff = (resorts[mask])
    fig = px.density_map(dff,
                      lat='Latitude',
                      lon='Longitude',
                      z='Total slopes',
                      radius=8,
                      center=dict(lat=45, lon=-100), zoom=2,
                      map_style="light",
                      height=500,
                     )
    return title, fig


# @callback(
#     Output('map_id', "figure", allow_duplicate=True),
#     Input("mantine-provider", "forceColorScheme"),
#     prevent_initial_call=True
# #     State({"type": "graph", "index": ALL}, "id"),
# )
# def update_figure(theme): #ids
# #     # template must be template object rather than just the template string name
#     template = pio.templates["mantine_light"] if theme == "light" else pio.templates["mantine_dark"]
# #     patched_figures = []
# #     for i in ids:
#     patched_fig = Patch()
#     patched_fig["layout"]["template"] = template
# #         patched_figures.append(patched_fig)

#     return patched_fig


if __name__ == '__main__':
    app.run(debug=True, port=8090, jupyter_mode='inline')

Hi @JuanG !

I am using this callback to apply theme changes to plotly figures.

def create_theme_callback(figure_id):
    @callback(
        Output(figure_id, 'figure', allow_duplicate=True),
        Input('theme-store', 'data'),
        prevent_initial_call=True
    )

    def apply_theme(theme):
        template = pio.templates['plotly'] if theme == 'light' else pio.templates['plotly_dark']
        patch = Patch()
        patch.layout.template = template
        return patch

This uses the basic plotly templates, but @AnnMarieW added mantine figure templates

Hope this helps,
kind regards

2 Likes

Thanks @Datenschubse ! Could you articulate how you get your Input: Input('theme-store', 'data') :thinking:

There are several of different ways to change the theme and update the figure templates.

It looks like you found the sections of the docs:

  • Plotly figure templates which describes how to use the figure templates in general, or to use the provided Mantine Themed Plotly figure templates. And yes, by default there are only two included with dmc, one for light and one for dark, but there is also an example of how to modify the templates if you want to make your own customization.

  • Theme Switch Components. There are two examples. The first is a simple one that uses an ActionIcon so it looks like the theme switch component used in the docs. It’s a regular callback that updates the forceColorScheme prop in the MantineProvider and then uses this prop as the input of a callback to determine which figure template to update. There is another example of using a Switch component and clientside callback to update the theme. It looks like @Datenschubse uses a method where the theme switch component updates a dcc.Store, then the dcc.Store is used as the input of a callback.

  • You can find more complete examples of apps with theme switch components in the dmc PyCafe

Here is one more complete example. It uses a dmc.Switch componet for the user to select the light dark mode. I like the switch because it’s easy to set persistence. It uses a clientside callback to set the theme, which is efficient. Then to update the figure, it uses the switch as the input of the callback, then updates the figure template using Patch.



import dash_mantine_components as dmc
from dash import Dash, dcc, Input, Output,  callback, Patch, clientside_callback, _dash_renderer
from dash_iconify import DashIconify
import plotly.express as px
import plotly.io as pio
_dash_renderer._set_react_version("18.2.0")

dmc.add_figure_templates(default="mantine_light")

df = px.data.gapminder()

dff = df[df.year == 2007]
avg_lifeExp = (dff["lifeExp"] * dff["pop"]).sum() / dff["pop"].sum()
map_fig = px.choropleth(
    dff,
    locations="iso_alpha",
    color="lifeExp",
    title="%.0f World Average Life Expectancy was %.1f years" % (2007, avg_lifeExp),
)

app = Dash(external_stylesheets=dmc.styles.ALL)


theme_toggle = dmc.Switch(
    offLabel=DashIconify(icon="radix-icons:sun", width=15, color=dmc.DEFAULT_THEME["colors"]["yellow"][8]),
    onLabel=DashIconify(icon="radix-icons:moon", width=15, color=dmc.DEFAULT_THEME["colors"]["yellow"][6]),
    id="color-scheme-switch",
    persistence=True,
    color="grey",
)

sample_controls = dmc.Box([
    dmc.Button("sample button"),
    dmc.Button("sample red button", color="red"),
    dmc.Button("sample yellow button", color="yellow"),
    dmc.Slider(value=25, my="lg"),
], w=600)

app.layout = dmc.MantineProvider(
    dmc.Box([ theme_toggle, sample_controls, dcc.Graph(figure=map_fig, id="map")]),
    forceColorScheme="dark"
)

@callback(
    Output("map", "figure"),
    Input("color-scheme-switch", "checked"),
)
def update_figure(switch_on):
    # template must be template object rather than just the template string name
    template = pio.templates["mantine_dark"] if switch_on  else pio.templates["mantine_light"]
    patched_figure = Patch()
    patched_figure["layout"]["template"] = template

    return patched_figure




clientside_callback(
    """ 
    (switchOn) => {
       document.documentElement.setAttribute('data-mantine-color-scheme', switchOn ? 'dark' : 'light');  
       return window.dash_clientside.no_update
    }
    """,
    Output("color-scheme-switch", "id"),
    Input("color-scheme-switch", "checked"),
)

if __name__ == "__main__":
    app.run(debug=True)



2 Likes

Thanks a lot! :slightly_smiling_face:
I’ve found this example contains much of the answers:
PyCafe - Dash - Interactive Data Visualization with Dash and Mantine
Again, I’ll hope this could be useful to the community.

2 Likes