deephaven.ui

A Python web framework for building real-time data-focused apps

deephaven.ui is a plugin for Deephaven that combines a reactive UI framework and a library of pre-built real-time data-focused components for creating data apps. Write scripts entirely in Python – no front-end engineering Javascript or CSS required. It uses a React-like approach to building components and rendering them in the UI, allowing for the creation of reactive components that can be re-used and composed together, as well as reacting to user input from the UI.

Key Features

  • Components: Create user interfaces from components defined entirely with Python.
  • Live dataframe aware: Components are live dataframe aware and can use Deephaven tables as a data source.
  • Reactive: UI components automatically update when the underlying Python data changes.
  • Declarative: Describe the UI as a function of the data and let the framework handle the rest.
  • Composable: Combine and re-use components to build complex interfaces.
  • Wide range of components: From simple text fields to complex tables and plots, the library has a wide range of components to build your app.

Getting Started

You can run the example Docker container with either of the following commands, depending on your Deephaven version:

You’ll need to find the link to open the UI in the Docker logs: docker

Using components

Get started by importing the deephaven.ui package as ui:

The ui package contains many components, which you can display in the UI:

Basic Hello World example.

By assigning the component to the hello_world variable, it displays in the UI in a panel named hello_world.

Handling events

Write functions to handle events. To write a button that will print event details to the console when clicked:

Whenever the button is pressed, event details are printed to the console.

Creating components

Use the @ui.component decorator to create your own custom components. This decorator wraps the function provided as a Deephaven component. For more details on the architecture, see Architecture documentation.

We can display a heading above a button as our custom component:

Custom component being displayed.

Using state

Often, you’ll want to react to the button presses and update the display. For example, to count the number of times a button has been pressed, use ui.use_state to introduce a state variable in your custom component:

Returned from ui.use_state is a tuple with two values: the current state (count), and a function to update that state (set_count).

The first time the button is displayed, the count will be 0 because that is the initial value passed into ui.use_state. Call set_count to update the state:

When state is updated, deephaven.ui will call your component again to re-render with the new value. After clicking once, it will re-render with count set to 1, then set to 2, and so on.

Each individual component has its own state:

Each counter has its own state.

Note

Functions prefixed with use_ are called hooks. use_state is built-in to deephaven.ui, and there are other hooks built-in shown below. You can also create your own hooks. Hooks are special functions. They must only be used at the top of a @ui.component or another hook. If you want to use one in a conditional or a loop, extract that logic to a new component and put it there.

Sharing state

In the previous example, the two buttons incremented their counter independently. State was stored within the counter components:

What if we wanted to have two buttons share the same count? To do this, move the state count upward to a parent component:

In the example below, we create a parent component ui_shared_state that contains the state, and then passes the state down into two ui_controlled_counter components. Now the buttons will always be in sync:

Buttons will always be in sync with shared state.

Examples

Below are some examples building custom components using deephaven.ui.

Text field (string)

You can create a TextField that takes input from the user. You can also use a Flex component to display multiple components in a row (or column, depending on the direction argument).

Text field.

Checkbox (boolean)

You can use a checkbox to get a boolean value from the user.

Checkbox

ActionGroup (string values)

An ActionGroup is a grouping of ActionButtons that are related to one another.

ActionMenu (string values)

ActionMenu combines an ActionButton with a Menu for simple “more actions” use cases.

ButtonGroup

ButtonGroup handles overflow for a grouping of buttons whose actions are related to each other.

RadioGroup

Radio buttons allow users to select a single option from a list of mutually exclusive options. All possible options are exposed up front for users to compare.

Picker (string values)

The ui.picker component can be used to select from a list of items. Here’s a basic example for selecting from a list of string values and displaying the selected key in a text field.

Use a picker to select from a list of items

Picker (table)

A picker can also take a Table. It will use the first column as the key and label by default.

Use a picker to select from a table

Picker (item table source)

A picker can also take an item_table_source. It will use the columns specified.

Use a picker to select from a table source

ComboBox (string values)

The ui.combo_box component can be used to select from a list of items. It also provides a search field to filter available results. Note that the search behavior differs slightly for different data types.

  • Numeric types - only support exact match
  • Text-based data types - support partial search matching
  • Date types support searching by different date parts (e.g. 2024, 2024-01, 2024-01-02, 2024-01-02 00, 2024-07-06 00:43, 2024-07-06 00:43:14, 2024-07-06 00:43:14.247)

Here’s a basic example for selecting from a list of string values and displaying the selected key in a text field.

ComboBox (item table source)

A combo_box can also take an item_table_source. It will use the columns specified.

ListView (string values)

A list view that can be used to create a list of selectable items. Here’s a basic example for selecting from a list of string values and displaying the selected key in a text field.

ListView (table)

A ListView can also take a Table. By default, it uses the first column as the key and label.

Use a list view to select from a table

ListView (item table source)

A list view can also take an item_table_source. It will use the columns specified.

Use a list view to select from a table source

ListView (list action group)

A list view can take a list_action_group as its actions prop.

ListView (list action menu)

A list view can take a list_action_menu as its actions prop.

Form (two variables)

You can have state with multiple different variables in one component. This example creates a text field and a slider, and we display the values of both of them.

Form with multiple inputs.

Form with submit

You can also create a form on which the user can click Submit and react to that on a specified callback. In this example, we create a Form that takes a name and age, and when the user clicks Submit, the values entered in the form are sent to the user on the form’s on_submit callback.

Submitting a form and printing out the data.

Button events

Included with events are many details about the action itself, e.g. modifier keys held down, or the name of the target element. In this example, we create a custom component that prints all press, key, and focus events to the console, and add two of them to a panel to show interaction with both of them (e.g. when focus switches from one button to another):

