Code: Select all
# module_hist.py
import numpy as np
import plotly.graph_objects as go
def plot_hist(n=1000, bins=30, seed=0):
rng = np.random.default_rng(int(seed))
data = rng.normal(0, 1, int(n))
fig = go.Figure(go.Histogram(x=data, nbinsx=int(bins), name="hist"))
fig.update_layout(margin=dict(l=40, r=20, t=40, b=40))
return fig
plot_hist_schema = {
"n": {"type": "int", "default": 1000, "min": 100, "max": 10000, "step": 100, "help": "Sample size"},
"bins": {"type": "int", "default": 30, "min": 5, "max": 100, "step": 1, "help": "Histogram bins"},
"seed": {"type": "int", "default": 0, "help": "Random seed"},
}
if __name__ == "__main__":
import fire
def cli(n=1000, bins=30, seed=0):
fig = plot_hist(n=n, bins=bins, seed=seed)
fig.show()
fire.Fire(cli)
< /code>
module_line.py
# module_line.py
import numpy as np
import plotly.graph_objects as go
def plot_line(n=50, noise=0.1, seed=0):
rng = np.random.default_rng(int(seed))
x = np.arange(n)
y = np.sin(x / 5) + noise * rng.standard_normal(n)
fig = go.Figure(go.Scatter(x=x, y=y, mode="lines+markers", name="line"))
fig.update_layout(margin=dict(l=40, r=20, t=40, b=40))
return fig
# Parameter schema for auto-form generation
plot_line_schema = {
"n": {"type": "int", "default": 50, "min": 10, "max": 500, "step": 10, "help": "Number of points"},
"noise": {"type": "float", "default": 0.1, "min": 0, "max": 1, "step": 0.05, "help": "Noise scale"},
"seed": {"type": "int", "default": 0, "help": "Random seed"},
}
if __name__ == "__main__":
import fire
# fire.Fire(plot_line) # e.g. python module_line.py --n=200 --noise=0.2 --seed=42
def cli(n=50, noise=0.1, seed=0):
fig = plot_line(n=n, noise=noise, seed=seed)
fig.show() #
And then I have a dash app:
# app.py
from dash import Dash, dcc, html, Input, Output
from module_line import plot_line
from module_hist import plot_hist
app = Dash(__name__)
app.layout = html.Div(
[
html.H2("Reusable Plotly Modules in Dash"),
dcc.Tabs(
id="tab",
value="line",
children=[
dcc.Tab(
label="Line",
value="line",
children=[
html.Div(
[
html.Label("n"),
dcc.Slider(
id="n-line", min=10, max=500, step=10, value=50
),
html.Label("noise"),
dcc.Slider(
id="noise", min=0, max=1, step=0.05, value=0.1
),
html.Label("seed"),
dcc.Input(id="seed-line", type="number", value=0),
dcc.Graph(id="fig-line"),
],
style={"padding": "1rem"},
)
],
),
dcc.Tab(
label="Histogram",
value="hist",
children=[
html.Div(
[
html.Label("n"),
dcc.Slider(
id="n-hist",
min=100,
max=10000,
step=100,
value=1000,
),
html.Label("bins"),
dcc.Slider(id="bins", min=5, max=100, step=1, value=30),
html.Label("seed"),
dcc.Input(id="seed-hist", type="number", value=0),
dcc.Graph(id="fig-hist"),
],
style={"padding": "1rem"},
)
],
),
],
),
]
)
@app.callback(
Output("fig-line", "figure"),
Input("n-line", "value"),
Input("noise", "value"),
Input("seed-line", "value"),
)
def update_line(n, noise, seed):
return plot_line(n=n, noise=noise, seed=seed)
@app.callback(
Output("fig-hist", "figure"),
Input("n-hist", "value"),
Input("bins", "value"),
Input("seed-hist", "value"),
)
def update_hist(n, bins, seed):
return plot_hist(n=n, bins=bins, seed=seed)
if __name__ == "__main__":
app.run(debug=True)
< /code>
It works great. I have a tabbed interface and in each tab the plotly plot.
I am trying to do it more dynamic, so that I don't have to rewrite the dash if I have a new plotly plot But I haven't been able to do it.
First I get this weird message (in all my years doing dash I have never seen this kind of error):

and the plots don't get updated etc.
This is my failing script:
from dash import Dash, dcc, html, Input, Output, State
import dash
from module_line import plot_line, plot_line_schema
from module_hist import plot_hist, plot_hist_schema
# Registry of available modules
MODULES = {
"Line Plot": {"func": plot_line, "schema": plot_line_schema},
"Histogram": {"func": plot_hist, "schema": plot_hist_schema},
}
app = Dash(__name__)
def make_controls(schema, prefix):
"""Generate Dash controls from schema dict"""
controls = []
for name, meta in schema.items():
cid = f"{prefix}-{name}"
label = html.Label(f"{name} ({meta.get('help','')})")
t = meta["type"]
if t in ("int", "float"):
controls.append(label)
controls.append(
dcc.Slider(
id=cid,
min=meta.get("min", 0),
max=meta.get("max", 100),
step=meta.get("step", 1),
value=meta["default"],
tooltip={"placement": "bottom"}
)
)
else: # fallback to text input
controls.append(label)
controls.append(
dcc.Input(id=cid, type="text", value=str(meta["default"]))
)
return html.Div(controls, style={"padding": "1rem"})
# Layout
app.layout = html.Div([
html.H2("Pluggable Plotly Modules"),
dcc.Dropdown(
id="module-select",
options=[{"label": k, "value": k} for k in MODULES],
value=list(MODULES.keys())[0],
),
html.Div(id="controls-area"),
html.Button("Update Plot", id="update-btn"),
dcc.Graph(id="output-figure")
])
@app.callback(
Output("controls-area", "children"),
Input("module-select", "value")
)
def update_controls(module_name):
schema = MODULES[module_name]["schema"]
return make_controls(schema, prefix=module_name)
@app.callback(
Output("output-figure", "figure"),
Input("update-btn", "n_clicks"),
State("module-select", "value"),
State("controls-area", "children")
)
def update_plot(n_clicks, module_name, controls_state):
if not n_clicks:
return dash.no_update
schema = MODULES[module_name]["schema"]
func = MODULES[module_name]["func"]
# Collect parameter values from State dynamically
ctx = dash.callback_context
inputs = {}
# for name in schema:
# comp_id = f"{module_name}-{name}"
# val = ctx.states.get(f"{comp_id}.value")
# # Cast based on schema type
# t = schema[name]["type"]
# if t == "int":
# val = int(val)
# elif t == "float":
# val = float(val)
# inputs[name] = val
for name, meta in schema.items():
comp_id = f"{module_name}-{name}"
val = ctx.states.get(f"{comp_id}.value")
if val is None:
val = meta["default"]
t = meta["type"]
try:
if t == "int":
val = int(val)
elif t == "float":
val = float(val)
# else leave as string
except Exception:
val = meta["default"]
inputs[name] = val
return func(**inputs)
if __name__ == "__main__":
app.run(debug=True)
< /code>
Can someone tell me
- What is wrong? What is that weird error message?
- Even disregarding my attempt, how can I dynamically add plots in my dash app? (it doesn't necessarily have to be tabs as you see)