######################################################################
# Steps to generate YAML configuration from Python dashboard:
# 1. Insert your dashboard configuration in block below.
# 2. Copy and paste YAML output from terminal.
# 3. Replace references to <REPLACE ME>.
# 4. Check the YAML configuration works by running it in this app:
######################################################################
import vizro.plotly.express as px
import vizro.models as vm
import vizro.actions as va
from vizro.models.types import capture
from vizro import Vizro
from vizro.tables import dash_ag_grid
selected_countries = [
"Singapore",
"Malaysia",
"Thailand",
"Indonesia",
"Philippines",
"Vietnam",
"Cambodia",
"Myanmar",
]
gapminder = px.data.gapminder().query("country.isin(@selected_countries)")
@capture("graph")
def bar_with_highlight(data_frame, highlight_country=None):
country_is_highlighted = data_frame["country"] == highlight_country
fig = px.bar(
data_frame,
x="lifeExp",
y="country",
labels={"lifeExp": "lifeExp in 2007"},
color=country_is_highlighted,
category_orders={"country": sorted(data_frame["country"]), "color": [False, True]},
)
fig.update_layout(showlegend=False)
return fig
page = vm.Page(
title="Self-highlight a graph and cross-filter",
components=[
vm.Graph(
id="bar_chart",
figure=bar_with_highlight(gapminder.query("year == 2007")),
header="💡 Click on a bar to highlight the selected country and filter the table below",
actions=[
va.set_control(control="highlight_parameter", value="y"),
va.set_control(control="country_filter", value="y"),
],
),
vm.AgGrid(id="gapminder_table", figure=dash_ag_grid(data_frame=gapminder)),
],
controls=[
vm.Parameter(
id="highlight_parameter",
targets=["bar_chart.highlight_country"],
selector=vm.RadioItems(options=["NONE", *selected_countries]),
visible=False,
),
vm.Filter(id="country_filter", column="country", targets=["gapminder_table"], visible=False),
],
)
dashboard = vm.Dashboard(pages=[page])
# DON'T PUT Vizro().build(dashboard).run() here or the app won't work.
# The last line above should just define dashboardvariable.
######################################################################
import pyaml
import json
import vizro.tables as vt
import vizro.tables as vf
import vizro.plotly.express as px
# All this bit written by ChatGPT but seems to work well...
def serialize(obj):
if isinstance(obj, vm.types.CapturedCallable):
# return a dict representation
name = obj._function.__name__
if not hasattr(px, name) and not hasattr(vt, name) and not hasattr(vf, name):
# Hack that assumes captured callable is written in the app.py file if it's not one of the
# built in functions.
name = f"__main__.{name}"
arguments = dict(obj._arguments)
if "data_frame" in arguments:
arguments["data_frame"] = "<REPLACE ME>"
return {"_target_": name, **arguments}
raise TypeError(f"Type {type(obj)} not serializable")
def insert_type(base, override):
if isinstance(base, dict) and isinstance(override, dict):
# First, recurse into all keys in base
result = {}
for k, v in base.items():
result[k] = insert_type(v, override.get(k, {}))
# Then, insert "type" from override if present at this level
if "type" in override:
result["type"] = override["type"]
return result
elif isinstance(base, list) and isinstance(override, list):
# recurse element-wise for lists (assumes matching lengths)
return [insert_type(b, o) for b, o in zip(base, override)]
else:
return base # leave scalars / non-dict / non-list as-is
# We want a the dumped version to exclude unset fields but include `type`. There doesn't seem to be an easy way to do this
# with pydantic (maybe possible using Field-level include though, not sure?). So we just manually generate both and then
# take the type key out of the second one and insert into the first.
# UPDATE: in recent pydantic there's exclude_if as a field which would probably do this for us more cleanly.
dict1 = dashboard.model_dump(exclude_unset=True)
dict2 = dashboard.model_dump()
result = insert_type(dict1, dict2)
# If we built this into Vizro then this serialization should be built into CapturedCallable itself rather than needing to
# use it in json.dumps.
json_str = json.dumps(result, default=serialize)
# Overall we do dashboard model -> dict -> json string -> dict -> yaml string.
# If we built serialization into model_dump itself then this could just be done as dashboard model -> dict -> yaml string.
# There are other ways to directly do dashboard model -> yaml string (e.g. pydantic-yaml, yaml.add_representer) but probably not worth
# pursuing - we should instead keep JSON/dict as our "source of truth" rather than using specific techniques.
# Format using pretty yaml seems more similar to our preferred using prettier.
print(pyaml.dump(json.loads(json_str), vspacing=dict(split_lines=1000)))
Vizro().build(dashboard).run()