Print the details of all events when pressing a button.

Data Examples

Many of the examples below use the stocks table provided by deephaven.plot.express package:

Table with input filter

User input can filter a table using the where method. In this example, a text field takes input from the user, and we filter the table based on the input. Simply returning the table t from the component will display it in the UI (as if we had set it to a variable name).

Table with a text field for filtering.

Table with range filter

You can also filter a table based on a range. In this example, a range slider takes input from the user, and we filter the table by price based on the input. Simply returning the table t from the component will display it in the UI (as if we had set it to a variable name).

Table with a slider for selecting the range.

Table with required filters

In the previous example, we took a user’s input. Nothing was displayed if the user didn’t enter any text. Instead, we can display a different message prompting the user for input. We use a few new components in this example:

  • IllustratedMessage (ui.illustrated_message): A component that displays an icon, heading, and content. In this case, we display a warning icon, a heading, and some content.
  • Icon (ui.icon): A component that displays an icon. In this case, we display the warning icon, and set the font size to 48px so it appears large in the UI.
  • Flex (ui.flex): A component that displays its children in a row. In this case, we display the input text fields beside eachother in a row.

Stock Widget Table Invalid Input

Stock Widget Table Valid Input

Plot with filters

You can also do plots as you would expect.

Stock Widget Plot

Dashboard Examples

In addition to creating components, you can also create dashboards that display many components laid out how you prefer.

Dashboard Layout Elements

The dashboard layout elements available are:

  • ui.dashboard: Create a dashboard to contain other layout elements.
  • ui.column: Create a column of elements stacked vertically.
  • ui.row: Create a row of elements laid out horizontally.
  • ui.stack: Create a stack of panels on top of each other. You can use the panel tab to switch between panels in the stack. Only one panel in a stack is visible at a time.
  • ui.panel: Create a panel to wrap an element. Panels can be moved around a dashboard manually by dragging the panel tab.

ui.dashboard

A dashboard should only contain 1 root layout element. If the component for the dashboard returns an array of elements, they will be wrapped in a single root element. If there are multiple child elements of a dashboard, they will be wrapped as follows:

  • If there are any rows, they will be wrapped in a column.
  • If there are no rows and any columns, they will be wrapped in a row.
  • If there are no rows or columns, they will be wrapped in a column.

ui.row and ui.column

Rows and columns typically contain other rows and columns or stacks. If a row or column contains no other rows or columns, each element will be wrapped in a stack if needed. For example, if you create a row with two panels, those panels will be laid out side by side in their own stacks.

ui.stack

Stacks are used to create a stack of panels on top of each other. Any elements in a stack will be wrapped in a panel if needed. It is recommended to provide the panels with a title because the automatically wrapped panels will receive a title of “Untitled”.

Basic Dashboard

Putting that all together, we can create a dashboard with two tables across the top and one plot across the bottom:

Stock Dashboard

Custom Components Dashboard

We can also create our own components and add them to a dashboard. In this example, we create one panel that will be used as the control input for selecting the phase, frequency, and amplitude of a wave. We then display multiple plots to show the different types of waves:

Multiwave Dashboard

Other Examples

Memoization

We can use the use_memo hook to memoize a value. This is useful if you have a value that is expensive to compute and you only want to compute it when the inputs change. In this example, we create a time table with a new column, y_sin, which is a sine wave. We use use_memo to memoize the time table, so that it is only re-computed when the inputs to the use_memo function change (in this case, the function is a lambda that takes no arguments, so it will only re-compute when the dependencies change, which is never). We then use the update method to update the table with the new column, based on the values inputted on the sliders.

Waves

Custom hook

We can write custom hooks that can be re-used. In this example, we create a custom hook that creates an input panel that controls the amplitude, frequency, and phase for a wave. We then use this custom hook in our waves component.

Wave Input

We can then re-use that hook to make a component that displays a plot as well:

Waves with plot

Using Panels

When you return an array of elements, they automatically get created as individual panels. You can use the ui.panel component to name the panel.

Re-using components

In a previous example, we created a text_filter_table component. We can re-use that component, and display two tables with an input filter side-by-side:

Double Table

Stock rollup

You can use the rollup method to create a rollup table. In this example, we create a rollup table that shows the average price of each stock and/or exchange. You can toggle the rollup by clicking on the ToggleButton. You can also highlight a specific stock by entering the symbol in the text field, but only when a rollup option isn’t selected. We wrap the highlight input field with a ui.fragment that is conditionally used so that it doesn’t appear when the rollup is selected. We also use the ui.contextual_help component to display a help message when you hover over the help icon.

Stock Rollup

Tabs using ui.tab

You can add Tabs within a panel by using the ui.tabs method. In this example, we create a panel with two tabs by passing in two instances of ui.tab as children.

When specifying tabs with this format, the following must be noted:

  • The title prop has to be unique, if not, the key prop has to be unique
  • The text_value prop is an optional accessibility improvement prop

Tabs using ui.tab_panels and ui.tab_list

You can add Tabs within a panel by using the ui.tabs method. In this example, we create a tabbed panel with multiple tabs by passing in the ui.tab_panels and ui.tab_list as children:

tab_list is the container of tab headers. It expects children of ui.item that have a unique key. text_value may be added for accessibility, but by default the key will be used for accessibility.

tab_panels is the content of the tabs. It expects children of ui.item which have a matching key with an item in the tab_list.

Multi-threading

State updates must be called from the render thread. All callbacks are automatically called from the render thread, but sometimes you will need to do some long-running operations asynchronously. You can use the use_render_queue hook to run a callback on the render thread. In this example, we create a form that takes a URL as input, and loads the CSV file from another thread before updating the state on the current thread.