Introduction to Dash ====================== *Dash* --------------- `Dash `_ is a low-code framework for building applications in Python. A minimal example of a *Dash* application include a *layout* featuring various organizational, data storage, and interactive components as well as *callbacks* which codify responses to user interaction with specific components. Basic inputs into a *Dash* *callback* include one or more *Input* and *Output* component properties and can also include one or more *State* component properties. An example simple *Dash* application is provided below (curtesy of `the Dash website `_): .. code-block:: python # Package imports: from dash import Dash, html, dcc, callback, Output, Input import plotly.express as px import pandas as pandas # Reading in data for application df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder_unfiltered.csv') # Initializing Dash application app = Dash() # Defining layout for the application app.layout = [ html.H1( children = "Title of Dash App", style = {'textAlign': 'center'} ), dcc.Dropdown( options = df.country.unique(), value = 'Canada', id = 'dropdown-selection' ), dcc.Graph( id = 'graph-content' ) ] # Defining callback (dropdown selection->update figure displayed in graph) @callback( Output('graph-content','figure'), Input('dropdown-selection','value') ) def update_graph(value): dff = df[df.country==value] return px.line(dff, x = 'year', y = 'pop') if __name__=='__main__': # Starting the default application server app.run(debug=True) *fusion-tools* components are written as `DashBlueprint `_ objects. Similar to `Flask Blueprints `_, *DashBlueprint*s enable piecewise construction of full applications by embedding functional components into a single unified layout. An example *Tool* is shown below with the same functionality as the minimal *Dash* application above: .. code-block:: python from dash import dcc from dash_extensions.enrich import DashBlueprint, html, Input, Output, State, PrefixIdTransform, MultiplexerTransform from fusion_tools.components import Tool import pandas as pd import plotly.express as px class BasicTool(Tool): def __init__(self): super().__init__() self.df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder_unfiltered.csv') def __str__(self): return 'Basic Tool' def load(self, component_prefix:int): self.component_prefix = component_prefix self.title = 'Basic Tool' self.blueprint = DashBlueprint( transforms = [ PrefixIdTransform(prefix = f'{component_prefix}'), MultiplexerTransform() ] ) self.get_callbacks() def gen_layout(self, session_data:dict): layout = html.Div([ html.H1( children = "Title of Dash App", style = {'textAlign': 'center'} ), dcc.Dropdown( options = self.df.country.unique(), value = 'Canada', id = 'dropdown-selection' ), dcc.Graph( id = 'graph-content' ) ]) return layout def get_callbacks(self): self.blueprint.callback( [ Input('dropdown-selection','value') ], [ Output('graph-content') ] )(self.update_graph) def update_graph(self, new_country): country_data = self.df[self.df.country==new_country] new_plot = px.line(country_data, x = 'year', y = 'pop') This can then be added into a *fusion-tools* *Visualization* as below: .. code-block:: python from fusion_tools import Visualization from basictool import BasicTool new_vis = Visualization( components = [ BasicTool() ] ) new_vis.start() *Pattern-matching callbacks* `Pattern-matching callbacks ` is a method in *Dash* to apply callbacks to either multiple components of the same "type" but different "index" or to associate callbacks with other components with the same "index". For example, all components that you want to associate with a callback are given an "id" property. The "id" can consist of either a *str* (e.g.: "component-1") or a dictionary with keys: "type" and "index" as below: .. code-block:: python example_div = html.Div( id = 'example-str-component', children = [ "This component is defined just with a string id" ] ) example_div2 = html.Div( id = {'type': 'example-pattern-matching-component', 'index': 0}, children = [ "This is a component of the type: 'example-pattern-matching-component' with the index 0" ] ) example_div3 = html.Div( id = {'type': 'example-pattern-matching-component', 'index': 1}, children = [ "This is a component of the type: 'example-pattern-matching-component' with the index 0" ] ) This lets you write one callback that can impact different components as below: .. code-block:: python @callback( [ Output({'type': 'example-pattern-matching-component','index': ALL}, 'children') ], [ Input({'type': 'some-random-button','index': ALL},'n_clicks') ] ) def update_multiple_components(clicks): # This callback responds to a button click and returns the same string # to each output div. output_str = 'This component has been updated!' # dash.ctx.outputs_list can tell you ahead of time how many pattern-matching components # should be updated with each output. return [output_str] * len(ctx.outputs_list[0]) @callback( [ Output({'type': 'example-pattern-matching-component','index': MATCH}, 'children') ], [ Input({'type': 'some-random-dropdown','index': MATCH},'value') ] ) def update_matching_component(dropdown_value): # In this scenario, a different dropdown menu is present for each "index" (or at least the ones # that are currently in the layout). Selecting a value from each dropdown impacts only # the Div with the same index. # NOTE: If the same components are used as "Output"s in multiple callbacks, you need to use # the MultiPlexerTransform() (https://www.dash-extensions.com/transforms/multiplexer_transform) return f'Dropdown of index: {ctx.triggered_id["index"]} has value: {dropdown_value}' Notice how the outputs for *ALL* pattern-matching callbacks are "lists" while outputs for *Match* pattern-matching callbacks can be a single output. Even if there is only output id that matches the *Output* any pattern-matching callbacks that contain *ALL* have to be a sequence. Another use-case for pattern-matching callbacks is that it allows you to define callbacks for components which are not in the current app layout. This is useful for *DashBlueprint* objects as the blueprint layout may not be active at all times. NOTE: This is a somewhat irregular application of multi-page applications in Dash. For more information on multi-page applications see `this link `.