Latest commit

History

History
485 lines (355 loc) · 20.3 KB

File metadata and controls

485 lines (355 loc) · 20.3 KB

Status: Template Layout System

Based on community feedback, this version of the template layout system will not be added to a future version of Dash. However, the work done here inspired many new features, such as:

- New in Dash 2.1: The low-code shorthands for Dash Core Components and the dash DataTable.

- New in Dash 2.1, The Input, State, and Output accepts components instead of ID strings. Dash callback will auto-generate the component's ID under-the-hood if not supplied.

- Available in the dash-bootstrap-templates library: Bootstrap themed figures.

We appreciate everyone's input on the template system. Templates are still in the dash-labs project plan, so stay tuned for a new version!

- ----------------------------------------------------------------------------------- This documentation describes code in a previous version of dash-labs (v0.4.0) - and is included here for legacy purposes only.-- You can install v0.4.0 with:- pip install dash-labs==0.4.0- ----------------------------------------------------------------------------------

The template layout system

Dash Labs introduces a template system that makes it possible to quickly add components to a pre-defined template.

Manually add components to a template

As will be described below, the template system integrates with @app.callback, but templates can also be used independently of @app.callback.

Templates that are included with Dash Labs are located in the dl.templates package. The convention is to assign a template instance to a variable named tpl. Components can then be added to the template with tpl.add_component

Here is a simple example of manually adding components to a DbcCard template

demos/template_system1.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=4)
div=html.Div()
button=html.Button(children="Click Me")
@app.callback(dl.Output(div, "children"), dl.Input(button, "n_clicks"))defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
tpl.add_component(button, label="Button to click", location="bottom")
tpl.add_component(div, location="top")
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Component location

When a component is added to a template using add_component, it is associated with a template location using the location argument. Components that share the same location will be grouped together by the template in the component layout it produces. Templates document the locations that they support in the constructor docstring. Here is the docstring for the DbcCard template used above.

 Template that places all components in a single card
Supported template locations:
- "bottom": Bottom region of the card (default for Input components)
- "top": Top region of the card (default for Output components)
...

Component label

When a component is added to a template using add_component, it may optionally be associated with a label string using the label argument. When provided, the template will wrap the provided component with a label in the component layout it produces.

Template children

Templates provide a .children property that returns a container that includes the components that were added to the template. This container is a regular Dash component that can be assigned to app.layout, or combined with other components to build app.layout.

Note: When using a template based on Dash Bootstrap Components, it's recommended to use dbc.Container as the top-level layout component, and to assign the template's children as the children of the dbc.Container. See https://dash-bootstrap-components.opensource.faculty.ai/docs/components/layout/ for more information. Similarly, when using a template based on Dash Design Kit, it's recommended to use ddk.App as the top-level layout component, and to assign the template's children as the children of the ddk.App.

template and app.callback integration

For convenience, @app.callback accepts an optional template argument. When provided, @app.callback will automatically add the provided input and output components to the template. Because of the information that @app.callback already has access to, it can choose reasonable defaults for each component's location and label. Because the components will be added to the template, it becomes possible to construct components inline in the @app.callback definition, rather than constructing them above and assigning them to local variables. With these conveniences, the example above becomes:

