Create a Dashboard with deephaven.ui
This guide shows you how to build a dashboard with deephaven.ui
, Deephaven’s Python library to create user interfaces. You’ll use a wide range of components supported by the library to familiarize you with what deephaven.ui
provides and dive deep into simulated data with live dataframes, visualizations, and interactivity seamlessly integrated into a dashboard.
- First, you’ll learn the basic
deephaven.ui
components. Basic components use panels, which are individual and adjustable windows within Deephaven. Tables, charts, and other components are rendered in panels. - After creating panels, you’ll create a dashboard. A dashboard is a collection of panels that open in a separate window within Deephaven.
- Then, you’ll create a custom panel component. Custom components enable rich interactivity within your panels and dashboards.
- Finally, you’ll embed your custom component into your dashboard.
To follow along, you need the
deephaven.ui
package and simulated data and charts from deephaven.plot.express
. Both of these packages are included in the default setup.
Import the simulated iris
data with this script:
from deephaven import ui
import deephaven.plot.express as dx
iris = dx.data.iris()
In this dataset, the Species
column is a categorical column with three values: setosa
, Versicolor
, and Virginia
. The SepalLength
, SepalWidth
, PetalLength
, and PetalWidth
columns are continuous numerical columns that contain measurements of the sepal and petal of an iris flower. The Timestamp
column is also useful for ordering the data.
You’ll mostly focus on SepalLength
and SepalWidth
in this guide.
Basic components
Components are the building blocks of deephaven.ui
. Each component takes parameters that control how the component appears. By default, a component renders in a panel.
ui.table
Wrapping a table in ui.table
unlocks visual functionality on the table.
Since you’re investigating SepalLength
and SepalWidth
, create a ui.table
that accentuates the latest filtered data.
With iris
, create a ui.table
that:
- Reverses the order so that the newest rows are shown first.
- Pulls the
Species
column to the front along withTimestamp
. - Hides the
PetalLength
,PetalWidth
, andSpeciesID
columns. - Uses the compact table density so you can see as many rows as possible.
ui_iris = ui.table(
iris,
reverse=True,
front_columns=["Timestamp", "Species"],
hidden_columns=["PetalLength", "PetalWidth", "SpeciesID"],
density="compact"
)
Charts
Charts from Deephaven Plotly Express (dx
) have no deephaven.ui
specific wrapping and are added directly. Create a dx.scatter
chart that compares SepalLength
and SepalWidth
by Species
.
scatter_by_species = dx.scatter(iris, x = "SepalLength", y = "SepalWidth", by="Species")
ui.text
The ui.text
component adds basic text. Create text to accompany the chart and table.
sepal_text = ui.text("SepalLength vs. SepalWidth By Species")
ui.flex
Wrap your chart and ui.table
in a ui.flex
component. ui.flex
is an implementation of Flexbox that enables responsive layouts that adjust as you resize panels.
Items within a ui.flex
component stretch and shrink based on available space.
sepal_flex = ui.flex(ui_iris, scatter_by_species)
The direction
of sepal_flex
is "row"
. Add sepal_text
and sepal_flex
to another panel, with a direction
of "column"
.
sepal_flex_column = ui.flex(sepal_text, sepal_flex, direction="column")
Tabs
The ui.tabs
component enables tabs within a panel. Create histograms of SepalLength
to display in tabs.
Histograms are useful to display comparisons of data distributions, so create dx.histogram
charts of the columns of interest, SepalLength
and SepalWidth
, by Species
.
Create ui.tab
elements for sepal_flex
, sepal_length_hist
, and sepal_width_hist
, then pass them to ui.tabs
to switch between different views.
sepal_length_hist = dx.histogram(iris, x="SepalLength", by="Species")
sepal_width_hist = dx.histogram(iris, x="SepalWidth", by="Species")
sepal_tabs = ui.tabs(
ui.tab(sepal_flex, title="Sepal Length vs. Sepal Width"),
ui.tab(sepal_length_hist, title="Sepal Length Histogram"),
ui.tab(sepal_width_hist, title="Sepal Width Histogram"),
)
sepal_flex_tabs = ui.flex(sepal_text, sepal_tabs, direction="column")
Markdown
ui.markdown
components allow you to provide text in a markdown format. Create an informational panel with markdown text.
about_markdown = ui.markdown(r"""
### Iris Dashboard
Explore **SepalLength** and **SepalWidth** from the Iris dataset with **deephaven.ui**
- The data powering this dashboard is simulated Iris data
- Charts are from Deephaven Plotly Express
- Other components are from **deephaven.ui**
""")
Now you have two responsive panels with fundamental components of deephaven.ui
that explore SepalLength
and SepalWidth
by Species
.
Dashboard
By default, components are rendered in a panel, but a dashboard enables more complex layouts in an isolated window.
When you have multiple panels, you can create a dashboard to contain them so that you can add even more panels later for your iris
data exploration.
Expand for complete code up to this point
from deephaven import ui
import deephaven.plot.express as dx
from deephaven import agg
iris = dx.data.iris()
ui_iris = ui.table(
iris,
reverse=True,
front_columns=["Timestamp", "Species"],
hidden_columns=["PetalLength", "PetalWidth", "SpeciesID"],
density="compact",
)
scatter_by_species = dx.scatter(iris, x="SepalLength", y="SepalWidth", by="Species")
sepal_text = ui.text("SepalLength vs. SepalWidth By Species Panel")
sepal_flex = ui.flex(ui_iris, scatter_by_species)
sepal_flex_column = ui.flex(sepal_text, sepal_flex, direction="column")
sepal_length_hist = dx.histogram(iris, x="SepalLength", by="Species")
sepal_width_hist = dx.histogram(iris, x="SepalWidth", by="Species")
sepal_tabs = ui.tabs(
ui.tab(sepal_flex, title="Sepal Length vs. Sepal Width"),
ui.tab(sepal_length_hist, title="Sepal Length Histogram"),
ui.tab(sepal_width_hist, title="Sepal Width Histogram"),
)
sepal_flex_tabs = ui.flex(sepal_text, sepal_tabs, direction="column")
about_markdown = ui.markdown(r"""
### Iris Dashboard
Explore the Iris dataset with **deephaven.ui**
- The data powering this dashboard is simulated Iris data
- Charts are from Deephaven Plotly Express
- Other components are from **deephaven.ui**
""")
Before that, create a ui.panel
manually to provide a title
.
sepal_panel = ui.panel(sepal_flex_tabs, title="Sepal Panel")
iris_dashboard = ui.dashboard(sepal_panel)
You now have a one-panel dashboard.
You can create a default layout with multiple panels. Create a panel for about_markdown
so you can give it a title
.
about_panel = ui.panel(about_markdown, title="About")
Row
One way to create a default layout is by wrapping your panels in a ui.row
component.
iris_dashboard_row = ui.dashboard(ui.row(about_panel, sepal_panel))
Column
Another way to create a default layout is with ui.column
.
iris_dashboard_column = ui.dashboard(ui.column(about_panel, sepal_panel))
Stack
One more way to create a default layout is with ui.stack
. The last panel is active by default.
ui.stack
is useful if you want a tabbed layout but also the ability to move the individual components around, which is not possible with ui.tabs
.
Create tabs that show the average, max, and min values of SepalLength
, SepalWidth
, PetalLength
, and PetalWidth
by Species
.
You can compare a specific statistic across the different Species
within the same panel or move between panels to compare different statistics across Species
.
from deephaven import agg
iris_avg = iris.agg_by([agg.avg(cols=["SepalLength", "SepalWidth", "PetalLength", "PetalWidth"])], by=["Species"])
iris_max = iris.agg_by([agg.max_(cols=["SepalLength", "SepalWidth", "PetalLength", "PetalWidth"])], by=["Species"])
iris_min = iris.agg_by([agg.min_(cols=["SepalLength", "SepalWidth", "PetalLength", "PetalWidth"])], by=["Species"])
ui_iris_avg = ui.panel(iris_avg, title="Average")
ui_iris_max = ui.panel(iris_max, title="Max")
ui_iris_min = ui.panel(iris_min, title="Min")
iris_agg_stack = ui.stack(ui_iris_avg, ui_iris_max, ui_iris_min)
iris_dashboard_stack = ui.dashboard(iris_agg_stack)
Interactivity
So far, you’ve worked with deephaven.ui
components that don’t interact with each other. Now, you’ll create your own component with interactivity and embed it into your dashboard.
Since this walkthrough investigates SepalLength
and SepalWidth
, this section shows you how to create a dx.densityheatmap
chart that shows the density of SepalLength
and SepalWidth
, with a Species
filter.
ui.component
A custom component uses the ui.component
decorator. The decorator signals to the deephaven.ui
rendering engine that this component needs to be rendered. Create a function with the ui.component
decorator that returns "Hello, World!"
.
@ui.component
def custom_component():
return "Hello, World!"
custom_panel = custom_component()
Picker
A ui.picker
allows you to select options from a list.
@ui.component
def species_panel():
species_picker = ui.picker("setosa", "versicolor", "virginica")
return species_picker
picker_panel = species_panel()
Table-backed ui.picker
ui.pickers
can pull directly from a table so they update automatically based on a column in the table. Modify your ui.picker
to pass in a table instead, which is recommended for dynamic data.
Note
It’s important to filter your table down to the distinct values you want. The ui.picker
does not do this for you.
species_table = iris.view("Species").select_distinct()
@ui.component
def species_panel():
species_picker = ui.picker(species_table)
return species_picker
species_picker_panel = species_panel()
ui.use_state
The ui.use_state
hook is how you’ll enable interactivity. The hook takes a starting value and returns a tuple of the current value and a function to set the value.
Next, modify your picker to take the species
and set_species
from the hook you just defined using on_change
and selected_key
. on_change
is called whenever an option is selected. selected_key
is the currently selected option. Using these makes the component controlled, meaning that the selected_key
is controlled outside of the ui.picker
itself. This allows you to use the selected_key
in other ways such as filtering a table.
Warning
Hooks like ui.use_state
are only available in components decorated with ui.component
.
Additionally, hooks should not be called conditionally within a component. Create a new component if conditional rendering is needed.
@ui.component
def species_panel():
species, set_species = ui.use_state()
species_picker = ui.picker(species_table, on_change=set_species, selected_key=species, label="Current Species")
return species_picker
species_picker_panel = species_panel()
Now, your picker is controlled by the rendering engine and updates as you pick values, and you have an up-to-date value from the table to filter on.
Utilizing State
Now, add a table filtered on this value and a chart that uses this filtered table. Return them with the picker.
You’ll create a dx.heatmap
, which shows the joint density of SepalLength
and SepalWidth
(the variables of interest) filtered by Species
.
@ui.component
def species_panel():
species, set_species = ui.use_state()
species_picker = ui.picker(
species_table,
on_change=set_species,
selected_key=species,
label="Current Species"
)
filtered_table = iris.where("Species = species")
heatmap = dx.density_heatmap(filtered_table, x="SepalLength", y="SepalWidth")
return ui.panel(ui.flex(species_picker, heatmap, direction="column"), title="Investigate Species")
species_picker_panel = species_panel()
ui.illustrated_message
Currently, an empty table and chart appear if no species is selected. For a more user-friendly experience, add a ui.illustrated_message
component to display instead if no species
is selected.
@ui.component
def species_panel():
species, set_species = ui.use_state()
species_picker = ui.picker(
species_table,
on_change=set_species,
selected_key=species,
label="Current Species"
)
heatmap = ui.illustrated_message(
ui.icon("vsFilter"),
ui.heading("Species required"),
ui.content("Select a species to display filtered table and chart."),
width="100%",
)
if species:
filtered_table = iris.where("Species = species")
heatmap = dx.density_heatmap(filtered_table, x="SepalLength", y="SepalWidth")
return ui.panel(ui.flex(species_picker, heatmap, direction="column"), title="Investigate Species")
species_picker_panel = species_panel()
Utilizing custom components
Next, embed your custom component in your dashboard.
iris_species_dashboard = ui.dashboard(
ui.column(
ui.row(about_panel, iris_agg_stack), ui.row(sepal_panel, species_picker_panel)
)
)
The top row contains lots of empty space. Resize the height of the top row to 1 and the bottom row to 2 for a ratio of 1:2, so the bottom row is twice the height of the top row.
iris_species_dashboard_resized = ui.dashboard(ui.column(ui.row(about_panel, iris_agg_stack, height=1), ui.row(sepal_panel, species_picker_panel, height=2)))
Dashboard state across panels
Currently, the ui.picker
selects Species
within a panel. You can change state across panels as well.
Recreate the sepal_flex_tabs
panel within the create_sepal_panel
function, which takes a set_species
function as an argument. Now, we’ll add a ui.table
event – these are useful if you see a row you want to investigate further.
- Add a listener (
on_row_double_press
) to the table that is called when a row is double-clicked, returning the row data. This sets theSpecies
value displayed in theui.picker
anddx.density_heatmap
. - Pull the
Species
value from the row data and set it withset_species
. - Then, create
sepal_panel
withcreate_sepal_panel
, passingset_species
to it.
def create_sepal_panel(set_species):
ui_iris = ui.table(
iris,
reverse=True,
front_columns=["Timestamp", "Species"],
hidden_columns=["PetalLength", "PetalWidth", "SpeciesID"],
density="compact",
on_row_double_press=lambda event: set_species(event["Species"]["value"]),
)
sepal_flex = ui.flex(ui_iris, scatter_by_species)
sepal_tabs = ui.tabs(
ui.tab(sepal_flex, title="Sepal Length vs. Sepal Width"),
ui.tab(sepal_length_hist, title="Sepal Length Histogram"),
ui.tab(sepal_width_hist, title="Sepal Width Histogram"),
)
sepal_flex_tabs = ui.flex(sepal_text, sepal_tabs, direction="column")
return ui.panel(sepal_flex_tabs, title="Sepal Panel")
@ui.component
def create_species_dashboard():
species, set_species = ui.use_state()
species_picker = ui.picker(
species_table,
on_change=set_species,
selected_key=species,
label="Current Species",
)
heatmap = ui.illustrated_message(
ui.icon("vsFilter"),
ui.heading("Species required"),
ui.content("Select a species to display filtered table and chart."),
width="100%",
)
if species:
filtered_table = iris.where("Species = species")
heatmap = dx.density_heatmap(filtered_table, x="SepalLength", y="SepalWidth")
species_panel = ui.panel(
ui.flex(species_picker, heatmap, direction="column"),
title="Investigate Species",
)
sepal_panel = create_sepal_panel(set_species)
return ui.column(
ui.row(about_panel, iris_agg_stack, height=1),
ui.row(sepal_panel, species_panel, height=2),
)
iris_species_dashboard_state = ui.dashboard(create_species_dashboard())
ui.use_row_data
and ui.badge
You’ve got a density heatmap that shows the density of SepalLength
and SepalWidth
by Species
, but it’s tricky to see the min, max, and average overall values for SepalLength
and SepalWidth
for the selected Species
. They’re in your table stack, but you can make them more prominent.
deephaven.ui
provides hooks to access table data. Each hook provides access to a specific part of the table and is updated when the table changes.
The hooks are:
ui.use_cell_data
- Accesses a single cell value.ui.use_row_data
- Accesses a row of data.ui.use_row_list
- Accesses a row as a list.ui.use_column_data
- Accesses a column of data.ui.use_table_data
- Accesses the entire table.
Warning
Filter your table to only the data you need the hook to pull out. The hooks do not filter the table for you.
Create a custom component that pulls the min, max, and average values for SepalLength
and SepalWidth
for the selected Species
.
To display the values, wrap them in ui.badge
components, which draw attention to specific values.
@ui.component
def summary_badges(species):
# Filter the tables to the selected species
species_min = iris_min.where("Species=species")
species_max = iris_max.where("Species=species")
species_avg = iris_avg.where("Species=species")
# Pull the desired columns from the tables before using the hooks
sepal_length_min = ui.use_cell_data(species_min.view(["SepalLength"]))
sepal_width_min = ui.use_cell_data(species_min.view(["SepalWidth"]))
sepal_length_max = ui.use_cell_data(species_max.view(["SepalLength"]))
sepal_width_max = ui.use_cell_data(species_max.view(["SepalWidth"]))
sepal_length_avg = ui.use_cell_data(species_avg.view(["SepalLength"]))
sepal_width_avg = ui.use_cell_data(species_avg.view(["SepalWidth"]))
# format the values to 3 decimal places
# set flex_grow to 0 to prevent the badges from growing
return ui.flex(
ui.badge(f"SepalLength Min: {sepal_length_min:.3f}", variant="info"),
ui.badge(f"SepalLength Max: {sepal_length_max:.3f}", variant="info"),
ui.badge(f"SepalLength Avg: {sepal_length_avg:.3f}", variant="info"),
ui.badge(f"SepalWidth Min: {sepal_width_min:.3f}", variant="info"),
ui.badge(f"SepalWidth Max: {sepal_width_max:.3f}", variant="info"),
ui.badge(f"SepalWidth Avg: {sepal_width_avg:.3f}", variant="info"),
flex_grow=0,
)
@ui.component
def create_species_dashboard():
species, set_species = ui.use_state()
species_picker = ui.picker(
species_table,
on_change=set_species,
selected_key=species,
label="Current Species",
)
heatmap = ui.illustrated_message(
ui.icon("vsFilter"),
ui.heading("Species required"),
ui.content("Select a species to display filtered table and chart."),
width="100%",
)
badges = None
if species:
filtered_table = iris.where("Species = species")
heatmap = dx.density_heatmap(filtered_table, x="SepalLength", y="SepalWidth")
badges = summary_badges(species)
species_panel = ui.panel(
ui.flex(species_picker, badges, heatmap, direction="column"),
title="Investigate Species",
)
sepal_panel = create_sepal_panel(set_species)
return ui.column(
ui.row(about_panel, iris_agg_stack, height=1),
ui.row(sepal_panel, species_panel, height=2),
)
iris_species_dashboard_badge = ui.dashboard(create_species_dashboard())
ui.use_memo
ui.use_memo
allows you to cache expensive calculations so they are only recalculated when needed. It takes a function and a list of dependencies. If the dependencies change, the function is recalculated.
Since you’ve added badges to the dashboard, the dx.heatmap
is recreated every time any of the badges change, but only needs to be recreated when the Species
changes.
Heatmap is a fairly expensive chart to create (it requires a filtered table in this case) and changes rarely, so pull the heatmap creation into a separate function and use ui.use_memo
to cache the heatmap creation.
def create_heatmap(species):
heatmap = ui.illustrated_message(
ui.icon("vsFilter"),
ui.heading("Species required"),
ui.content("Select a species to display filtered table and chart."),
width="100%",
)
if species:
filtered_table = iris.where("Species = species")
heatmap = dx.density_heatmap(filtered_table, x="SepalLength", y="SepalWidth")
return heatmap
@ui.component
def create_species_dashboard():
species, set_species = ui.use_state()
species_picker = ui.picker(
species_table,
on_change=set_species,
selected_key=species,
label="Current Species",
)
heatmap = ui.use_memo(lambda: create_heatmap(species), [species])
badges = summary_badges(species) if species else None
species_panel = ui.panel(
ui.flex(species_picker, badges, heatmap, direction="column"),
title="Investigate Species",
)
sepal_panel = create_sepal_panel(set_species)
return ui.column(
ui.row(about_panel, iris_agg_stack, height=1),
ui.row(sepal_panel, species_panel, height=2),
)
iris_species_dashboard_final = ui.dashboard(create_species_dashboard())
Expand for final code
from deephaven import ui
import deephaven.plot.express as dx
from deephaven import agg
iris = dx.data.iris()
ui_iris = ui.table(
iris,
reverse=True,
front_columns=["Timestamp", "Species"],
hidden_columns=["PetalLength", "PetalWidth", "SpeciesID"],
density="compact",
)
scatter_by_species = dx.scatter(iris, x="SepalLength", y="SepalWidth", by="Species")
sepal_text = ui.text("SepalLength vs. SepalWidth By Species Panel")
sepal_flex = ui.flex(ui_iris, scatter_by_species)
sepal_flex_column = ui.flex(sepal_text, sepal_flex, direction="column")
sepal_length_hist = dx.histogram(iris, x="SepalLength", by="Species")
sepal_width_hist = dx.histogram(iris, x="SepalWidth", by="Species")
sepal_tabs = ui.tabs(
ui.tab(sepal_flex, title="Sepal Length vs. Sepal Width"),
ui.tab(sepal_length_hist, title="Sepal Length Histogram"),
ui.tab(sepal_width_hist, title="Sepal Width Histogram"),
)
sepal_flex_tabs = ui.flex(sepal_text, sepal_tabs, direction="column")
about_markdown = ui.markdown(r"""
### Iris Dashboard
Explore the Iris dataset with **deephaven.ui**
- The data powering this dashboard is simulated Iris data
- Charts are from Deephaven Plotly Express
- Other components are from **deephaven.ui**
""")
sepal_panel = ui.panel(sepal_flex_tabs, title="Sepal Panel")
about_panel = ui.panel(about_markdown, title="About")
iris_avg = iris.agg_by([agg.avg(cols=["SepalLength", "SepalWidth", "PetalLength", "PetalWidth"])], by=["Species"])
iris_max = iris.agg_by([agg.max_(cols=["SepalLength", "SepalWidth", "PetalLength", "PetalWidth"])], by=["Species"])
iris_min = iris.agg_by([agg.min_(cols=["SepalLength", "SepalWidth", "PetalLength", "PetalWidth"])], by=["Species"])
ui_iris_avg = ui.panel(iris_avg, title="Average")
ui_iris_max = ui.panel(iris_max, title="Max")
ui_iris_min = ui.panel(iris_min, title="Min")
iris_agg_stack = ui.stack(ui_iris_avg, ui_iris_max, ui_iris_min)
species_table = iris.view("Species").select_distinct()
def create_sepal_panel(set_species):
ui_iris = ui.table(
iris,
reverse=True,
front_columns=["Timestamp", "Species"],
hidden_columns=["PetalLength", "PetalWidth", "SpeciesID"],
density="compact",
on_row_double_press=lambda event: set_species(event["Species"]["value"])
)
sepal_flex = ui.flex(ui_iris, scatter_by_species)
sepal_tabs = ui.tabs(
ui.tab(sepal_flex, title="Sepal Length vs. Sepal Width"),
ui.tab(sepal_length_hist, title="Sepal Length Histogram"),
ui.tab(sepal_width_hist, title="Sepal Width Histogram"),
)
sepal_flex_tabs = ui.flex(sepal_text, sepal_tabs, direction="column")
return ui.panel(sepal_flex_tabs, title="Sepal Panel")
@ui.component
def summary_badges(species):
# Filter the tables to the selected species
species_min = iris_min.where("Species=species")
species_max = iris_max.where("Species=species")
species_avg = iris_avg.where("Species=species")
# Pull the desired columns from the tables before using the hooks
sepal_length_min = ui.use_cell_data(species_min.view(["SepalLength"]))
sepal_width_min = ui.use_cell_data(species_min.view(["SepalWidth"]))
sepal_length_max = ui.use_cell_data(species_max.view(["SepalLength"]))
sepal_width_max = ui.use_cell_data(species_max.view(["SepalWidth"]))
sepal_length_avg = ui.use_cell_data(species_avg.view(["SepalLength"]))
sepal_width_avg = ui.use_cell_data(species_avg.view(["SepalWidth"]))
# format the values to 3 decimal places
# set flex_grow to 0 to prevent the badges from growing
return ui.flex(
ui.badge(f"SepalLength Min: {sepal_length_min:.3f}", variant="info"),
ui.badge(f"SepalLength Max: {sepal_length_max:.3f}", variant="info"),
ui.badge(f"SepalLength Avg: {sepal_length_avg:.3f}", variant="info"),
ui.badge(f"SepalWidth Min: {sepal_width_min:.3f}", variant="info"),
ui.badge(f"SepalWidth Max: {sepal_width_max:.3f}", variant="info"),
ui.badge(f"SepalWidth Avg: {sepal_width_avg:.3f}", variant="info"),
flex_grow=0
)
def create_heatmap(species):
heatmap = ui.illustrated_message(
ui.icon("vsFilter"),
ui.heading("Species required"),
ui.content("Select a species to display filtered table and chart."),
width="100%",
)
if species:
filtered_table = iris.where("Species = species")
heatmap = dx.density_heatmap(filtered_table, x="SepalLength", y="SepalWidth")
return heatmap
@ui.component
def create_species_dashboard():
species, set_species = ui.use_state()
species_picker = ui.picker(
species_table,
on_change=set_species,
selected_key=species,
label="Current Species",
)
heatmap = ui.use_memo(lambda: create_heatmap(species), [species])
badges = summary_badges(species) if species else None
species_panel = ui.panel(
ui.flex(species_picker, badges, heatmap, direction="column"),
title="Investigate Species",
)
sepal_panel = create_sepal_panel(set_species)
return ui.column(
ui.row(about_panel, iris_agg_stack, height=1),
ui.row(sepal_panel, species_panel, height=2),
)
iris_species_dashboard_final = ui.dashboard(create_species_dashboard())
You’ve now completed this dashboard crash course with your custom component and interactivity.
Wrapping up
This wraps up the deephaven.ui
dashboard crash course. In this course, you learned about the following components and concepts and created a dashboard with many of them: