I have managed to do a simple example of plotly and dash where I can develop plotly scripts following a format, and then easily add them to a dash app. For example # 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) 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() # <--- here, in CLI wrapper only fire.Fire(cli) and then 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) 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 dont have to rewrite the dash if I have a new plotly plot But I havent 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 dont get actualized etc. My failing script is this one 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) Can someone tell me what is wrong? what is that weird error message Even disregarding my attempt, how can I dinamically add plots in my dash app? (it doesnt necessarily have to be tabs as you see) To add complexity I am thinking of adding also lists to the types of entry, but that is besides this question scope. Just letting it be known Continue reading...