demos/template_system2.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(dl.Output(html.Div(), "children"),dl.Input(html.Button(children="Click Me"), "n_clicks", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Customize labels and locations

When a template is populated using @app.callback, the label string and location for a component can be overridden using the label and location keyword arguments to dl.Input/dl.State/dl.Output. See the "Button to click" label added above.

Default output

When a template is provided, and no Output dependency is provided, the template will provide a default output container for the result of the function (typically an html.Div component).

demos/template_system3.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(dl.Input(html.Button(children="Click Me"), "n_clicks", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Template component builders

To reduce the amount of boilerplate required to construct the dependency components to pass to @app.callback, template classes provide a variety of helper functions. A few examples are tpl.new_div(), tpl.new_button(), tpl.new_dropdown(), etc. These are relatively simple class methods that return a dependency object wrapping a component. For example:

tpl.new_dropdown(["A", "B", "C"], label="My Dropdown")

evaluates to...

dl.Input(
dcc.Dropdown(
id={'uid': 'd4713d60-c8a7-0639-eb11-67b367a9c378'},
options=[
{'value': 'A', 'label': 'A'}, {'value': 'B', 'label': 'B'},
{'value': 'C', 'label': 'C'}
],
value='A',
clearable=False
),
"value",
label="My Dropdown"
)

All of these functions provide the following keyword arguments:

  • label: The label to display for the component.
  • location: The template location of the component.
  • component_property: The property (or property grouping) considered to be the value of the component. This value is optional, and the template will provide a reasonable default for each component type (e.g. n_clicks for dcc.Button, value for dcc.Dropdown, figure for dcc.Graph).
  • kind: The dependency class to return. One of dl.Input, dl.State, or dl.Output. This value is optional and templates will provide a reasonable defaults (e.g. dl.Input for dcc.Button and dcc.Dropdown, dl.Output for dcc.Graph, etc.)
  • id: Optional argument to override the generated component id
  • opts: Dictionary of keyword arguments to pass to the constructor of the component that is created.

In addition to these standard keyword arguments, component builders also provide args to make the configuration of the components as concise as possible. e.g. dl.dropdown_input(["A", "B", "C]), dl.slider_input(0, 10).

These component builders can significantly shorten many @app.callback specifications.

Here is an update to the previous example that uses the tpl.new_button component constructor instead of manually creating dl.Input and html.Button objects.

demos/template_system4.py

importdash_bootstrap_componentsasdbcimportdash_labsasdlimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(tpl.new_button("Click Me", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Component builder specialization

Another advantage of the component builder paradigm is that templates can specialize the representation of the different components. For example, Dash Bootstrap templates can use dbc.Select in place of dcc.Dropdown for tpl.new_dropdown(). Similarly, DDK templates can use ddk.Graph in place of dcc.Graph for tpl.new_graph().

Manually executed function using state

The ipywidgets @interact decorator supports a manual argument. When True, an update button is automatically added and changes to the other widgets are not applied until the update button is clicked. This workflow can be replicated with @app.callback by adding a button component and specifying that all inputs other than the button should be classified as State (rather than the default of Input).

Here is a full example of specifying all the components except the button as kind=dl.State to @app.callback.

demos/basic_decorator_manual.py

importdashimportdash_labsasdlimportnumpyasnpimportdash_core_componentsasdccimportplotly.expressaspximportdash_bootstrap_componentsasdbcapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcRow(app, title="Manual Update", theme=dbc.themes.SOLAR)
@app.callback(args=dict(fun=tpl.new_dropdown(["sin", "cos", "exp"], label="Function", kind=dl.State),figure_title=tpl.new_textbox("Initial Title", label="Figure Title", kind=dl.State ),phase=tpl.new_slider(1, 10, label="Phase", kind=dl.State),amplitude=tpl.new_slider(1, 10, value=3, label="Amplitude", kind=dl.State),n_clicks=tpl.new_button("Update", label=None), ),template=tpl,)defgreet(fun, figure_title, phase, amplitude, n_clicks):
print(fun, figure_title, phase, amplitude)
xs=np.linspace(-10, 10, 100)
returndcc.Graph(
figure=px.line(x=xs, y=getattr(np, fun)(xs+phase) *amplitude).update_layout(
title_text=figure_title
)
)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Custom output components

When a template is provided, the new @app.callback decorator no longer requires a caller to explicitly provide the output component that the callback function result will be stored in. However, explicit output components and output properties can still be configured.

Here is an example that outputs a string to the children property of a dcc.Markdown component.

Note that the default value of kind for tpl.new_markdown is dl.Output, which is why it's not necessary to override the kind argument.

demos/output_markdown.py

importdashimportdash_labsasdlimportdash_bootstrap_componentsasdbcapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, "App Title", sidebar_columns=6)
@app.callback(output=tpl.new_markdown(),args=tpl.new_textarea("## Heading\n", opts=dict(style={"width": "100%", "height": 400}) ),template=tpl,)defmarkdown_preview(input_text):
returninput_textapp.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Adding additional components to a template

Additional components can be added to a template after the initial components are added by @app.callback.

Note how the add_component method supports before and after keyword arguments that can be used to insert new components at specific locations between components added by app.callback.

demos/template_with_custom_additions.py

importdashimportdash_labsasdlimportnumpyasnpimportdash_core_componentsasdccimportdash_bootstrap_componentsasdbcimportplotly.expressaspxapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, title="Dash Labs App")
# import dash_core_components as dcc@app.callback(inputs=dict(fun=tpl.new_dropdown(["sin", "cos", "exp"], label="Function"),figure_title=tpl.new_textbox("Initial Title", label="Figure Title"),phase=tpl.new_slider(1, 10, value=3, label="Phase"),amplitude=tpl.new_slider(1, 10, value=4, label="Amplitude"), ),output=tpl.new_graph(),template=tpl,)deffunction_browser(fun, figure_title, phase, amplitude):
xs=np.linspace(-10, 10, 100)
returnpx.line(x=xs, y=getattr(np, fun)(xs+phase) *amplitude).update_layout(
title_text=figure_title
)
# Add extra component to templatetpl.add_component(
dcc.Markdown(children="# First Group"), location="sidebar", before="fun"
)
tpl.add_component(
dcc.Markdown(
children=[
"# Second Group\n""Specify the Phase and Amplitudue for the chosen function"
]
),
location="sidebar",
before="phase",
)
tpl.add_component(
dcc.Markdown(children=["# H2 Title\n", "Here is the *main* plot"]),
location="main",
before=0,
)
tpl.add_component(
dcc.Link("Made with Dash", href="https://dash.plotly.com/"),
component_property="children",
location="main",
)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Advanced: Accessing individual components to build custom layouts

This section describes how to retrieve components that have been added to, and created by, a template. It is intended mostly for information purposes, and is not intended to be a common workflow.

Template locations property

The components added to a template are stored in the .locations property.

This is a dictionary from template location to OrderedDicts of ArgumentComponents (described below).

ArgumentComponents

You might think that the values of the .location dictionaries described above would simply be collections of the components added to the template. The reason it's not quite that simple is that for a single component added to a template, the template may create multiple components: There is the original component, one for the label, and both of these may be wrapped in a container component. Because the caller may want access to any, or all, of these components individually, references to all of these components, and their associated props, are stored in a ArgumentComponents instance. Here are the attributes of ArgumentComponents, and an example of why a caller may want to access them.

  • .arg_component: This a reference to the innermost component that actually provides the callback function with an input value, which corresponds to the properties stored in .arg_property attribute. A caller would want to access this component in order to register additional callback functions to execute when the callback function is updated.
  • .label_component: This is the component that displays the label string for the component, where the label text is stored in the .label_property property of the component. A caller may want to access this component to customize the label styling, or access the current value of the label string.
  • .container_component: This is the outer-most component that contains all the other components described above, where the contained components are stored in the .container_property property of the container. This is generally the component that a caller would incorporate when building a custom layout.

This example uses @app.callback to add components to a template, constructs a fully custom layout, and defines custom callbacks on the components returned by @app.callback. This is loosely based on the Dash Bootstrap Components example at https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/.

Notice how custom callbacks are applied to the dropdowns returned by @app.callback to prevent specifying the same feature as both x and y values.

demos/custom_layout_and_callback_integration.py

# Based on https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/# Based on https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/importdash_labsasdlimportplotly.expressaspximportdash_core_componentsasdccimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcfromdash.dependenciesimportInput, Outputimportdash# Load datadf=px.data.iris()
feature_cols= [colforcolindf.columnsif"species"notincol]
feature_labels= [col.replace("_", " ").title() +" (cm)"forcolinfeature_cols]
feature_options= [
{"label": label, "value": col} forcol, labelinzip(feature_cols, feature_labels)
]
# Build app and templateapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, title="Iris Dataset")
# Use parameterize to create components@app.callback(args=dict(x=dl.Input(dcc.Dropdown(options=feature_options, value="sepal_length")),y=dl.Input(dcc.Dropdown(options=feature_options, value="sepal_width")), ),template=tpl,)defiris(x, y):
returndcc.Graph(
figure=px.scatter(df, x=x, y=y, color="species"),
)
# Get references to the dropdowns and register a custom callback to prevent the user# from setting x and y to the same variable# Get the dropdown components that were created by parameterizex_component=tpl.locations["sidebar"]["x"].arg_componenty_component=tpl.locations["sidebar"]["y"].arg_component# Define standalone function that computes what values to enable, reuse for both# dropdowns with app.callbackdeffilter_options(v):
"""Disable option ability to plot x vs x"""return [
{"label": label, "value": col, "disabled": col==v}
forcol, labelinzip(feature_cols, feature_labels)
]
app.callback(Output(x_component.id, "options"), [Input(y_component.id, "value")])(
filter_options
)
app.callback(Output(y_component.id, "options"), [Input(x_component.id, "value")])(
filter_options
)
x_container=tpl.locations["sidebar"]["x"].container_componenty_container=tpl.locations["sidebar"]["y"].container_componentoutput_component=tpl.locations["main"][0].container_componentapp.layout=html.Div(
[
html.H1("Iris Feature Explorer"),
html.H2("Select Features"),
x_container,
y_container,
html.Hr(),
html.H2("Feature Scatter Plot"),
output_component,
]
)
if__name__=="__main__":
app.run_server(debug=True)

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

History
485 lines (355 loc) · 20.3 KB

File metadata and controls

485 lines (355 loc) · 20.3 KB

Status: Template Layout System

Based on community feedback, this version of the template layout system will not be added to a future version of Dash. However, the work done here inspired many new features, such as:

- New in Dash 2.1: The low-code shorthands for Dash Core Components and the dash DataTable.

- New in Dash 2.1, The Input, State, and Output accepts components instead of ID strings. Dash callback will auto-generate the component's ID under-the-hood if not supplied.

- Available in the dash-bootstrap-templates library: Bootstrap themed figures.

We appreciate everyone's input on the template system. Templates are still in the dash-labs project plan, so stay tuned for a new version!

- ----------------------------------------------------------------------------------- This documentation describes code in a previous version of dash-labs (v0.4.0) - and is included here for legacy purposes only.-- You can install v0.4.0 with:- pip install dash-labs==0.4.0- ----------------------------------------------------------------------------------

The template layout system

Dash Labs introduces a template system that makes it possible to quickly add components to a pre-defined template.

Manually add components to a template

As will be described below, the template system integrates with @app.callback, but templates can also be used independently of @app.callback.

Templates that are included with Dash Labs are located in the dl.templates package. The convention is to assign a template instance to a variable named tpl. Components can then be added to the template with tpl.add_component

Here is a simple example of manually adding components to a DbcCard template

demos/template_system1.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=4)
div=html.Div()
button=html.Button(children="Click Me")
@app.callback(dl.Output(div, "children"), dl.Input(button, "n_clicks"))defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
tpl.add_component(button, label="Button to click", location="bottom")
tpl.add_component(div, location="top")
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Component location

When a component is added to a template using add_component, it is associated with a template location using the location argument. Components that share the same location will be grouped together by the template in the component layout it produces. Templates document the locations that they support in the constructor docstring. Here is the docstring for the DbcCard template used above.

 Template that places all components in a single card
Supported template locations:
- "bottom": Bottom region of the card (default for Input components)
- "top": Top region of the card (default for Output components)
...

Component label

When a component is added to a template using add_component, it may optionally be associated with a label string using the label argument. When provided, the template will wrap the provided component with a label in the component layout it produces.

Template children

Templates provide a .children property that returns a container that includes the components that were added to the template. This container is a regular Dash component that can be assigned to app.layout, or combined with other components to build app.layout.

Note: When using a template based on Dash Bootstrap Components, it's recommended to use dbc.Container as the top-level layout component, and to assign the template's children as the children of the dbc.Container. See https://dash-bootstrap-components.opensource.faculty.ai/docs/components/layout/ for more information. Similarly, when using a template based on Dash Design Kit, it's recommended to use ddk.App as the top-level layout component, and to assign the template's children as the children of the ddk.App.

template and app.callback integration

For convenience, @app.callback accepts an optional template argument. When provided, @app.callback will automatically add the provided input and output components to the template. Because of the information that @app.callback already has access to, it can choose reasonable defaults for each component's location and label. Because the components will be added to the template, it becomes possible to construct components inline in the @app.callback definition, rather than constructing them above and assigning them to local variables. With these conveniences, the example above becomes:

demos/template_system2.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(dl.Output(html.Div(), "children"),dl.Input(html.Button(children="Click Me"), "n_clicks", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Customize labels and locations

When a template is populated using @app.callback, the label string and location for a component can be overridden using the label and location keyword arguments to dl.Input/dl.State/dl.Output. See the "Button to click" label added above.

Default output

When a template is provided, and no Output dependency is provided, the template will provide a default output container for the result of the function (typically an html.Div component).

demos/template_system3.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(dl.Input(html.Button(children="Click Me"), "n_clicks", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Template component builders

To reduce the amount of boilerplate required to construct the dependency components to pass to @app.callback, template classes provide a variety of helper functions. A few examples are tpl.new_div(), tpl.new_button(), tpl.new_dropdown(), etc. These are relatively simple class methods that return a dependency object wrapping a component. For example:

tpl.new_dropdown(["A", "B", "C"], label="My Dropdown")

evaluates to...

dl.Input(
dcc.Dropdown(
id={'uid': 'd4713d60-c8a7-0639-eb11-67b367a9c378'},
options=[
{'value': 'A', 'label': 'A'}, {'value': 'B', 'label': 'B'},
{'value': 'C', 'label': 'C'}
],
value='A',
clearable=False
),
"value",
label="My Dropdown"
)

All of these functions provide the following keyword arguments:

  • label: The label to display for the component.
  • location: The template location of the component.
  • component_property: The property (or property grouping) considered to be the value of the component. This value is optional, and the template will provide a reasonable default for each component type (e.g. n_clicks for dcc.Button, value for dcc.Dropdown, figure for dcc.Graph).
  • kind: The dependency class to return. One of dl.Input, dl.State, or dl.Output. This value is optional and templates will provide a reasonable defaults (e.g. dl.Input for dcc.Button and dcc.Dropdown, dl.Output for dcc.Graph, etc.)
  • id: Optional argument to override the generated component id
  • opts: Dictionary of keyword arguments to pass to the constructor of the component that is created.

In addition to these standard keyword arguments, component builders also provide args to make the configuration of the components as concise as possible. e.g. dl.dropdown_input(["A", "B", "C]), dl.slider_input(0, 10).

These component builders can significantly shorten many @app.callback specifications.

Here is an update to the previous example that uses the tpl.new_button component constructor instead of manually creating dl.Input and html.Button objects.

demos/template_system4.py

importdash_bootstrap_componentsasdbcimportdash_labsasdlimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(tpl.new_button("Click Me", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Component builder specialization

Another advantage of the component builder paradigm is that templates can specialize the representation of the different components. For example, Dash Bootstrap templates can use dbc.Select in place of dcc.Dropdown for tpl.new_dropdown(). Similarly, DDK templates can use ddk.Graph in place of dcc.Graph for tpl.new_graph().

Manually executed function using state

The ipywidgets @interact decorator supports a manual argument. When True, an update button is automatically added and changes to the other widgets are not applied until the update button is clicked. This workflow can be replicated with @app.callback by adding a button component and specifying that all inputs other than the button should be classified as State (rather than the default of Input).

Here is a full example of specifying all the components except the button as kind=dl.State to @app.callback.

demos/basic_decorator_manual.py

importdashimportdash_labsasdlimportnumpyasnpimportdash_core_componentsasdccimportplotly.expressaspximportdash_bootstrap_componentsasdbcapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcRow(app, title="Manual Update", theme=dbc.themes.SOLAR)
@app.callback(args=dict(fun=tpl.new_dropdown(["sin", "cos", "exp"], label="Function", kind=dl.State),figure_title=tpl.new_textbox("Initial Title", label="Figure Title", kind=dl.State ),phase=tpl.new_slider(1, 10, label="Phase", kind=dl.State),amplitude=tpl.new_slider(1, 10, value=3, label="Amplitude", kind=dl.State),n_clicks=tpl.new_button("Update", label=None), ),template=tpl,)defgreet(fun, figure_title, phase, amplitude, n_clicks):
print(fun, figure_title, phase, amplitude)
xs=np.linspace(-10, 10, 100)
returndcc.Graph(
figure=px.line(x=xs, y=getattr(np, fun)(xs+phase) *amplitude).update_layout(
title_text=figure_title
)
)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Custom output components

When a template is provided, the new @app.callback decorator no longer requires a caller to explicitly provide the output component that the callback function result will be stored in. However, explicit output components and output properties can still be configured.

Here is an example that outputs a string to the children property of a dcc.Markdown component.

Note that the default value of kind for tpl.new_markdown is dl.Output, which is why it's not necessary to override the kind argument.

demos/output_markdown.py

importdashimportdash_labsasdlimportdash_bootstrap_componentsasdbcapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, "App Title", sidebar_columns=6)
@app.callback(output=tpl.new_markdown(),args=tpl.new_textarea("## Heading\n", opts=dict(style={"width": "100%", "height": 400}) ),template=tpl,)defmarkdown_preview(input_text):
returninput_textapp.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Adding additional components to a template

Additional components can be added to a template after the initial components are added by @app.callback.

Note how the add_component method supports before and after keyword arguments that can be used to insert new components at specific locations between components added by app.callback.

demos/template_with_custom_additions.py

importdashimportdash_labsasdlimportnumpyasnpimportdash_core_componentsasdccimportdash_bootstrap_componentsasdbcimportplotly.expressaspxapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, title="Dash Labs App")
# import dash_core_components as dcc@app.callback(inputs=dict(fun=tpl.new_dropdown(["sin", "cos", "exp"], label="Function"),figure_title=tpl.new_textbox("Initial Title", label="Figure Title"),phase=tpl.new_slider(1, 10, value=3, label="Phase"),amplitude=tpl.new_slider(1, 10, value=4, label="Amplitude"), ),output=tpl.new_graph(),template=tpl,)deffunction_browser(fun, figure_title, phase, amplitude):
xs=np.linspace(-10, 10, 100)
returnpx.line(x=xs, y=getattr(np, fun)(xs+phase) *amplitude).update_layout(
title_text=figure_title
)
# Add extra component to templatetpl.add_component(
dcc.Markdown(children="# First Group"), location="sidebar", before="fun"
)
tpl.add_component(
dcc.Markdown(
children=[
"# Second Group\n""Specify the Phase and Amplitudue for the chosen function"
]
),
location="sidebar",
before="phase",
)
tpl.add_component(
dcc.Markdown(children=["# H2 Title\n", "Here is the *main* plot"]),
location="main",
before=0,
)
tpl.add_component(
dcc.Link("Made with Dash", href="https://dash.plotly.com/"),
component_property="children",
location="main",
)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Advanced: Accessing individual components to build custom layouts

This section describes how to retrieve components that have been added to, and created by, a template. It is intended mostly for information purposes, and is not intended to be a common workflow.

Template locations property

The components added to a template are stored in the .locations property.

This is a dictionary from template location to OrderedDicts of ArgumentComponents (described below).

ArgumentComponents

You might think that the values of the .location dictionaries described above would simply be collections of the components added to the template. The reason it's not quite that simple is that for a single component added to a template, the template may create multiple components: There is the original component, one for the label, and both of these may be wrapped in a container component. Because the caller may want access to any, or all, of these components individually, references to all of these components, and their associated props, are stored in a ArgumentComponents instance. Here are the attributes of ArgumentComponents, and an example of why a caller may want to access them.

  • .arg_component: This a reference to the innermost component that actually provides the callback function with an input value, which corresponds to the properties stored in .arg_property attribute. A caller would want to access this component in order to register additional callback functions to execute when the callback function is updated.
  • .label_component: This is the component that displays the label string for the component, where the label text is stored in the .label_property property of the component. A caller may want to access this component to customize the label styling, or access the current value of the label string.
  • .container_component: This is the outer-most component that contains all the other components described above, where the contained components are stored in the .container_property property of the container. This is generally the component that a caller would incorporate when building a custom layout.

This example uses @app.callback to add components to a template, constructs a fully custom layout, and defines custom callbacks on the components returned by @app.callback. This is loosely based on the Dash Bootstrap Components example at https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/.

Notice how custom callbacks are applied to the dropdowns returned by @app.callback to prevent specifying the same feature as both x and y values.

demos/custom_layout_and_callback_integration.py

# Based on https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/# Based on https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/importdash_labsasdlimportplotly.expressaspximportdash_core_componentsasdccimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcfromdash.dependenciesimportInput, Outputimportdash# Load datadf=px.data.iris()
feature_cols= [colforcolindf.columnsif"species"notincol]
feature_labels= [col.replace("_", " ").title() +" (cm)"forcolinfeature_cols]
feature_options= [
{"label": label, "value": col} forcol, labelinzip(feature_cols, feature_labels)
]
# Build app and templateapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, title="Iris Dataset")
# Use parameterize to create components@app.callback(args=dict(x=dl.Input(dcc.Dropdown(options=feature_options, value="sepal_length")),y=dl.Input(dcc.Dropdown(options=feature_options, value="sepal_width")), ),template=tpl,)defiris(x, y):
returndcc.Graph(
figure=px.scatter(df, x=x, y=y, color="species"),
)
# Get references to the dropdowns and register a custom callback to prevent the user# from setting x and y to the same variable# Get the dropdown components that were created by parameterizex_component=tpl.locations["sidebar"]["x"].arg_componenty_component=tpl.locations["sidebar"]["y"].arg_component# Define standalone function that computes what values to enable, reuse for both# dropdowns with app.callbackdeffilter_options(v):
"""Disable option ability to plot x vs x"""return [
{"label": label, "value": col, "disabled": col==v}
forcol, labelinzip(feature_cols, feature_labels)
]
app.callback(Output(x_component.id, "options"), [Input(y_component.id, "value")])(
filter_options
)
app.callback(Output(y_component.id, "options"), [Input(x_component.id, "value")])(
filter_options
)
x_container=tpl.locations["sidebar"]["x"].container_componenty_container=tpl.locations["sidebar"]["y"].container_componentoutput_component=tpl.locations["main"][0].container_componentapp.layout=html.Div(
[
html.H1("Iris Feature Explorer"),
html.H2("Select Features"),
x_container,
y_container,
html.Hr(),
html.H2("Feature Scatter Plot"),
output_component,
]
)
if__name__=="__main__":
app.run_server(debug=True)

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

History
485 lines (355 loc) · 20.3 KB

File metadata and controls

485 lines (355 loc) · 20.3 KB

Status: Template Layout System

Based on community feedback, this version of the template layout system will not be added to a future version of Dash. However, the work done here inspired many new features, such as:

- New in Dash 2.1: The low-code shorthands for Dash Core Components and the dash DataTable.

- New in Dash 2.1, The Input, State, and Output accepts components instead of ID strings. Dash callback will auto-generate the component's ID under-the-hood if not supplied.

- Available in the dash-bootstrap-templates library: Bootstrap themed figures.

We appreciate everyone's input on the template system. Templates are still in the dash-labs project plan, so stay tuned for a new version!

- ----------------------------------------------------------------------------------- This documentation describes code in a previous version of dash-labs (v0.4.0) - and is included here for legacy purposes only.-- You can install v0.4.0 with:- pip install dash-labs==0.4.0- ----------------------------------------------------------------------------------

The template layout system

Dash Labs introduces a template system that makes it possible to quickly add components to a pre-defined template.

Manually add components to a template

As will be described below, the template system integrates with @app.callback, but templates can also be used independently of @app.callback.

Templates that are included with Dash Labs are located in the dl.templates package. The convention is to assign a template instance to a variable named tpl. Components can then be added to the template with tpl.add_component

Here is a simple example of manually adding components to a DbcCard template

demos/template_system1.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=4)
div=html.Div()
button=html.Button(children="Click Me")
@app.callback(dl.Output(div, "children"), dl.Input(button, "n_clicks"))defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
tpl.add_component(button, label="Button to click", location="bottom")
tpl.add_component(div, location="top")
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Component location

When a component is added to a template using add_component, it is associated with a template location using the location argument. Components that share the same location will be grouped together by the template in the component layout it produces. Templates document the locations that they support in the constructor docstring. Here is the docstring for the DbcCard template used above.

 Template that places all components in a single card
Supported template locations:
- "bottom": Bottom region of the card (default for Input components)
- "top": Top region of the card (default for Output components)
...

Component label

When a component is added to a template using add_component, it may optionally be associated with a label string using the label argument. When provided, the template will wrap the provided component with a label in the component layout it produces.

Template children

Templates provide a .children property that returns a container that includes the components that were added to the template. This container is a regular Dash component that can be assigned to app.layout, or combined with other components to build app.layout.

Note: When using a template based on Dash Bootstrap Components, it's recommended to use dbc.Container as the top-level layout component, and to assign the template's children as the children of the dbc.Container. See https://dash-bootstrap-components.opensource.faculty.ai/docs/components/layout/ for more information. Similarly, when using a template based on Dash Design Kit, it's recommended to use ddk.App as the top-level layout component, and to assign the template's children as the children of the ddk.App.

template and app.callback integration

For convenience, @app.callback accepts an optional template argument. When provided, @app.callback will automatically add the provided input and output components to the template. Because of the information that @app.callback already has access to, it can choose reasonable defaults for each component's location and label. Because the components will be added to the template, it becomes possible to construct components inline in the @app.callback definition, rather than constructing them above and assigning them to local variables. With these conveniences, the example above becomes:

demos/template_system2.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(dl.Output(html.Div(), "children"),dl.Input(html.Button(children="Click Me"), "n_clicks", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Customize labels and locations

When a template is populated using @app.callback, the label string and location for a component can be overridden using the label and location keyword arguments to dl.Input/dl.State/dl.Output. See the "Button to click" label added above.

Default output

When a template is provided, and no Output dependency is provided, the template will provide a default output container for the result of the function (typically an html.Div component).

demos/template_system3.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(dl.Input(html.Button(children="Click Me"), "n_clicks", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Template component builders

To reduce the amount of boilerplate required to construct the dependency components to pass to @app.callback, template classes provide a variety of helper functions. A few examples are tpl.new_div(), tpl.new_button(), tpl.new_dropdown(), etc. These are relatively simple class methods that return a dependency object wrapping a component. For example:

tpl.new_dropdown(["A", "B", "C"], label="My Dropdown")

evaluates to...

dl.Input(
dcc.Dropdown(
id={'uid': 'd4713d60-c8a7-0639-eb11-67b367a9c378'},
options=[
{'value': 'A', 'label': 'A'}, {'value': 'B', 'label': 'B'},
{'value': 'C', 'label': 'C'}
],
value='A',
clearable=False
),
"value",
label="My Dropdown"
)

All of these functions provide the following keyword arguments:

  • label: The label to display for the component.
  • location: The template location of the component.
  • component_property: The property (or property grouping) considered to be the value of the component. This value is optional, and the template will provide a reasonable default for each component type (e.g. n_clicks for dcc.Button, value for dcc.Dropdown, figure for dcc.Graph).
  • kind: The dependency class to return. One of dl.Input, dl.State, or dl.Output. This value is optional and templates will provide a reasonable defaults (e.g. dl.Input for dcc.Button and dcc.Dropdown, dl.Output for dcc.Graph, etc.)
  • id: Optional argument to override the generated component id
  • opts: Dictionary of keyword arguments to pass to the constructor of the component that is created.

In addition to these standard keyword arguments, component builders also provide args to make the configuration of the components as concise as possible. e.g. dl.dropdown_input(["A", "B", "C]), dl.slider_input(0, 10).

These component builders can significantly shorten many @app.callback specifications.

Here is an update to the previous example that uses the tpl.new_button component constructor instead of manually creating dl.Input and html.Button objects.

demos/template_system4.py

importdash_bootstrap_componentsasdbcimportdash_labsasdlimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(tpl.new_button("Click Me", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Component builder specialization

Another advantage of the component builder paradigm is that templates can specialize the representation of the different components. For example, Dash Bootstrap templates can use dbc.Select in place of dcc.Dropdown for tpl.new_dropdown(). Similarly, DDK templates can use ddk.Graph in place of dcc.Graph for tpl.new_graph().

Manually executed function using state

The ipywidgets @interact decorator supports a manual argument. When True, an update button is automatically added and changes to the other widgets are not applied until the update button is clicked. This workflow can be replicated with @app.callback by adding a button component and specifying that all inputs other than the button should be classified as State (rather than the default of Input).

Here is a full example of specifying all the components except the button as kind=dl.State to @app.callback.

demos/basic_decorator_manual.py

importdashimportdash_labsasdlimportnumpyasnpimportdash_core_componentsasdccimportplotly.expressaspximportdash_bootstrap_componentsasdbcapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcRow(app, title="Manual Update", theme=dbc.themes.SOLAR)
@app.callback(args=dict(fun=tpl.new_dropdown(["sin", "cos", "exp"], label="Function", kind=dl.State),figure_title=tpl.new_textbox("Initial Title", label="Figure Title", kind=dl.State ),phase=tpl.new_slider(1, 10, label="Phase", kind=dl.State),amplitude=tpl.new_slider(1, 10, value=3, label="Amplitude", kind=dl.State),n_clicks=tpl.new_button("Update", label=None), ),template=tpl,)defgreet(fun, figure_title, phase, amplitude, n_clicks):
print(fun, figure_title, phase, amplitude)
xs=np.linspace(-10, 10, 100)
returndcc.Graph(
figure=px.line(x=xs, y=getattr(np, fun)(xs+phase) *amplitude).update_layout(
title_text=figure_title
)
)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Custom output components

When a template is provided, the new @app.callback decorator no longer requires a caller to explicitly provide the output component that the callback function result will be stored in. However, explicit output components and output properties can still be configured.

Here is an example that outputs a string to the children property of a dcc.Markdown component.

Note that the default value of kind for tpl.new_markdown is dl.Output, which is why it's not necessary to override the kind argument.

demos/output_markdown.py

importdashimportdash_labsasdlimportdash_bootstrap_componentsasdbcapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, "App Title", sidebar_columns=6)
@app.callback(output=tpl.new_markdown(),args=tpl.new_textarea("## Heading\n", opts=dict(style={"width": "100%", "height": 400}) ),template=tpl,)defmarkdown_preview(input_text):
returninput_textapp.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Adding additional components to a template

Additional components can be added to a template after the initial components are added by @app.callback.

Note how the add_component method supports before and after keyword arguments that can be used to insert new components at specific locations between components added by app.callback.

demos/template_with_custom_additions.py

importdashimportdash_labsasdlimportnumpyasnpimportdash_core_componentsasdccimportdash_bootstrap_componentsasdbcimportplotly.expressaspxapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, title="Dash Labs App")
# import dash_core_components as dcc@app.callback(inputs=dict(fun=tpl.new_dropdown(["sin", "cos", "exp"], label="Function"),figure_title=tpl.new_textbox("Initial Title", label="Figure Title"),phase=tpl.new_slider(1, 10, value=3, label="Phase"),amplitude=tpl.new_slider(1, 10, value=4, label="Amplitude"), ),output=tpl.new_graph(),template=tpl,)deffunction_browser(fun, figure_title, phase, amplitude):
xs=np.linspace(-10, 10, 100)
returnpx.line(x=xs, y=getattr(np, fun)(xs+phase) *amplitude).update_layout(
title_text=figure_title
)
# Add extra component to templatetpl.add_component(
dcc.Markdown(children="# First Group"), location="sidebar", before="fun"
)
tpl.add_component(
dcc.Markdown(
children=[
"# Second Group\n""Specify the Phase and Amplitudue for the chosen function"
]
),
location="sidebar",
before="phase",
)
tpl.add_component(
dcc.Markdown(children=["# H2 Title\n", "Here is the *main* plot"]),
location="main",
before=0,
)
tpl.add_component(
dcc.Link("Made with Dash", href="https://dash.plotly.com/"),
component_property="children",
location="main",
)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Advanced: Accessing individual components to build custom layouts

This section describes how to retrieve components that have been added to, and created by, a template. It is intended mostly for information purposes, and is not intended to be a common workflow.

Template locations property

The components added to a template are stored in the .locations property.

This is a dictionary from template location to OrderedDicts of ArgumentComponents (described below).

ArgumentComponents

You might think that the values of the .location dictionaries described above would simply be collections of the components added to the template. The reason it's not quite that simple is that for a single component added to a template, the template may create multiple components: There is the original component, one for the label, and both of these may be wrapped in a container component. Because the caller may want access to any, or all, of these components individually, references to all of these components, and their associated props, are stored in a ArgumentComponents instance. Here are the attributes of ArgumentComponents, and an example of why a caller may want to access them.

  • .arg_component: This a reference to the innermost component that actually provides the callback function with an input value, which corresponds to the properties stored in .arg_property attribute. A caller would want to access this component in order to register additional callback functions to execute when the callback function is updated.
  • .label_component: This is the component that displays the label string for the component, where the label text is stored in the .label_property property of the component. A caller may want to access this component to customize the label styling, or access the current value of the label string.
  • .container_component: This is the outer-most component that contains all the other components described above, where the contained components are stored in the .container_property property of the container. This is generally the component that a caller would incorporate when building a custom layout.

This example uses @app.callback to add components to a template, constructs a fully custom layout, and defines custom callbacks on the components returned by @app.callback. This is loosely based on the Dash Bootstrap Components example at https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/.

Notice how custom callbacks are applied to the dropdowns returned by @app.callback to prevent specifying the same feature as both x and y values.

demos/custom_layout_and_callback_integration.py

# Based on https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/# Based on https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/importdash_labsasdlimportplotly.expressaspximportdash_core_componentsasdccimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcfromdash.dependenciesimportInput, Outputimportdash# Load datadf=px.data.iris()
feature_cols= [colforcolindf.columnsif"species"notincol]
feature_labels= [col.replace("_", " ").title() +" (cm)"forcolinfeature_cols]
feature_options= [
{"label": label, "value": col} forcol, labelinzip(feature_cols, feature_labels)
]
# Build app and templateapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, title="Iris Dataset")
# Use parameterize to create components@app.callback(args=dict(x=dl.Input(dcc.Dropdown(options=feature_options, value="sepal_length")),y=dl.Input(dcc.Dropdown(options=feature_options, value="sepal_width")), ),template=tpl,)defiris(x, y):
returndcc.Graph(
figure=px.scatter(df, x=x, y=y, color="species"),
)
# Get references to the dropdowns and register a custom callback to prevent the user# from setting x and y to the same variable# Get the dropdown components that were created by parameterizex_component=tpl.locations["sidebar"]["x"].arg_componenty_component=tpl.locations["sidebar"]["y"].arg_component# Define standalone function that computes what values to enable, reuse for both# dropdowns with app.callbackdeffilter_options(v):
"""Disable option ability to plot x vs x"""return [
{"label": label, "value": col, "disabled": col==v}
forcol, labelinzip(feature_cols, feature_labels)
]
app.callback(Output(x_component.id, "options"), [Input(y_component.id, "value")])(
filter_options
)
app.callback(Output(y_component.id, "options"), [Input(x_component.id, "value")])(
filter_options
)
x_container=tpl.locations["sidebar"]["x"].container_componenty_container=tpl.locations["sidebar"]["y"].container_componentoutput_component=tpl.locations["main"][0].container_componentapp.layout=html.Div(
[
html.H1("Iris Feature Explorer"),
html.H2("Select Features"),
x_container,
y_container,
html.Hr(),
html.H2("Feature Scatter Plot"),
output_component,
]
)
if__name__=="__main__":
app.run_server(debug=True)

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

History
485 lines (355 loc) · 20.3 KB

File metadata and controls

485 lines (355 loc) · 20.3 KB

Status: Template Layout System

Based on community feedback, this version of the template layout system will not be added to a future version of Dash. However, the work done here inspired many new features, such as:

- New in Dash 2.1: The low-code shorthands for Dash Core Components and the dash DataTable.

- New in Dash 2.1, The Input, State, and Output accepts components instead of ID strings. Dash callback will auto-generate the component's ID under-the-hood if not supplied.

- Available in the dash-bootstrap-templates library: Bootstrap themed figures.

We appreciate everyone's input on the template system. Templates are still in the dash-labs project plan, so stay tuned for a new version!

- ----------------------------------------------------------------------------------- This documentation describes code in a previous version of dash-labs (v0.4.0) - and is included here for legacy purposes only.-- You can install v0.4.0 with:- pip install dash-labs==0.4.0- ----------------------------------------------------------------------------------

The template layout system

Dash Labs introduces a template system that makes it possible to quickly add components to a pre-defined template.

Manually add components to a template

As will be described below, the template system integrates with @app.callback, but templates can also be used independently of @app.callback.

Templates that are included with Dash Labs are located in the dl.templates package. The convention is to assign a template instance to a variable named tpl. Components can then be added to the template with tpl.add_component

Here is a simple example of manually adding components to a DbcCard template

demos/template_system1.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=4)
div=html.Div()
button=html.Button(children="Click Me")
@app.callback(dl.Output(div, "children"), dl.Input(button, "n_clicks"))defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
tpl.add_component(button, label="Button to click", location="bottom")
tpl.add_component(div, location="top")
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Component location

When a component is added to a template using add_component, it is associated with a template location using the location argument. Components that share the same location will be grouped together by the template in the component layout it produces. Templates document the locations that they support in the constructor docstring. Here is the docstring for the DbcCard template used above.

 Template that places all components in a single card
Supported template locations:
- "bottom": Bottom region of the card (default for Input components)
- "top": Top region of the card (default for Output components)
...

Component label

When a component is added to a template using add_component, it may optionally be associated with a label string using the label argument. When provided, the template will wrap the provided component with a label in the component layout it produces.

Template children

Templates provide a .children property that returns a container that includes the components that were added to the template. This container is a regular Dash component that can be assigned to app.layout, or combined with other components to build app.layout.

Note: When using a template based on Dash Bootstrap Components, it's recommended to use dbc.Container as the top-level layout component, and to assign the template's children as the children of the dbc.Container. See https://dash-bootstrap-components.opensource.faculty.ai/docs/components/layout/ for more information. Similarly, when using a template based on Dash Design Kit, it's recommended to use ddk.App as the top-level layout component, and to assign the template's children as the children of the ddk.App.

template and app.callback integration

For convenience, @app.callback accepts an optional template argument. When provided, @app.callback will automatically add the provided input and output components to the template. Because of the information that @app.callback already has access to, it can choose reasonable defaults for each component's location and label. Because the components will be added to the template, it becomes possible to construct components inline in the @app.callback definition, rather than constructing them above and assigning them to local variables. With these conveniences, the example above becomes:

demos/template_system2.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(dl.Output(html.Div(), "children"),dl.Input(html.Button(children="Click Me"), "n_clicks", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Customize labels and locations

When a template is populated using @app.callback, the label string and location for a component can be overridden using the label and location keyword arguments to dl.Input/dl.State/dl.Output. See the "Button to click" label added above.

Default output

When a template is provided, and no Output dependency is provided, the template will provide a default output container for the result of the function (typically an html.Div component).

demos/template_system3.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(dl.Input(html.Button(children="Click Me"), "n_clicks", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Template component builders

To reduce the amount of boilerplate required to construct the dependency components to pass to @app.callback, template classes provide a variety of helper functions. A few examples are tpl.new_div(), tpl.new_button(), tpl.new_dropdown(), etc. These are relatively simple class methods that return a dependency object wrapping a component. For example:

tpl.new_dropdown(["A", "B", "C"], label="My Dropdown")

evaluates to...

dl.Input(
dcc.Dropdown(
id={'uid': 'd4713d60-c8a7-0639-eb11-67b367a9c378'},
options=[
{'value': 'A', 'label': 'A'}, {'value': 'B', 'label': 'B'},
{'value': 'C', 'label': 'C'}
],
value='A',
clearable=False
),
"value",
label="My Dropdown"
)

All of these functions provide the following keyword arguments:

  • label: The label to display for the component.
  • location: The template location of the component.
  • component_property: The property (or property grouping) considered to be the value of the component. This value is optional, and the template will provide a reasonable default for each component type (e.g. n_clicks for dcc.Button, value for dcc.Dropdown, figure for dcc.Graph).
  • kind: The dependency class to return. One of dl.Input, dl.State, or dl.Output. This value is optional and templates will provide a reasonable defaults (e.g. dl.Input for dcc.Button and dcc.Dropdown, dl.Output for dcc.Graph, etc.)
  • id: Optional argument to override the generated component id
  • opts: Dictionary of keyword arguments to pass to the constructor of the component that is created.

In addition to these standard keyword arguments, component builders also provide args to make the configuration of the components as concise as possible. e.g. dl.dropdown_input(["A", "B", "C]), dl.slider_input(0, 10).

These component builders can significantly shorten many @app.callback specifications.

Here is an update to the previous example that uses the tpl.new_button component constructor instead of manually creating dl.Input and html.Button objects.

demos/template_system4.py

importdash_bootstrap_componentsasdbcimportdash_labsasdlimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(tpl.new_button("Click Me", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Component builder specialization

Another advantage of the component builder paradigm is that templates can specialize the representation of the different components. For example, Dash Bootstrap templates can use dbc.Select in place of dcc.Dropdown for tpl.new_dropdown(). Similarly, DDK templates can use ddk.Graph in place of dcc.Graph for tpl.new_graph().

Manually executed function using state

The ipywidgets @interact decorator supports a manual argument. When True, an update button is automatically added and changes to the other widgets are not applied until the update button is clicked. This workflow can be replicated with @app.callback by adding a button component and specifying that all inputs other than the button should be classified as State (rather than the default of Input).

Here is a full example of specifying all the components except the button as kind=dl.State to @app.callback.

demos/basic_decorator_manual.py

importdashimportdash_labsasdlimportnumpyasnpimportdash_core_componentsasdccimportplotly.expressaspximportdash_bootstrap_componentsasdbcapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcRow(app, title="Manual Update", theme=dbc.themes.SOLAR)
@app.callback(args=dict(fun=tpl.new_dropdown(["sin", "cos", "exp"], label="Function", kind=dl.State),figure_title=tpl.new_textbox("Initial Title", label="Figure Title", kind=dl.State ),phase=tpl.new_slider(1, 10, label="Phase", kind=dl.State),amplitude=tpl.new_slider(1, 10, value=3, label="Amplitude", kind=dl.State),n_clicks=tpl.new_button("Update", label=None), ),template=tpl,)defgreet(fun, figure_title, phase, amplitude, n_clicks):
print(fun, figure_title, phase, amplitude)
xs=np.linspace(-10, 10, 100)
returndcc.Graph(
figure=px.line(x=xs, y=getattr(np, fun)(xs+phase) *amplitude).update_layout(
title_text=figure_title
)
)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Custom output components

When a template is provided, the new @app.callback decorator no longer requires a caller to explicitly provide the output component that the callback function result will be stored in. However, explicit output components and output properties can still be configured.

Here is an example that outputs a string to the children property of a dcc.Markdown component.

Note that the default value of kind for tpl.new_markdown is dl.Output, which is why it's not necessary to override the kind argument.

demos/output_markdown.py

importdashimportdash_labsasdlimportdash_bootstrap_componentsasdbcapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, "App Title", sidebar_columns=6)
@app.callback(output=tpl.new_markdown(),args=tpl.new_textarea("## Heading\n", opts=dict(style={"width": "100%", "height": 400}) ),template=tpl,)defmarkdown_preview(input_text):
returninput_textapp.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Adding additional components to a template

Additional components can be added to a template after the initial components are added by @app.callback.

Note how the add_component method supports before and after keyword arguments that can be used to insert new components at specific locations between components added by app.callback.

demos/template_with_custom_additions.py

importdashimportdash_labsasdlimportnumpyasnpimportdash_core_componentsasdccimportdash_bootstrap_componentsasdbcimportplotly.expressaspxapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, title="Dash Labs App")
# import dash_core_components as dcc@app.callback(inputs=dict(fun=tpl.new_dropdown(["sin", "cos", "exp"], label="Function"),figure_title=tpl.new_textbox("Initial Title", label="Figure Title"),phase=tpl.new_slider(1, 10, value=3, label="Phase"),amplitude=tpl.new_slider(1, 10, value=4, label="Amplitude"), ),output=tpl.new_graph(),template=tpl,)deffunction_browser(fun, figure_title, phase, amplitude):
xs=np.linspace(-10, 10, 100)
returnpx.line(x=xs, y=getattr(np, fun)(xs+phase) *amplitude).update_layout(
title_text=figure_title
)
# Add extra component to templatetpl.add_component(
dcc.Markdown(children="# First Group"), location="sidebar", before="fun"
)
tpl.add_component(
dcc.Markdown(
children=[
"# Second Group\n""Specify the Phase and Amplitudue for the chosen function"
]
),
location="sidebar",
before="phase",
)
tpl.add_component(
dcc.Markdown(children=["# H2 Title\n", "Here is the *main* plot"]),
location="main",
before=0,
)
tpl.add_component(
dcc.Link("Made with Dash", href="https://dash.plotly.com/"),
component_property="children",
location="main",
)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Advanced: Accessing individual components to build custom layouts

This section describes how to retrieve components that have been added to, and created by, a template. It is intended mostly for information purposes, and is not intended to be a common workflow.

Template locations property

The components added to a template are stored in the .locations property.

This is a dictionary from template location to OrderedDicts of ArgumentComponents (described below).

ArgumentComponents

You might think that the values of the .location dictionaries described above would simply be collections of the components added to the template. The reason it's not quite that simple is that for a single component added to a template, the template may create multiple components: There is the original component, one for the label, and both of these may be wrapped in a container component. Because the caller may want access to any, or all, of these components individually, references to all of these components, and their associated props, are stored in a ArgumentComponents instance. Here are the attributes of ArgumentComponents, and an example of why a caller may want to access them.

  • .arg_component: This a reference to the innermost component that actually provides the callback function with an input value, which corresponds to the properties stored in .arg_property attribute. A caller would want to access this component in order to register additional callback functions to execute when the callback function is updated.
  • .label_component: This is the component that displays the label string for the component, where the label text is stored in the .label_property property of the component. A caller may want to access this component to customize the label styling, or access the current value of the label string.
  • .container_component: This is the outer-most component that contains all the other components described above, where the contained components are stored in the .container_property property of the container. This is generally the component that a caller would incorporate when building a custom layout.

This example uses @app.callback to add components to a template, constructs a fully custom layout, and defines custom callbacks on the components returned by @app.callback. This is loosely based on the Dash Bootstrap Components example at https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/.

Notice how custom callbacks are applied to the dropdowns returned by @app.callback to prevent specifying the same feature as both x and y values.

demos/custom_layout_and_callback_integration.py

# Based on https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/# Based on https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/importdash_labsasdlimportplotly.expressaspximportdash_core_componentsasdccimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcfromdash.dependenciesimportInput, Outputimportdash# Load datadf=px.data.iris()
feature_cols= [colforcolindf.columnsif"species"notincol]
feature_labels= [col.replace("_", " ").title() +" (cm)"forcolinfeature_cols]
feature_options= [
{"label": label, "value": col} forcol, labelinzip(feature_cols, feature_labels)
]
# Build app and templateapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, title="Iris Dataset")
# Use parameterize to create components@app.callback(args=dict(x=dl.Input(dcc.Dropdown(options=feature_options, value="sepal_length")),y=dl.Input(dcc.Dropdown(options=feature_options, value="sepal_width")), ),template=tpl,)defiris(x, y):
returndcc.Graph(
figure=px.scatter(df, x=x, y=y, color="species"),
)
# Get references to the dropdowns and register a custom callback to prevent the user# from setting x and y to the same variable# Get the dropdown components that were created by parameterizex_component=tpl.locations["sidebar"]["x"].arg_componenty_component=tpl.locations["sidebar"]["y"].arg_component# Define standalone function that computes what values to enable, reuse for both# dropdowns with app.callbackdeffilter_options(v):
"""Disable option ability to plot x vs x"""return [
{"label": label, "value": col, "disabled": col==v}
forcol, labelinzip(feature_cols, feature_labels)
]
app.callback(Output(x_component.id, "options"), [Input(y_component.id, "value")])(
filter_options
)
app.callback(Output(y_component.id, "options"), [Input(x_component.id, "value")])(
filter_options
)
x_container=tpl.locations["sidebar"]["x"].container_componenty_container=tpl.locations["sidebar"]["y"].container_componentoutput_component=tpl.locations["main"][0].container_componentapp.layout=html.Div(
[
html.H1("Iris Feature Explorer"),
html.H2("Select Features"),
x_container,
y_container,
html.Hr(),
html.H2("Feature Scatter Plot"),
output_component,
]
)
if__name__=="__main__":
app.run_server(debug=True)

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

History
485 lines (355 loc) · 20.3 KB

File metadata and controls

485 lines (355 loc) · 20.3 KB

Status: Template Layout System

Based on community feedback, this version of the template layout system will not be added to a future version of Dash. However, the work done here inspired many new features, such as:

- New in Dash 2.1: The low-code shorthands for Dash Core Components and the dash DataTable.

- New in Dash 2.1, The Input, State, and Output accepts components instead of ID strings. Dash callback will auto-generate the component's ID under-the-hood if not supplied.

- Available in the dash-bootstrap-templates library: Bootstrap themed figures.

We appreciate everyone's input on the template system. Templates are still in the dash-labs project plan, so stay tuned for a new version!

- ----------------------------------------------------------------------------------- This documentation describes code in a previous version of dash-labs (v0.4.0) - and is included here for legacy purposes only.-- You can install v0.4.0 with:- pip install dash-labs==0.4.0- ----------------------------------------------------------------------------------

The template layout system

Dash Labs introduces a template system that makes it possible to quickly add components to a pre-defined template.

Manually add components to a template

As will be described below, the template system integrates with @app.callback, but templates can also be used independently of @app.callback.

Templates that are included with Dash Labs are located in the dl.templates package. The convention is to assign a template instance to a variable named tpl. Components can then be added to the template with tpl.add_component

Here is a simple example of manually adding components to a DbcCard template

demos/template_system1.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=4)
div=html.Div()
button=html.Button(children="Click Me")
@app.callback(dl.Output(div, "children"), dl.Input(button, "n_clicks"))defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
tpl.add_component(button, label="Button to click", location="bottom")
tpl.add_component(div, location="top")
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Component location

When a component is added to a template using add_component, it is associated with a template location using the location argument. Components that share the same location will be grouped together by the template in the component layout it produces. Templates document the locations that they support in the constructor docstring. Here is the docstring for the DbcCard template used above.

 Template that places all components in a single card
Supported template locations:
- "bottom": Bottom region of the card (default for Input components)
- "top": Top region of the card (default for Output components)
...

Component label

When a component is added to a template using add_component, it may optionally be associated with a label string using the label argument. When provided, the template will wrap the provided component with a label in the component layout it produces.

Template children

Templates provide a .children property that returns a container that includes the components that were added to the template. This container is a regular Dash component that can be assigned to app.layout, or combined with other components to build app.layout.

Note: When using a template based on Dash Bootstrap Components, it's recommended to use dbc.Container as the top-level layout component, and to assign the template's children as the children of the dbc.Container. See https://dash-bootstrap-components.opensource.faculty.ai/docs/components/layout/ for more information. Similarly, when using a template based on Dash Design Kit, it's recommended to use ddk.App as the top-level layout component, and to assign the template's children as the children of the ddk.App.

template and app.callback integration

For convenience, @app.callback accepts an optional template argument. When provided, @app.callback will automatically add the provided input and output components to the template. Because of the information that @app.callback already has access to, it can choose reasonable defaults for each component's location and label. Because the components will be added to the template, it becomes possible to construct components inline in the @app.callback definition, rather than constructing them above and assigning them to local variables. With these conveniences, the example above becomes:

demos/template_system2.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(dl.Output(html.Div(), "children"),dl.Input(html.Button(children="Click Me"), "n_clicks", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Customize labels and locations

When a template is populated using @app.callback, the label string and location for a component can be overridden using the label and location keyword arguments to dl.Input/dl.State/dl.Output. See the "Button to click" label added above.

Default output

When a template is provided, and no Output dependency is provided, the template will provide a default output container for the result of the function (typically an html.Div component).

demos/template_system3.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(dl.Input(html.Button(children="Click Me"), "n_clicks", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Template component builders

To reduce the amount of boilerplate required to construct the dependency components to pass to @app.callback, template classes provide a variety of helper functions. A few examples are tpl.new_div(), tpl.new_button(), tpl.new_dropdown(), etc. These are relatively simple class methods that return a dependency object wrapping a component. For example:

tpl.new_dropdown(["A", "B", "C"], label="My Dropdown")

evaluates to...

dl.Input(
dcc.Dropdown(
id={'uid': 'd4713d60-c8a7-0639-eb11-67b367a9c378'},
options=[
{'value': 'A', 'label': 'A'}, {'value': 'B', 'label': 'B'},
{'value': 'C', 'label': 'C'}
],
value='A',
clearable=False
),
"value",
label="My Dropdown"
)

All of these functions provide the following keyword arguments:

  • label: The label to display for the component.
  • location: The template location of the component.
  • component_property: The property (or property grouping) considered to be the value of the component. This value is optional, and the template will provide a reasonable default for each component type (e.g. n_clicks for dcc.Button, value for dcc.Dropdown, figure for dcc.Graph).
  • kind: The dependency class to return. One of dl.Input, dl.State, or dl.Output. This value is optional and templates will provide a reasonable defaults (e.g. dl.Input for dcc.Button and dcc.Dropdown, dl.Output for dcc.Graph, etc.)
  • id: Optional argument to override the generated component id
  • opts: Dictionary of keyword arguments to pass to the constructor of the component that is created.

In addition to these standard keyword arguments, component builders also provide args to make the configuration of the components as concise as possible. e.g. dl.dropdown_input(["A", "B", "C]), dl.slider_input(0, 10).

These component builders can significantly shorten many @app.callback specifications.

Here is an update to the previous example that uses the tpl.new_button component constructor instead of manually creating dl.Input and html.Button objects.

demos/template_system4.py

importdash_bootstrap_componentsasdbcimportdash_labsasdlimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(tpl.new_button("Click Me", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Component builder specialization

Another advantage of the component builder paradigm is that templates can specialize the representation of the different components. For example, Dash Bootstrap templates can use dbc.Select in place of dcc.Dropdown for tpl.new_dropdown(). Similarly, DDK templates can use ddk.Graph in place of dcc.Graph for tpl.new_graph().

Manually executed function using state

The ipywidgets @interact decorator supports a manual argument. When True, an update button is automatically added and changes to the other widgets are not applied until the update button is clicked. This workflow can be replicated with @app.callback by adding a button component and specifying that all inputs other than the button should be classified as State (rather than the default of Input).

Here is a full example of specifying all the components except the button as kind=dl.State to @app.callback.

demos/basic_decorator_manual.py

importdashimportdash_labsasdlimportnumpyasnpimportdash_core_componentsasdccimportplotly.expressaspximportdash_bootstrap_componentsasdbcapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcRow(app, title="Manual Update", theme=dbc.themes.SOLAR)
@app.callback(args=dict(fun=tpl.new_dropdown(["sin", "cos", "exp"], label="Function", kind=dl.State),figure_title=tpl.new_textbox("Initial Title", label="Figure Title", kind=dl.State ),phase=tpl.new_slider(1, 10, label="Phase", kind=dl.State),amplitude=tpl.new_slider(1, 10, value=3, label="Amplitude", kind=dl.State),n_clicks=tpl.new_button("Update", label=None), ),template=tpl,)defgreet(fun, figure_title, phase, amplitude, n_clicks):
print(fun, figure_title, phase, amplitude)
xs=np.linspace(-10, 10, 100)
returndcc.Graph(
figure=px.line(x=xs, y=getattr(np, fun)(xs+phase) *amplitude).update_layout(
title_text=figure_title
)
)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Custom output components

When a template is provided, the new @app.callback decorator no longer requires a caller to explicitly provide the output component that the callback function result will be stored in. However, explicit output components and output properties can still be configured.

Here is an example that outputs a string to the children property of a dcc.Markdown component.

Note that the default value of kind for tpl.new_markdown is dl.Output, which is why it's not necessary to override the kind argument.

demos/output_markdown.py

importdashimportdash_labsasdlimportdash_bootstrap_componentsasdbcapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, "App Title", sidebar_columns=6)
@app.callback(output=tpl.new_markdown(),args=tpl.new_textarea("## Heading\n", opts=dict(style={"width": "100%", "height": 400}) ),template=tpl,)defmarkdown_preview(input_text):
returninput_textapp.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Adding additional components to a template

Additional components can be added to a template after the initial components are added by @app.callback.

Note how the add_component method supports before and after keyword arguments that can be used to insert new components at specific locations between components added by app.callback.

demos/template_with_custom_additions.py

importdashimportdash_labsasdlimportnumpyasnpimportdash_core_componentsasdccimportdash_bootstrap_componentsasdbcimportplotly.expressaspxapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, title="Dash Labs App")
# import dash_core_components as dcc@app.callback(inputs=dict(fun=tpl.new_dropdown(["sin", "cos", "exp"], label="Function"),figure_title=tpl.new_textbox("Initial Title", label="Figure Title"),phase=tpl.new_slider(1, 10, value=3, label="Phase"),amplitude=tpl.new_slider(1, 10, value=4, label="Amplitude"), ),output=tpl.new_graph(),template=tpl,)deffunction_browser(fun, figure_title, phase, amplitude):
xs=np.linspace(-10, 10, 100)
returnpx.line(x=xs, y=getattr(np, fun)(xs+phase) *amplitude).update_layout(
title_text=figure_title
)
# Add extra component to templatetpl.add_component(
dcc.Markdown(children="# First Group"), location="sidebar", before="fun"
)
tpl.add_component(
dcc.Markdown(
children=[
"# Second Group\n""Specify the Phase and Amplitudue for the chosen function"
]
),
location="sidebar",
before="phase",
)
tpl.add_component(
dcc.Markdown(children=["# H2 Title\n", "Here is the *main* plot"]),
location="main",
before=0,
)
tpl.add_component(
dcc.Link("Made with Dash", href="https://dash.plotly.com/"),
component_property="children",
location="main",
)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Advanced: Accessing individual components to build custom layouts

This section describes how to retrieve components that have been added to, and created by, a template. It is intended mostly for information purposes, and is not intended to be a common workflow.

Template locations property

The components added to a template are stored in the .locations property.

This is a dictionary from template location to OrderedDicts of ArgumentComponents (described below).

ArgumentComponents

You might think that the values of the .location dictionaries described above would simply be collections of the components added to the template. The reason it's not quite that simple is that for a single component added to a template, the template may create multiple components: There is the original component, one for the label, and both of these may be wrapped in a container component. Because the caller may want access to any, or all, of these components individually, references to all of these components, and their associated props, are stored in a ArgumentComponents instance. Here are the attributes of ArgumentComponents, and an example of why a caller may want to access them.

  • .arg_component: This a reference to the innermost component that actually provides the callback function with an input value, which corresponds to the properties stored in .arg_property attribute. A caller would want to access this component in order to register additional callback functions to execute when the callback function is updated.
  • .label_component: This is the component that displays the label string for the component, where the label text is stored in the .label_property property of the component. A caller may want to access this component to customize the label styling, or access the current value of the label string.
  • .container_component: This is the outer-most component that contains all the other components described above, where the contained components are stored in the .container_property property of the container. This is generally the component that a caller would incorporate when building a custom layout.

This example uses @app.callback to add components to a template, constructs a fully custom layout, and defines custom callbacks on the components returned by @app.callback. This is loosely based on the Dash Bootstrap Components example at https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/.

Notice how custom callbacks are applied to the dropdowns returned by @app.callback to prevent specifying the same feature as both x and y values.

demos/custom_layout_and_callback_integration.py

# Based on https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/# Based on https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/importdash_labsasdlimportplotly.expressaspximportdash_core_componentsasdccimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcfromdash.dependenciesimportInput, Outputimportdash# Load datadf=px.data.iris()
feature_cols= [colforcolindf.columnsif"species"notincol]
feature_labels= [col.replace("_", " ").title() +" (cm)"forcolinfeature_cols]
feature_options= [
{"label": label, "value": col} forcol, labelinzip(feature_cols, feature_labels)
]
# Build app and templateapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, title="Iris Dataset")
# Use parameterize to create components@app.callback(args=dict(x=dl.Input(dcc.Dropdown(options=feature_options, value="sepal_length")),y=dl.Input(dcc.Dropdown(options=feature_options, value="sepal_width")), ),template=tpl,)defiris(x, y):
returndcc.Graph(
figure=px.scatter(df, x=x, y=y, color="species"),
)
# Get references to the dropdowns and register a custom callback to prevent the user# from setting x and y to the same variable# Get the dropdown components that were created by parameterizex_component=tpl.locations["sidebar"]["x"].arg_componenty_component=tpl.locations["sidebar"]["y"].arg_component# Define standalone function that computes what values to enable, reuse for both# dropdowns with app.callbackdeffilter_options(v):
"""Disable option ability to plot x vs x"""return [
{"label": label, "value": col, "disabled": col==v}
forcol, labelinzip(feature_cols, feature_labels)
]
app.callback(Output(x_component.id, "options"), [Input(y_component.id, "value")])(
filter_options
)
app.callback(Output(y_component.id, "options"), [Input(x_component.id, "value")])(
filter_options
)
x_container=tpl.locations["sidebar"]["x"].container_componenty_container=tpl.locations["sidebar"]["y"].container_componentoutput_component=tpl.locations["main"][0].container_componentapp.layout=html.Div(
[
html.H1("Iris Feature Explorer"),
html.H2("Select Features"),
x_container,
y_container,
html.Hr(),
html.H2("Feature Scatter Plot"),
output_component,
]
)
if__name__=="__main__":
app.run_server(debug=True)

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

History
485 lines (355 loc) · 20.3 KB

File metadata and controls

485 lines (355 loc) · 20.3 KB

Status: Template Layout System

Based on community feedback, this version of the template layout system will not be added to a future version of Dash. However, the work done here inspired many new features, such as:

- New in Dash 2.1: The low-code shorthands for Dash Core Components and the dash DataTable.

- New in Dash 2.1, The Input, State, and Output accepts components instead of ID strings. Dash callback will auto-generate the component's ID under-the-hood if not supplied.

- Available in the dash-bootstrap-templates library: Bootstrap themed figures.

We appreciate everyone's input on the template system. Templates are still in the dash-labs project plan, so stay tuned for a new version!

- ----------------------------------------------------------------------------------- This documentation describes code in a previous version of dash-labs (v0.4.0) - and is included here for legacy purposes only.-- You can install v0.4.0 with:- pip install dash-labs==0.4.0- ----------------------------------------------------------------------------------

The template layout system

Dash Labs introduces a template system that makes it possible to quickly add components to a pre-defined template.

Manually add components to a template

As will be described below, the template system integrates with @app.callback, but templates can also be used independently of @app.callback.

Templates that are included with Dash Labs are located in the dl.templates package. The convention is to assign a template instance to a variable named tpl. Components can then be added to the template with tpl.add_component

Here is a simple example of manually adding components to a DbcCard template

demos/template_system1.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=4)
div=html.Div()
button=html.Button(children="Click Me")
@app.callback(dl.Output(div, "children"), dl.Input(button, "n_clicks"))defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
tpl.add_component(button, label="Button to click", location="bottom")
tpl.add_component(div, location="top")
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Component location

When a component is added to a template using add_component, it is associated with a template location using the location argument. Components that share the same location will be grouped together by the template in the component layout it produces. Templates document the locations that they support in the constructor docstring. Here is the docstring for the DbcCard template used above.

 Template that places all components in a single card
Supported template locations:
- "bottom": Bottom region of the card (default for Input components)
- "top": Top region of the card (default for Output components)
...

Component label

When a component is added to a template using add_component, it may optionally be associated with a label string using the label argument. When provided, the template will wrap the provided component with a label in the component layout it produces.

Template children

Templates provide a .children property that returns a container that includes the components that were added to the template. This container is a regular Dash component that can be assigned to app.layout, or combined with other components to build app.layout.

Note: When using a template based on Dash Bootstrap Components, it's recommended to use dbc.Container as the top-level layout component, and to assign the template's children as the children of the dbc.Container. See https://dash-bootstrap-components.opensource.faculty.ai/docs/components/layout/ for more information. Similarly, when using a template based on Dash Design Kit, it's recommended to use ddk.App as the top-level layout component, and to assign the template's children as the children of the ddk.App.

template and app.callback integration

For convenience, @app.callback accepts an optional template argument. When provided, @app.callback will automatically add the provided input and output components to the template. Because of the information that @app.callback already has access to, it can choose reasonable defaults for each component's location and label. Because the components will be added to the template, it becomes possible to construct components inline in the @app.callback definition, rather than constructing them above and assigning them to local variables. With these conveniences, the example above becomes:

demos/template_system2.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(dl.Output(html.Div(), "children"),dl.Input(html.Button(children="Click Me"), "n_clicks", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Customize labels and locations

When a template is populated using @app.callback, the label string and location for a component can be overridden using the label and location keyword arguments to dl.Input/dl.State/dl.Output. See the "Button to click" label added above.

Default output

When a template is provided, and no Output dependency is provided, the template will provide a default output container for the result of the function (typically an html.Div component).

demos/template_system3.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(dl.Input(html.Button(children="Click Me"), "n_clicks", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Template component builders

To reduce the amount of boilerplate required to construct the dependency components to pass to @app.callback, template classes provide a variety of helper functions. A few examples are tpl.new_div(), tpl.new_button(), tpl.new_dropdown(), etc. These are relatively simple class methods that return a dependency object wrapping a component. For example:

tpl.new_dropdown(["A", "B", "C"], label="My Dropdown")

evaluates to...

dl.Input(
dcc.Dropdown(
id={'uid': 'd4713d60-c8a7-0639-eb11-67b367a9c378'},
options=[
{'value': 'A', 'label': 'A'}, {'value': 'B', 'label': 'B'},
{'value': 'C', 'label': 'C'}
],
value='A',
clearable=False
),
"value",
label="My Dropdown"
)

All of these functions provide the following keyword arguments:

  • label: The label to display for the component.
  • location: The template location of the component.
  • component_property: The property (or property grouping) considered to be the value of the component. This value is optional, and the template will provide a reasonable default for each component type (e.g. n_clicks for dcc.Button, value for dcc.Dropdown, figure for dcc.Graph).
  • kind: The dependency class to return. One of dl.Input, dl.State, or dl.Output. This value is optional and templates will provide a reasonable defaults (e.g. dl.Input for dcc.Button and dcc.Dropdown, dl.Output for dcc.Graph, etc.)
  • id: Optional argument to override the generated component id
  • opts: Dictionary of keyword arguments to pass to the constructor of the component that is created.

In addition to these standard keyword arguments, component builders also provide args to make the configuration of the components as concise as possible. e.g. dl.dropdown_input(["A", "B", "C]), dl.slider_input(0, 10).

These component builders can significantly shorten many @app.callback specifications.

Here is an update to the previous example that uses the tpl.new_button component constructor instead of manually creating dl.Input and html.Button objects.

demos/template_system4.py

importdash_bootstrap_componentsasdbcimportdash_labsasdlimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(tpl.new_button("Click Me", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Component builder specialization

Another advantage of the component builder paradigm is that templates can specialize the representation of the different components. For example, Dash Bootstrap templates can use dbc.Select in place of dcc.Dropdown for tpl.new_dropdown(). Similarly, DDK templates can use ddk.Graph in place of dcc.Graph for tpl.new_graph().

Manually executed function using state

The ipywidgets @interact decorator supports a manual argument. When True, an update button is automatically added and changes to the other widgets are not applied until the update button is clicked. This workflow can be replicated with @app.callback by adding a button component and specifying that all inputs other than the button should be classified as State (rather than the default of Input).

Here is a full example of specifying all the components except the button as kind=dl.State to @app.callback.

demos/basic_decorator_manual.py

importdashimportdash_labsasdlimportnumpyasnpimportdash_core_componentsasdccimportplotly.expressaspximportdash_bootstrap_componentsasdbcapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcRow(app, title="Manual Update", theme=dbc.themes.SOLAR)
@app.callback(args=dict(fun=tpl.new_dropdown(["sin", "cos", "exp"], label="Function", kind=dl.State),figure_title=tpl.new_textbox("Initial Title", label="Figure Title", kind=dl.State ),phase=tpl.new_slider(1, 10, label="Phase", kind=dl.State),amplitude=tpl.new_slider(1, 10, value=3, label="Amplitude", kind=dl.State),n_clicks=tpl.new_button("Update", label=None), ),template=tpl,)defgreet(fun, figure_title, phase, amplitude, n_clicks):
print(fun, figure_title, phase, amplitude)
xs=np.linspace(-10, 10, 100)
returndcc.Graph(
figure=px.line(x=xs, y=getattr(np, fun)(xs+phase) *amplitude).update_layout(
title_text=figure_title
)
)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Custom output components

When a template is provided, the new @app.callback decorator no longer requires a caller to explicitly provide the output component that the callback function result will be stored in. However, explicit output components and output properties can still be configured.

Here is an example that outputs a string to the children property of a dcc.Markdown component.

Note that the default value of kind for tpl.new_markdown is dl.Output, which is why it's not necessary to override the kind argument.

demos/output_markdown.py

importdashimportdash_labsasdlimportdash_bootstrap_componentsasdbcapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, "App Title", sidebar_columns=6)
@app.callback(output=tpl.new_markdown(),args=tpl.new_textarea("## Heading\n", opts=dict(style={"width": "100%", "height": 400}) ),template=tpl,)defmarkdown_preview(input_text):
returninput_textapp.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Adding additional components to a template

Additional components can be added to a template after the initial components are added by @app.callback.

Note how the add_component method supports before and after keyword arguments that can be used to insert new components at specific locations between components added by app.callback.

demos/template_with_custom_additions.py

importdashimportdash_labsasdlimportnumpyasnpimportdash_core_componentsasdccimportdash_bootstrap_componentsasdbcimportplotly.expressaspxapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, title="Dash Labs App")
# import dash_core_components as dcc@app.callback(inputs=dict(fun=tpl.new_dropdown(["sin", "cos", "exp"], label="Function"),figure_title=tpl.new_textbox("Initial Title", label="Figure Title"),phase=tpl.new_slider(1, 10, value=3, label="Phase"),amplitude=tpl.new_slider(1, 10, value=4, label="Amplitude"), ),output=tpl.new_graph(),template=tpl,)deffunction_browser(fun, figure_title, phase, amplitude):
xs=np.linspace(-10, 10, 100)
returnpx.line(x=xs, y=getattr(np, fun)(xs+phase) *amplitude).update_layout(
title_text=figure_title
)
# Add extra component to templatetpl.add_component(
dcc.Markdown(children="# First Group"), location="sidebar", before="fun"
)
tpl.add_component(
dcc.Markdown(
children=[
"# Second Group\n""Specify the Phase and Amplitudue for the chosen function"
]
),
location="sidebar",
before="phase",
)
tpl.add_component(
dcc.Markdown(children=["# H2 Title\n", "Here is the *main* plot"]),
location="main",
before=0,
)
tpl.add_component(
dcc.Link("Made with Dash", href="https://dash.plotly.com/"),
component_property="children",
location="main",
)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Advanced: Accessing individual components to build custom layouts

This section describes how to retrieve components that have been added to, and created by, a template. It is intended mostly for information purposes, and is not intended to be a common workflow.

Template locations property

The components added to a template are stored in the .locations property.

This is a dictionary from template location to OrderedDicts of ArgumentComponents (described below).

ArgumentComponents

You might think that the values of the .location dictionaries described above would simply be collections of the components added to the template. The reason it's not quite that simple is that for a single component added to a template, the template may create multiple components: There is the original component, one for the label, and both of these may be wrapped in a container component. Because the caller may want access to any, or all, of these components individually, references to all of these components, and their associated props, are stored in a ArgumentComponents instance. Here are the attributes of ArgumentComponents, and an example of why a caller may want to access them.

  • .arg_component: This a reference to the innermost component that actually provides the callback function with an input value, which corresponds to the properties stored in .arg_property attribute. A caller would want to access this component in order to register additional callback functions to execute when the callback function is updated.
  • .label_component: This is the component that displays the label string for the component, where the label text is stored in the .label_property property of the component. A caller may want to access this component to customize the label styling, or access the current value of the label string.
  • .container_component: This is the outer-most component that contains all the other components described above, where the contained components are stored in the .container_property property of the container. This is generally the component that a caller would incorporate when building a custom layout.

This example uses @app.callback to add components to a template, constructs a fully custom layout, and defines custom callbacks on the components returned by @app.callback. This is loosely based on the Dash Bootstrap Components example at https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/.

Notice how custom callbacks are applied to the dropdowns returned by @app.callback to prevent specifying the same feature as both x and y values.

demos/custom_layout_and_callback_integration.py

# Based on https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/# Based on https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/importdash_labsasdlimportplotly.expressaspximportdash_core_componentsasdccimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcfromdash.dependenciesimportInput, Outputimportdash# Load datadf=px.data.iris()
feature_cols= [colforcolindf.columnsif"species"notincol]
feature_labels= [col.replace("_", " ").title() +" (cm)"forcolinfeature_cols]
feature_options= [
{"label": label, "value": col} forcol, labelinzip(feature_cols, feature_labels)
]
# Build app and templateapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, title="Iris Dataset")
# Use parameterize to create components@app.callback(args=dict(x=dl.Input(dcc.Dropdown(options=feature_options, value="sepal_length")),y=dl.Input(dcc.Dropdown(options=feature_options, value="sepal_width")), ),template=tpl,)defiris(x, y):
returndcc.Graph(
figure=px.scatter(df, x=x, y=y, color="species"),
)
# Get references to the dropdowns and register a custom callback to prevent the user# from setting x and y to the same variable# Get the dropdown components that were created by parameterizex_component=tpl.locations["sidebar"]["x"].arg_componenty_component=tpl.locations["sidebar"]["y"].arg_component# Define standalone function that computes what values to enable, reuse for both# dropdowns with app.callbackdeffilter_options(v):
"""Disable option ability to plot x vs x"""return [
{"label": label, "value": col, "disabled": col==v}
forcol, labelinzip(feature_cols, feature_labels)
]
app.callback(Output(x_component.id, "options"), [Input(y_component.id, "value")])(
filter_options
)
app.callback(Output(y_component.id, "options"), [Input(x_component.id, "value")])(
filter_options
)
x_container=tpl.locations["sidebar"]["x"].container_componenty_container=tpl.locations["sidebar"]["y"].container_componentoutput_component=tpl.locations["main"][0].container_componentapp.layout=html.Div(
[
html.H1("Iris Feature Explorer"),
html.H2("Select Features"),
x_container,
y_container,
html.Hr(),
html.H2("Feature Scatter Plot"),
output_component,
]
)
if__name__=="__main__":
app.run_server(debug=True)

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

History
485 lines (355 loc) · 20.3 KB

File metadata and controls

485 lines (355 loc) · 20.3 KB

Status: Template Layout System

Based on community feedback, this version of the template layout system will not be added to a future version of Dash. However, the work done here inspired many new features, such as:

- New in Dash 2.1: The low-code shorthands for Dash Core Components and the dash DataTable.

- New in Dash 2.1, The Input, State, and Output accepts components instead of ID strings. Dash callback will auto-generate the component's ID under-the-hood if not supplied.

- Available in the dash-bootstrap-templates library: Bootstrap themed figures.

We appreciate everyone's input on the template system. Templates are still in the dash-labs project plan, so stay tuned for a new version!

- ----------------------------------------------------------------------------------- This documentation describes code in a previous version of dash-labs (v0.4.0) - and is included here for legacy purposes only.-- You can install v0.4.0 with:- pip install dash-labs==0.4.0- ----------------------------------------------------------------------------------

The template layout system

Dash Labs introduces a template system that makes it possible to quickly add components to a pre-defined template.

Manually add components to a template

As will be described below, the template system integrates with @app.callback, but templates can also be used independently of @app.callback.

Templates that are included with Dash Labs are located in the dl.templates package. The convention is to assign a template instance to a variable named tpl. Components can then be added to the template with tpl.add_component

Here is a simple example of manually adding components to a DbcCard template

demos/template_system1.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=4)
div=html.Div()
button=html.Button(children="Click Me")
@app.callback(dl.Output(div, "children"), dl.Input(button, "n_clicks"))defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
tpl.add_component(button, label="Button to click", location="bottom")
tpl.add_component(div, location="top")
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Component location

When a component is added to a template using add_component, it is associated with a template location using the location argument. Components that share the same location will be grouped together by the template in the component layout it produces. Templates document the locations that they support in the constructor docstring. Here is the docstring for the DbcCard template used above.

 Template that places all components in a single card
Supported template locations:
- "bottom": Bottom region of the card (default for Input components)
- "top": Top region of the card (default for Output components)
...

Component label

When a component is added to a template using add_component, it may optionally be associated with a label string using the label argument. When provided, the template will wrap the provided component with a label in the component layout it produces.

Template children

Templates provide a .children property that returns a container that includes the components that were added to the template. This container is a regular Dash component that can be assigned to app.layout, or combined with other components to build app.layout.

Note: When using a template based on Dash Bootstrap Components, it's recommended to use dbc.Container as the top-level layout component, and to assign the template's children as the children of the dbc.Container. See https://dash-bootstrap-components.opensource.faculty.ai/docs/components/layout/ for more information. Similarly, when using a template based on Dash Design Kit, it's recommended to use ddk.App as the top-level layout component, and to assign the template's children as the children of the ddk.App.

template and app.callback integration

For convenience, @app.callback accepts an optional template argument. When provided, @app.callback will automatically add the provided input and output components to the template. Because of the information that @app.callback already has access to, it can choose reasonable defaults for each component's location and label. Because the components will be added to the template, it becomes possible to construct components inline in the @app.callback definition, rather than constructing them above and assigning them to local variables. With these conveniences, the example above becomes:

demos/template_system2.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(dl.Output(html.Div(), "children"),dl.Input(html.Button(children="Click Me"), "n_clicks", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Customize labels and locations

When a template is populated using @app.callback, the label string and location for a component can be overridden using the label and location keyword arguments to dl.Input/dl.State/dl.Output. See the "Button to click" label added above.

Default output

When a template is provided, and no Output dependency is provided, the template will provide a default output container for the result of the function (typically an html.Div component).

demos/template_system3.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(dl.Input(html.Button(children="Click Me"), "n_clicks", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Template component builders

To reduce the amount of boilerplate required to construct the dependency components to pass to @app.callback, template classes provide a variety of helper functions. A few examples are tpl.new_div(), tpl.new_button(), tpl.new_dropdown(), etc. These are relatively simple class methods that return a dependency object wrapping a component. For example:

tpl.new_dropdown(["A", "B", "C"], label="My Dropdown")

evaluates to...

dl.Input(
dcc.Dropdown(
id={'uid': 'd4713d60-c8a7-0639-eb11-67b367a9c378'},
options=[
{'value': 'A', 'label': 'A'}, {'value': 'B', 'label': 'B'},
{'value': 'C', 'label': 'C'}
],
value='A',
clearable=False
),
"value",
label="My Dropdown"
)

All of these functions provide the following keyword arguments:

  • label: The label to display for the component.
  • location: The template location of the component.
  • component_property: The property (or property grouping) considered to be the value of the component. This value is optional, and the template will provide a reasonable default for each component type (e.g. n_clicks for dcc.Button, value for dcc.Dropdown, figure for dcc.Graph).
  • kind: The dependency class to return. One of dl.Input, dl.State, or dl.Output. This value is optional and templates will provide a reasonable defaults (e.g. dl.Input for dcc.Button and dcc.Dropdown, dl.Output for dcc.Graph, etc.)
  • id: Optional argument to override the generated component id
  • opts: Dictionary of keyword arguments to pass to the constructor of the component that is created.

In addition to these standard keyword arguments, component builders also provide args to make the configuration of the components as concise as possible. e.g. dl.dropdown_input(["A", "B", "C]), dl.slider_input(0, 10).

These component builders can significantly shorten many @app.callback specifications.

Here is an update to the previous example that uses the tpl.new_button component constructor instead of manually creating dl.Input and html.Button objects.

demos/template_system4.py

importdash_bootstrap_componentsasdbcimportdash_labsasdlimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(tpl.new_button("Click Me", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Component builder specialization

Another advantage of the component builder paradigm is that templates can specialize the representation of the different components. For example, Dash Bootstrap templates can use dbc.Select in place of dcc.Dropdown for tpl.new_dropdown(). Similarly, DDK templates can use ddk.Graph in place of dcc.Graph for tpl.new_graph().

Manually executed function using state

The ipywidgets @interact decorator supports a manual argument. When True, an update button is automatically added and changes to the other widgets are not applied until the update button is clicked. This workflow can be replicated with @app.callback by adding a button component and specifying that all inputs other than the button should be classified as State (rather than the default of Input).

Here is a full example of specifying all the components except the button as kind=dl.State to @app.callback.

demos/basic_decorator_manual.py

importdashimportdash_labsasdlimportnumpyasnpimportdash_core_componentsasdccimportplotly.expressaspximportdash_bootstrap_componentsasdbcapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcRow(app, title="Manual Update", theme=dbc.themes.SOLAR)
@app.callback(args=dict(fun=tpl.new_dropdown(["sin", "cos", "exp"], label="Function", kind=dl.State),figure_title=tpl.new_textbox("Initial Title", label="Figure Title", kind=dl.State ),phase=tpl.new_slider(1, 10, label="Phase", kind=dl.State),amplitude=tpl.new_slider(1, 10, value=3, label="Amplitude", kind=dl.State),n_clicks=tpl.new_button("Update", label=None), ),template=tpl,)defgreet(fun, figure_title, phase, amplitude, n_clicks):
print(fun, figure_title, phase, amplitude)
xs=np.linspace(-10, 10, 100)
returndcc.Graph(
figure=px.line(x=xs, y=getattr(np, fun)(xs+phase) *amplitude).update_layout(
title_text=figure_title
)
)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Custom output components

When a template is provided, the new @app.callback decorator no longer requires a caller to explicitly provide the output component that the callback function result will be stored in. However, explicit output components and output properties can still be configured.

Here is an example that outputs a string to the children property of a dcc.Markdown component.

Note that the default value of kind for tpl.new_markdown is dl.Output, which is why it's not necessary to override the kind argument.

demos/output_markdown.py

importdashimportdash_labsasdlimportdash_bootstrap_componentsasdbcapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, "App Title", sidebar_columns=6)
@app.callback(output=tpl.new_markdown(),args=tpl.new_textarea("## Heading\n", opts=dict(style={"width": "100%", "height": 400}) ),template=tpl,)defmarkdown_preview(input_text):
returninput_textapp.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Adding additional components to a template

Additional components can be added to a template after the initial components are added by @app.callback.

Note how the add_component method supports before and after keyword arguments that can be used to insert new components at specific locations between components added by app.callback.

demos/template_with_custom_additions.py

importdashimportdash_labsasdlimportnumpyasnpimportdash_core_componentsasdccimportdash_bootstrap_componentsasdbcimportplotly.expressaspxapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, title="Dash Labs App")
# import dash_core_components as dcc@app.callback(inputs=dict(fun=tpl.new_dropdown(["sin", "cos", "exp"], label="Function"),figure_title=tpl.new_textbox("Initial Title", label="Figure Title"),phase=tpl.new_slider(1, 10, value=3, label="Phase"),amplitude=tpl.new_slider(1, 10, value=4, label="Amplitude"), ),output=tpl.new_graph(),template=tpl,)deffunction_browser(fun, figure_title, phase, amplitude):
xs=np.linspace(-10, 10, 100)
returnpx.line(x=xs, y=getattr(np, fun)(xs+phase) *amplitude).update_layout(
title_text=figure_title
)
# Add extra component to templatetpl.add_component(
dcc.Markdown(children="# First Group"), location="sidebar", before="fun"
)
tpl.add_component(
dcc.Markdown(
children=[
"# Second Group\n""Specify the Phase and Amplitudue for the chosen function"
]
),
location="sidebar",
before="phase",
)
tpl.add_component(
dcc.Markdown(children=["# H2 Title\n", "Here is the *main* plot"]),
location="main",
before=0,
)
tpl.add_component(
dcc.Link("Made with Dash", href="https://dash.plotly.com/"),
component_property="children",
location="main",
)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Advanced: Accessing individual components to build custom layouts

This section describes how to retrieve components that have been added to, and created by, a template. It is intended mostly for information purposes, and is not intended to be a common workflow.

Template locations property

The components added to a template are stored in the .locations property.

This is a dictionary from template location to OrderedDicts of ArgumentComponents (described below).

ArgumentComponents

You might think that the values of the .location dictionaries described above would simply be collections of the components added to the template. The reason it's not quite that simple is that for a single component added to a template, the template may create multiple components: There is the original component, one for the label, and both of these may be wrapped in a container component. Because the caller may want access to any, or all, of these components individually, references to all of these components, and their associated props, are stored in a ArgumentComponents instance. Here are the attributes of ArgumentComponents, and an example of why a caller may want to access them.

  • .arg_component: This a reference to the innermost component that actually provides the callback function with an input value, which corresponds to the properties stored in .arg_property attribute. A caller would want to access this component in order to register additional callback functions to execute when the callback function is updated.
  • .label_component: This is the component that displays the label string for the component, where the label text is stored in the .label_property property of the component. A caller may want to access this component to customize the label styling, or access the current value of the label string.
  • .container_component: This is the outer-most component that contains all the other components described above, where the contained components are stored in the .container_property property of the container. This is generally the component that a caller would incorporate when building a custom layout.

This example uses @app.callback to add components to a template, constructs a fully custom layout, and defines custom callbacks on the components returned by @app.callback. This is loosely based on the Dash Bootstrap Components example at https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/.

Notice how custom callbacks are applied to the dropdowns returned by @app.callback to prevent specifying the same feature as both x and y values.

demos/custom_layout_and_callback_integration.py

# Based on https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/# Based on https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/importdash_labsasdlimportplotly.expressaspximportdash_core_componentsasdccimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcfromdash.dependenciesimportInput, Outputimportdash# Load datadf=px.data.iris()
feature_cols= [colforcolindf.columnsif"species"notincol]
feature_labels= [col.replace("_", " ").title() +" (cm)"forcolinfeature_cols]
feature_options= [
{"label": label, "value": col} forcol, labelinzip(feature_cols, feature_labels)
]
# Build app and templateapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, title="Iris Dataset")
# Use parameterize to create components@app.callback(args=dict(x=dl.Input(dcc.Dropdown(options=feature_options, value="sepal_length")),y=dl.Input(dcc.Dropdown(options=feature_options, value="sepal_width")), ),template=tpl,)defiris(x, y):
returndcc.Graph(
figure=px.scatter(df, x=x, y=y, color="species"),
)
# Get references to the dropdowns and register a custom callback to prevent the user# from setting x and y to the same variable# Get the dropdown components that were created by parameterizex_component=tpl.locations["sidebar"]["x"].arg_componenty_component=tpl.locations["sidebar"]["y"].arg_component# Define standalone function that computes what values to enable, reuse for both# dropdowns with app.callbackdeffilter_options(v):
"""Disable option ability to plot x vs x"""return [
{"label": label, "value": col, "disabled": col==v}
forcol, labelinzip(feature_cols, feature_labels)
]
app.callback(Output(x_component.id, "options"), [Input(y_component.id, "value")])(
filter_options
)
app.callback(Output(y_component.id, "options"), [Input(x_component.id, "value")])(
filter_options
)
x_container=tpl.locations["sidebar"]["x"].container_componenty_container=tpl.locations["sidebar"]["y"].container_componentoutput_component=tpl.locations["main"][0].container_componentapp.layout=html.Div(
[
html.H1("Iris Feature Explorer"),
html.H2("Select Features"),
x_container,
y_container,
html.Hr(),
html.H2("Feature Scatter Plot"),
output_component,
]
)
if__name__=="__main__":
app.run_server(debug=True)

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

History
485 lines (355 loc) · 20.3 KB

File metadata and controls

485 lines (355 loc) · 20.3 KB

Status: Template Layout System

Based on community feedback, this version of the template layout system will not be added to a future version of Dash. However, the work done here inspired many new features, such as:

- New in Dash 2.1: The low-code shorthands for Dash Core Components and the dash DataTable.

- New in Dash 2.1, The Input, State, and Output accepts components instead of ID strings. Dash callback will auto-generate the component's ID under-the-hood if not supplied.

- Available in the dash-bootstrap-templates library: Bootstrap themed figures.

We appreciate everyone's input on the template system. Templates are still in the dash-labs project plan, so stay tuned for a new version!

- ----------------------------------------------------------------------------------- This documentation describes code in a previous version of dash-labs (v0.4.0) - and is included here for legacy purposes only.-- You can install v0.4.0 with:- pip install dash-labs==0.4.0- ----------------------------------------------------------------------------------

The template layout system

Dash Labs introduces a template system that makes it possible to quickly add components to a pre-defined template.

Manually add components to a template

As will be described below, the template system integrates with @app.callback, but templates can also be used independently of @app.callback.

Templates that are included with Dash Labs are located in the dl.templates package. The convention is to assign a template instance to a variable named tpl. Components can then be added to the template with tpl.add_component

Here is a simple example of manually adding components to a DbcCard template

demos/template_system1.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=4)
div=html.Div()
button=html.Button(children="Click Me")
@app.callback(dl.Output(div, "children"), dl.Input(button, "n_clicks"))defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
tpl.add_component(button, label="Button to click", location="bottom")
tpl.add_component(div, location="top")
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Component location

When a component is added to a template using add_component, it is associated with a template location using the location argument. Components that share the same location will be grouped together by the template in the component layout it produces. Templates document the locations that they support in the constructor docstring. Here is the docstring for the DbcCard template used above.

 Template that places all components in a single card
Supported template locations:
- "bottom": Bottom region of the card (default for Input components)
- "top": Top region of the card (default for Output components)
...

Component label

When a component is added to a template using add_component, it may optionally be associated with a label string using the label argument. When provided, the template will wrap the provided component with a label in the component layout it produces.

Template children

Templates provide a .children property that returns a container that includes the components that were added to the template. This container is a regular Dash component that can be assigned to app.layout, or combined with other components to build app.layout.

Note: When using a template based on Dash Bootstrap Components, it's recommended to use dbc.Container as the top-level layout component, and to assign the template's children as the children of the dbc.Container. See https://dash-bootstrap-components.opensource.faculty.ai/docs/components/layout/ for more information. Similarly, when using a template based on Dash Design Kit, it's recommended to use ddk.App as the top-level layout component, and to assign the template's children as the children of the ddk.App.

template and app.callback integration

For convenience, @app.callback accepts an optional template argument. When provided, @app.callback will automatically add the provided input and output components to the template. Because of the information that @app.callback already has access to, it can choose reasonable defaults for each component's location and label. Because the components will be added to the template, it becomes possible to construct components inline in the @app.callback definition, rather than constructing them above and assigning them to local variables. With these conveniences, the example above becomes:

demos/template_system2.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(dl.Output(html.Div(), "children"),dl.Input(html.Button(children="Click Me"), "n_clicks", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Customize labels and locations

When a template is populated using @app.callback, the label string and location for a component can be overridden using the label and location keyword arguments to dl.Input/dl.State/dl.Output. See the "Button to click" label added above.

Default output

When a template is provided, and no Output dependency is provided, the template will provide a default output container for the result of the function (typically an html.Div component).

demos/template_system3.py

importdash_labsasdlimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(dl.Input(html.Button(children="Click Me"), "n_clicks", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Template component builders

To reduce the amount of boilerplate required to construct the dependency components to pass to @app.callback, template classes provide a variety of helper functions. A few examples are tpl.new_div(), tpl.new_button(), tpl.new_dropdown(), etc. These are relatively simple class methods that return a dependency object wrapping a component. For example:

tpl.new_dropdown(["A", "B", "C"], label="My Dropdown")

evaluates to...

dl.Input(
dcc.Dropdown(
id={'uid': 'd4713d60-c8a7-0639-eb11-67b367a9c378'},
options=[
{'value': 'A', 'label': 'A'}, {'value': 'B', 'label': 'B'},
{'value': 'C', 'label': 'C'}
],
value='A',
clearable=False
),
"value",
label="My Dropdown"
)

All of these functions provide the following keyword arguments:

  • label: The label to display for the component.
  • location: The template location of the component.
  • component_property: The property (or property grouping) considered to be the value of the component. This value is optional, and the template will provide a reasonable default for each component type (e.g. n_clicks for dcc.Button, value for dcc.Dropdown, figure for dcc.Graph).
  • kind: The dependency class to return. One of dl.Input, dl.State, or dl.Output. This value is optional and templates will provide a reasonable defaults (e.g. dl.Input for dcc.Button and dcc.Dropdown, dl.Output for dcc.Graph, etc.)
  • id: Optional argument to override the generated component id
  • opts: Dictionary of keyword arguments to pass to the constructor of the component that is created.

In addition to these standard keyword arguments, component builders also provide args to make the configuration of the components as concise as possible. e.g. dl.dropdown_input(["A", "B", "C]), dl.slider_input(0, 10).

These component builders can significantly shorten many @app.callback specifications.

Here is an update to the previous example that uses the tpl.new_button component constructor instead of manually creating dl.Input and html.Button objects.

demos/template_system4.py

importdash_bootstrap_componentsasdbcimportdash_labsasdlimportdashapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcCard(app, title="Simple App", columns=6)
@app.callback(tpl.new_button("Click Me", label="Button to click"),template=tpl,)defcallback(n_clicks):
return"Clicked {} times".format(n_clicks)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Component builder specialization

Another advantage of the component builder paradigm is that templates can specialize the representation of the different components. For example, Dash Bootstrap templates can use dbc.Select in place of dcc.Dropdown for tpl.new_dropdown(). Similarly, DDK templates can use ddk.Graph in place of dcc.Graph for tpl.new_graph().

Manually executed function using state

The ipywidgets @interact decorator supports a manual argument. When True, an update button is automatically added and changes to the other widgets are not applied until the update button is clicked. This workflow can be replicated with @app.callback by adding a button component and specifying that all inputs other than the button should be classified as State (rather than the default of Input).

Here is a full example of specifying all the components except the button as kind=dl.State to @app.callback.

demos/basic_decorator_manual.py

importdashimportdash_labsasdlimportnumpyasnpimportdash_core_componentsasdccimportplotly.expressaspximportdash_bootstrap_componentsasdbcapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcRow(app, title="Manual Update", theme=dbc.themes.SOLAR)
@app.callback(args=dict(fun=tpl.new_dropdown(["sin", "cos", "exp"], label="Function", kind=dl.State),figure_title=tpl.new_textbox("Initial Title", label="Figure Title", kind=dl.State ),phase=tpl.new_slider(1, 10, label="Phase", kind=dl.State),amplitude=tpl.new_slider(1, 10, value=3, label="Amplitude", kind=dl.State),n_clicks=tpl.new_button("Update", label=None), ),template=tpl,)defgreet(fun, figure_title, phase, amplitude, n_clicks):
print(fun, figure_title, phase, amplitude)
xs=np.linspace(-10, 10, 100)
returndcc.Graph(
figure=px.line(x=xs, y=getattr(np, fun)(xs+phase) *amplitude).update_layout(
title_text=figure_title
)
)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Custom output components

When a template is provided, the new @app.callback decorator no longer requires a caller to explicitly provide the output component that the callback function result will be stored in. However, explicit output components and output properties can still be configured.

Here is an example that outputs a string to the children property of a dcc.Markdown component.

Note that the default value of kind for tpl.new_markdown is dl.Output, which is why it's not necessary to override the kind argument.

demos/output_markdown.py

importdashimportdash_labsasdlimportdash_bootstrap_componentsasdbcapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, "App Title", sidebar_columns=6)
@app.callback(output=tpl.new_markdown(),args=tpl.new_textarea("## Heading\n", opts=dict(style={"width": "100%", "height": 400}) ),template=tpl,)defmarkdown_preview(input_text):
returninput_textapp.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Adding additional components to a template

Additional components can be added to a template after the initial components are added by @app.callback.

Note how the add_component method supports before and after keyword arguments that can be used to insert new components at specific locations between components added by app.callback.

demos/template_with_custom_additions.py

importdashimportdash_labsasdlimportnumpyasnpimportdash_core_componentsasdccimportdash_bootstrap_componentsasdbcimportplotly.expressaspxapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, title="Dash Labs App")
# import dash_core_components as dcc@app.callback(inputs=dict(fun=tpl.new_dropdown(["sin", "cos", "exp"], label="Function"),figure_title=tpl.new_textbox("Initial Title", label="Figure Title"),phase=tpl.new_slider(1, 10, value=3, label="Phase"),amplitude=tpl.new_slider(1, 10, value=4, label="Amplitude"), ),output=tpl.new_graph(),template=tpl,)deffunction_browser(fun, figure_title, phase, amplitude):
xs=np.linspace(-10, 10, 100)
returnpx.line(x=xs, y=getattr(np, fun)(xs+phase) *amplitude).update_layout(
title_text=figure_title
)
# Add extra component to templatetpl.add_component(
dcc.Markdown(children="# First Group"), location="sidebar", before="fun"
)
tpl.add_component(
dcc.Markdown(
children=[
"# Second Group\n""Specify the Phase and Amplitudue for the chosen function"
]
),
location="sidebar",
before="phase",
)
tpl.add_component(
dcc.Markdown(children=["# H2 Title\n", "Here is the *main* plot"]),
location="main",
before=0,
)
tpl.add_component(
dcc.Link("Made with Dash", href="https://dash.plotly.com/"),
component_property="children",
location="main",
)
app.layout=dbc.Container(fluid=True, children=tpl.children)
if__name__=="__main__":
app.run_server(debug=True)

Advanced: Accessing individual components to build custom layouts

This section describes how to retrieve components that have been added to, and created by, a template. It is intended mostly for information purposes, and is not intended to be a common workflow.

Template locations property

The components added to a template are stored in the .locations property.

This is a dictionary from template location to OrderedDicts of ArgumentComponents (described below).

ArgumentComponents

You might think that the values of the .location dictionaries described above would simply be collections of the components added to the template. The reason it's not quite that simple is that for a single component added to a template, the template may create multiple components: There is the original component, one for the label, and both of these may be wrapped in a container component. Because the caller may want access to any, or all, of these components individually, references to all of these components, and their associated props, are stored in a ArgumentComponents instance. Here are the attributes of ArgumentComponents, and an example of why a caller may want to access them.

  • .arg_component: This a reference to the innermost component that actually provides the callback function with an input value, which corresponds to the properties stored in .arg_property attribute. A caller would want to access this component in order to register additional callback functions to execute when the callback function is updated.
  • .label_component: This is the component that displays the label string for the component, where the label text is stored in the .label_property property of the component. A caller may want to access this component to customize the label styling, or access the current value of the label string.
  • .container_component: This is the outer-most component that contains all the other components described above, where the contained components are stored in the .container_property property of the container. This is generally the component that a caller would incorporate when building a custom layout.

This example uses @app.callback to add components to a template, constructs a fully custom layout, and defines custom callbacks on the components returned by @app.callback. This is loosely based on the Dash Bootstrap Components example at https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/.

Notice how custom callbacks are applied to the dropdowns returned by @app.callback to prevent specifying the same feature as both x and y values.

demos/custom_layout_and_callback_integration.py

# Based on https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/# Based on https://dash-bootstrap-components.opensource.faculty.ai/examples/iris/importdash_labsasdlimportplotly.expressaspximportdash_core_componentsasdccimportdash_html_componentsashtmlimportdash_bootstrap_componentsasdbcfromdash.dependenciesimportInput, Outputimportdash# Load datadf=px.data.iris()
feature_cols= [colforcolindf.columnsif"species"notincol]
feature_labels= [col.replace("_", " ").title() +" (cm)"forcolinfeature_cols]
feature_options= [
{"label": label, "value": col} forcol, labelinzip(feature_cols, feature_labels)
]
# Build app and templateapp=dash.Dash(__name__, plugins=[dl.plugins.FlexibleCallbacks()])
tpl=dl.templates.DbcSidebar(app, title="Iris Dataset")
# Use parameterize to create components@app.callback(args=dict(x=dl.Input(dcc.Dropdown(options=feature_options, value="sepal_length")),y=dl.Input(dcc.Dropdown(options=feature_options, value="sepal_width")), ),template=tpl,)defiris(x, y):
returndcc.Graph(
figure=px.scatter(df, x=x, y=y, color="species"),
)
# Get references to the dropdowns and register a custom callback to prevent the user# from setting x and y to the same variable# Get the dropdown components that were created by parameterizex_component=tpl.locations["sidebar"]["x"].arg_componenty_component=tpl.locations["sidebar"]["y"].arg_component# Define standalone function that computes what values to enable, reuse for both# dropdowns with app.callbackdeffilter_options(v):
"""Disable option ability to plot x vs x"""return [
{"label": label, "value": col, "disabled": col==v}
forcol, labelinzip(feature_cols, feature_labels)
]
app.callback(Output(x_component.id, "options"), [Input(y_component.id, "value")])(
filter_options
)
app.callback(Output(y_component.id, "options"), [Input(x_component.id, "value")])(
filter_options
)
x_container=tpl.locations["sidebar"]["x"].container_componenty_container=tpl.locations["sidebar"]["y"].container_componentoutput_component=tpl.locations["main"][0].container_componentapp.layout=html.Div(
[
html.H1("Iris Feature Explorer"),
html.H2("Select Features"),
x_container,
y_container,
html.Hr(),
html.H2("Feature Scatter Plot"),
output_component,
]
)
if__name__=="__main__":
app.run_server(debug=True)