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:

# For Deephaven < 0.37.0
docker run --rm --name deephaven-ui -p 10000:10000 --pull=always ghcr.io/deephaven/server-ui:latest

# For Deephaven >= 0.37.0
docker run --rm --name deephaven-ui -p 10000:10000 --pull=always ghcr.io/deephaven/server:latest

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:

from deephaven import ui

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

hello_world = ui.heading("Hello World!")

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:

my_button = ui.button("Click Me!", on_press=lambda e: print(f"Button was clicked! {e}"))

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:

@ui.component
def ui_foo_bar():
    return [
        ui.heading("Click button below"),
        ui.button("Click Me!", on_press=lambda: print("Button was clicked!")),
    ]


foo_bar = ui_foo_bar()

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:

@ui.component
def ui_counter():
    count, set_count = ui.use_state(0)
    # ...

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:

@ui.component
def ui_counter():
    count, set_count = ui.use_state(0)
    return ui.button(f"Pressed {count} times", on_press=lambda: set_count(count + 1))

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:

@ui.component
def ui_counter():
    count, set_count = ui.use_state(0)
    return ui.button(f"Pressed {count} times", on_press=lambda: set_count(count + 1))


c1 = ui_counter()
c2 = ui_counter()

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:

@ui.component
def ui_controlled_counter(count, operation, on_press):
    return ui.button(f"Value is {count}, press to {operation}", on_press=on_press)


@ui.component
def ui_shared_state():
    count, set_count = ui.use_state(0)

    def handle_press():
        set_count(count + 1)

    return [
        ui.heading(f"Value is {count}"),
        ui_controlled_counter(count, "increment", lambda: set_count(count + 1)),
        ui_controlled_counter(count, "decrement", lambda: set_count(count - 1)),
    ]


shared_state = ui_shared_state()

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).

@ui.component
def ui_input():
    text, set_text = ui.use_state("hello")

    return [ui.text_field(value=text, on_change=set_text), ui.text(f"You typed {text}")]


my_input = ui_input()

Text field.

Checkbox (boolean)

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

@ui.component
def ui_checkbox():
    liked, set_liked = ui.use_state(True)
    return ui.flex(
        ui.checkbox("I liked this", is_selected=liked, on_change=set_liked),
        ui.text("You liked this" if liked else "You didn't like this"),
        direction="column",
    )


my_checkbox = ui_checkbox()

Checkbox

ActionGroup (string values)

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

@ui.component
def ui_action_group():
    [action, on_action] = ui.use_state()

    return ui.flex(
        ui.action_group(
            "Aaa",
            "Bbb",
            "Ccc",
            on_action=on_action,
        ),
        ui.text(action),
        direction="column",
    )


my_action_group = ui_action_group()

ActionMenu (string values)

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

@ui.component
def ui_action_menu():
    [action, on_action] = ui.use_state()

    return ui.flex(
        ui.action_menu(
            "Aaa",
            "Bbb",
            "Ccc",
            on_action=on_action,
        ),
        ui.text(action),
        direction="column",
    )


my_action_menu = ui_action_menu()

ButtonGroup

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

@ui.component
def ui_button_group():
    return ui.button_group(ui.button("One"), ui.button("Two"))


my_button_group = ui_button_group()

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.

@ui.component
def ui_radio_group():
    return ui.radio_group(
        ui.radio("One", value="one"),
        ui.radio("Two", value="two"),
        label="Radio Group",
    )


my_radio_group = ui_radio_group()

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.

@ui.component
def ui_picker():
    value, set_value = ui.use_state("")

    # Picker for selecting values
    pick = ui.picker(
        "Text 1",
        "Text 2",
        "Text 3",
        label="Text",
        on_selection_change=set_value,
        selected_key=value,
    )

    # Show current selection in a ui.text component
    text = ui.text("Selection: " + value)

    # Display picker and output in a flex column
    return ui.flex(
        pick,
        text,
        direction="column",
        margin=10,
        gap=10,
    )


my_picker = ui_picker()

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.

import deephaven.ui as ui
from deephaven import time_table
import datetime

# Ticking table with initial row count of 200 that adds a row every second
initial_row_count = 200
column_types = time_table(
    "PT1S",
    start_time=datetime.datetime.now() - datetime.timedelta(seconds=initial_row_count),
).view(
    [
        "Id=new Integer(i)",
        "Display=new String(`Display `+i)",
    ]
)


@ui.component
def ui_picker_table():
    value, set_value = ui.use_state("")

    pick_table = ui.picker(
        column_types,
        label="Text",
        on_change=set_value,
        selected_key=value,
    )

    text = ui.text(f"Selection: {value}")

    return ui.flex(pick_table, text, direction="column", margin=10, gap=10)


my_picker_table = ui_picker_table()

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.

import deephaven.ui as ui
from deephaven import time_table
import datetime

# Ticking table with initial row count of 200 that adds a row every second
initial_row_count = 200
column_types = time_table(
    "PT1S",
    start_time=datetime.datetime.now() - datetime.timedelta(seconds=initial_row_count),
).view(
    [
        "Id=new Integer(i)",
        "Display=new String(`Display `+i)",
    ]
)


@ui.component
def ui_picker_table_source():
    value, set_value = ui.use_state("")

    pick_table = ui.picker(
        ui.item_table_source(column_types, key_column="Id", label_column="Display"),
        label="Text",
        on_change=set_value,
        selected_key=value,
    )

    text = ui.text(f"Selection: {value}")

    return ui.flex(pick_table, text, direction="column", margin=10, gap=10)


my_picker_table_source = ui_picker_table_source()

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.

from deephaven import ui


@ui.component
def ui_combo_box():
    value, set_value = ui.use_state("")

    combo = ui.combo_box(
        "Text 1",
        "Text 2",
        "Text 3",
        label="Text",
        on_selection_change=set_value,
        selected_key=value,
    )

    text = ui.text("Selection: " + str(value))

    return combo, text


my_combo_box = ui_combo_box()

ComboBox (item table source)

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

import deephaven.ui as ui
from deephaven import time_table
import datetime

# Ticking table with initial row count of 200 that adds a row every second
initial_row_count = 200
_table = time_table(
    "PT1S",
    start_time=datetime.datetime.now() - datetime.timedelta(seconds=initial_row_count),
).update(
    [
        "Id=new Integer(i)",
        "Display=new String(`Display `+i)",
    ]
)


@ui.component
def ui_combo_box_item_table_source(table):
    value, set_value = ui.use_state("")

    combo = ui.combo_box(
        ui.item_table_source(table, key_column="Id", label_column="Display"),
        label="Text",
        on_change=set_value,
        selected_key=value,
    )

    text = ui.text(f"Selection: {value}")

    return combo, text


my_combo_box_item_table_source = ui_combo_box_item_table_source(_table)

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.

from deephaven import ui


@ui.component
def ui_list_view():
    value, set_value = ui.use_state(["Text 2"])

    # list_view with text children
    lv = ui.list_view(
        "Text 1",
        "Text 2",
        "Text 3",
        aria_label="List View - Basic",
        on_change=set_value,
        selected_keys=value,
    )

    # list_view with item children
    lv2 = ui.list_view(
        ui.item("Item 1", key="Text 1"),
        ui.item("Item 2", key="Text 2"),
        ui.item("Item 3", key="Text 3"),
        aria_label="List View - Basic",
        on_change=set_value,
        selected_keys=value,
    )

    text = ui.text("Selection: " + ", ".join(map(str, value)), grid_column="span 2")

    return text, lv, lv2


lv = ui_list_view()

ListView (table)

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

from deephaven import time_table, ui
import datetime

# Ticking table with initial row count of 200 that adds a row every second
initial_row_count = 200
column_types = time_table(
    "PT1S",
    start_time=datetime.datetime.now() - datetime.timedelta(seconds=initial_row_count),
).update(
    [
        "Id=new Integer(i)",
        "Display=new String(`Display `+i)",
    ]
)


@ui.component
def ui_list_view_table():
    value, set_value = ui.use_state([])

    lv = ui.list_view(
        column_types,
        aria_label="List View",
        on_change=set_value,
        selected_keys=value,
    )

    text = ui.text("Selection: " + ", ".join(map(str, value)))

    return ui.flex(
        lv,
        text,
        direction="column",
        margin=10,
        gap=10,
        # necessary to avoid overflowing container height
        min_height=0,
    )


lv_table = ui_list_view_table()

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.

from deephaven import time_table, ui
import datetime

# Ticking table with initial row count of 200 that adds a row every second
initial_row_count = 200
column_types = time_table(
    "PT1S",
    start_time=datetime.datetime.now() - datetime.timedelta(seconds=initial_row_count),
).update(
    [
        "Id=new Integer(i)",
        "Display=new String(`Display `+i)",
    ]
)


@ui.component
def ui_list_view_table_source():
    value, set_value = ui.use_state([2, 4, 5])

    lv = ui.list_view(
        ui.item_table_source(column_types, key_column="Id", label_column="Display"),
        aria_label="List View",
        on_change=set_value,
        selected_keys=value,
    )

    text = ui.text("Selection: " + ", ".join(map(str, value)))

    return ui.flex(
        lv,
        text,
        direction="column",
        margin=10,
        gap=10,
        # necessary to avoid overflowing container height
        min_height=0,
    )


lv_table_source = ui_list_view_table_source()

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.

from deephaven import time_table, ui
import datetime

# Ticking table with initial row count of 200 that adds a row every second
initial_row_count = 200
_column_types = time_table(
    "PT1S",
    start_time=datetime.datetime.now() - datetime.timedelta(seconds=initial_row_count),
).update(
    [
        "Id=new String(`key-`+i)",
        "Display=new String(`Display `+i)",
    ]
)

# `ui.list_view`` with `ui.list_action_group` actions
@ui.component
def ui_list_view_action_group():
    value, set_value = ui.use_state(["key-2", "key-4", "key-5"])

    action_item_keys, set_action_item_idx = ui.use_state(["", ""])
    on_action = ui.use_callback(
        lambda action_key, item_key: set_action_item_idx([action_key, str(item_key)]),
        [],
    )

    lv = ui.list_view(
        _column_types,
        key_column="Id",
        label_column="Display",
        aria_label="List View",
        on_change=set_value,
        selected_keys=value,
        actions=ui.list_action_group(
            "Edit",
            "Delete",
            on_action=on_action,
        ),
    )

    text_selection = ui.text("Selection: " + ", ".join(map(str, value)))
    text_action = ui.text("Action: " + " ".join(map(str, action_item_keys)))

    return lv, text_selection, text_action


my_list_view_action_group = ui_list_view_action_group()

ListView (list action menu)

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

from deephaven import time_table, ui
import datetime

# Ticking table with initial row count of 200 that adds a row every second
initial_row_count = 200
_column_types = time_table(
    "PT1S",
    start_time=datetime.datetime.now() - datetime.timedelta(seconds=initial_row_count),
).update(
    [
        "Id=new String(`key-`+i)",
        "Display=new String(`Display `+i)",
    ]
)

# `ui.list_view`` with `ui.list_action_menu` actions
@ui.component
def ui_list_view_action_menu():
    value, set_value = ui.use_state(["key-2", "key-4", "key-5"])

    action_item_keys, set_action_item_idx = ui.use_state(["", ""])
    on_action = ui.use_callback(
        lambda action_key, item_key: set_action_item_idx([action_key, str(item_key)]),
        [],
    )

    lv = ui.list_view(
        _column_types,
        key_column="Id",
        label_column="Display",
        aria_label="List View",
        on_change=set_value,
        selected_keys=value,
        actions=ui.list_action_menu(
            "Edit",
            "Delete",
            on_action=on_action,
        ),
    )

    text_selection = ui.text("Selection: " + ", ".join(map(str, value)))
    text_action = ui.text("Action: " + " ".join(map(str, action_item_keys)))

    return lv, text_selection, text_action


my_list_view_action_menu = ui_list_view_action_menu()

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.

@ui.component
def ui_form():
    name, set_name = ui.use_state("Homer")
    age, set_age = ui.use_state(36)

    return ui.flex(
        ui.text_field(value=name, on_change=set_name),
        ui.slider(value=age, on_change=set_age),
        ui.text(f"Hello {name}, you are {age} years old"),
        direction="column",
    )


my_form = ui_form()

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.

from deephaven import ui
import deephaven.plot.express as dx


stocks = dx.data.stocks()


@ui.component
def illustrated_message_placeholder_example():
    input, set_input = ui.use_state("")
    filter, set_filter = ui.use_state("")

    def handle_submit(data):
        set_filter(input)

    my_form_submit = ui.form(
        ui.text_field(value=input, on_change=set_input, default_value=""),
        ui.button("Submit", type="submit"),
        on_submit=handle_submit,
    )
    return [
        (ui.illustrated_message("Please enter a filter"), my_form_submit)
        if filter == ""
        else stocks.where(f"Sym=`{filter}`")
    ]


my_illustrated_message_placeholder_example = illustrated_message_placeholder_example()

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):

@ui.component
def ui_button_event_printer(*children, id="My Button"):
    return ui.button(
        *children,
        on_key_down=print,
        on_key_up=print,
        on_press=print,
        on_press_start=print,
        on_press_end=print,
        on_press_change=lambda is_pressed: print(f"{id} is_pressed: {is_pressed}"),
        on_press_up=print,
        on_focus=print,
        on_blur=print,
        on_focus_change=lambda is_focused: print(f"{id} is_focused: {is_focused}"),
        id=id,
    )


@ui.component
def ui_button_events():
    return [
        ui_button_event_printer("1", id="My Button 1"),
        ui_button_event_printer("2", id="My Button 2"),
    ]


my_button_events = ui_button_events()

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:

import deephaven.plot.express as dx

stocks = dx.data.stocks()

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).

@ui.component
def ui_text_filter_table(source, column):
    value, set_value = ui.use_state("FISH")
    t = source.where(f"{column}=`{value}`")
    return ui.flex(
        ui.text_field(value=value, on_change=set_value),
        t,
        direction="column",
        flex_grow=1,
    )


my_text_filter_table = ui_text_filter_table(stocks, "Sym")

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).

@ui.component
def ui_range_table(source, column):
    range, set_range = ui.use_state({"start": 1000, "end": 10000})
    t = source.where(f"{column} >= {range['start']} && {column} <= {range['end']}")
    return ui.flex(
        ui.range_slider(
            value=range, on_change=set_range, label=column, min_value=0, max_value=50000
        ),
        t,
        direction="column",
        flex_grow=1,
    )


my_range_table = ui_range_table(stocks, "Size")

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.
@ui.component
def ui_stock_widget_table(source, default_sym="", default_exchange=""):
    sym, set_sym = ui.use_state(default_sym)
    exchange, set_exchange = ui.use_state(default_exchange)

    ti1 = ui.text_field(
        label="Sym", label_position="side", value=sym, on_change=set_sym
    )
    ti2 = ui.text_field(
        label="Exchange", label_position="side", value=exchange, on_change=set_exchange
    )
    error_message = ui.illustrated_message(
        ui.icon("vsWarning", style={"fontSize": "48px"}),
        ui.heading("Invalid Input"),
        ui.content("Please enter 'Sym' and 'Exchange' above"),
    )
    t1 = (
        source.where([f"Sym=`{sym.upper()}`", f"Exchange=`{exchange.upper()}`"])
        if sym and exchange
        else error_message
    )

    return ui.flex(ui.flex(ti1, ti2), t1, direction="column", flex_grow=1)


my_stock_widget_table = ui_stock_widget_table(stocks, "", "")

Stock Widget Table Invalid Input

Stock Widget Table Valid Input

Plot with filters

You can also do plots as you would expect.

from deephaven.plot.figure import Figure


@ui.component
def ui_stock_widget_plot(source, default_sym="", default_exchange=""):
    sym, set_sym = ui.use_state(default_sym)
    exchange, set_exchange = ui.use_state(default_exchange)

    ti1 = ui.text_field(
        label="Sym", label_position="side", value=sym, on_change=set_sym
    )
    ti2 = ui.text_field(
        label="Exchange", label_position="side", value=exchange, on_change=set_exchange
    )
    t1 = source.where([f"Sym=`{sym.upper()}`", f"Exchange=`{exchange}`"])
    p = (
        Figure()
        .plot_xy(series_name=f"{sym}-{exchange}", t=t1, x="Timestamp", y="Price")
        .show()
    )

    return ui.flex(ui.flex(ti1, ti2), t1, p, direction="column", flex_grow=1)


my_stock_widget_plot = ui_stock_widget_plot(stocks, "CAT", "TPET")

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:

from deephaven import ui
from deephaven.plot import express as dx
from deephaven.plot.figure import Figure

_stocks = dx.data.stocks()
_cat_stocks = _stocks.where("Sym=`CAT`")
_dog_stocks = _stocks.where("Sym=`DOG`")
_stocks_plot = (
    Figure()
    .plot_xy("Cat", _cat_stocks, x="Timestamp", y="Price")
    .plot_xy("Dog", _dog_stocks, x="Timestamp", y="Price")
    .show()
)

my_dash = ui.dashboard(
    ui.column(
        ui.row(
            ui.stack(ui.panel(_cat_stocks, title="Cat")),
            ui.stack(ui.panel(_dog_stocks, title="Dog")),
        ),
        ui.stack(ui.panel(_stocks_plot, title="Stocks")),
    )
)

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:

from deephaven import ui, time_table
from deephaven.plot.figure import Figure


def use_wave_input():
    """
    Demonstrating a custom hook.
    Creates an input panel that controls the amplitude, frequency, and phase for a wave
    """
    amplitude, set_amplitude = ui.use_state(1.0)
    frequency, set_frequency = ui.use_state(1.0)
    phase, set_phase = ui.use_state(1.0)

    input_panel = ui.flex(
        ui.slider(
            label="Amplitude",
            default_value=amplitude,
            min_value=-100.0,
            max_value=100.0,
            on_change=set_amplitude,
            step=0.1,
        ),
        ui.slider(
            label="Frequency",
            default_value=frequency,
            min_value=-100.0,
            max_value=100.0,
            on_change=set_frequency,
            step=0.1,
        ),
        ui.slider(
            label="Phase",
            default_value=phase,
            min_value=-100.0,
            max_value=100.0,
            on_change=set_phase,
            step=0.1,
        ),
        direction="column",
    )

    return amplitude, frequency, phase, input_panel


@ui.component
def multiwave():
    amplitude, frequency, phase, wave_input = use_wave_input()

    tt = ui.use_memo(lambda: time_table("PT1s").update("x=i"), [])
    t = ui.use_memo(
        lambda: tt.update(
            [
                f"y_sin={amplitude}*Math.sin({frequency}*x+{phase})",
                f"y_cos={amplitude}*Math.cos({frequency}*x+{phase})",
                f"y_tan={amplitude}*Math.tan({frequency}*x+{phase})",
            ]
        ),
        [amplitude, frequency, phase],
    )
    p_sin = ui.use_memo(
        lambda: Figure().plot_xy(series_name="Sine", t=t, x="x", y="y_sin").show(), [t]
    )
    p_cos = ui.use_memo(
        lambda: Figure().plot_xy(series_name="Cosine", t=t, x="x", y="y_cos").show(),
        [t],
    )
    p_tan = ui.use_memo(
        lambda: Figure().plot_xy(series_name="Tangent", t=t, x="x", y="y_tan").show(),
        [t],
    )

    return ui.column(
        ui.row(
            ui.stack(
                ui.panel(wave_input, title="Wave Input"),
                ui.panel(t, title="Wave Table"),
                activeItemIndex=0,
            ),
            height=25,
        ),
        ui.row(
            ui.stack(ui.panel(p_sin, title="Sine"), width=50),
            ui.stack(ui.panel(p_cos, title="Cosine"), width=30),
            ui.stack(ui.panel(p_tan, title="Tangent")),
        ),
    )


mw = ui.dashboard(multiwave())

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.

from deephaven import ui
from deephaven import time_table


@ui.component
def waves():
    amplitude, set_amplitude = ui.use_state(1)
    frequency, set_frequency = ui.use_state(1)
    phase, set_phase = ui.use_state(1)

    tt = ui.use_memo(lambda: time_table("PT1s").update("x=i"), [])
    t = tt.update_view([f"y_sin={amplitude}*Math.sin({frequency}*x+{phase})"])

    return ui.flex(
        ui.flex(
            ui.slider(
                label="Amplitude",
                default_value=amplitude,
                min_value=-100,
                max_value=100,
                on_change=set_amplitude,
            ),
            ui.slider(
                label="Frequency",
                default_value=frequency,
                min_value=-100,
                max_value=100,
                on_change=set_frequency,
            ),
            ui.slider(
                label="Phase",
                default_value=phase,
                min_value=-100,
                max_value=100,
                on_change=set_phase,
            ),
            direction="column",
        ),
        t,
        flex_grow=1,
    )


w = waves()

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.

from deephaven import ui
from deephaven import time_table


def use_wave_input():
    """
    Demonstrating a custom hook.
    Creates an input panel that controls the amplitude, frequency, and phase for a wave
    """
    amplitude, set_amplitude = ui.use_state(1.0)
    frequency, set_frequency = ui.use_state(1.0)
    phase, set_phase = ui.use_state(1.0)

    input_panel = ui.flex(
        ui.slider(
            label="Amplitude",
            default_value=amplitude,
            min_value=-100.0,
            max_value=100.0,
            on_change=set_amplitude,
            step=0.1,
        ),
        ui.slider(
            label="Frequency",
            default_value=frequency,
            min_value=-100.0,
            max_value=100.0,
            on_change=set_frequency,
            step=0.1,
        ),
        ui.slider(
            label="Phase",
            default_value=phase,
            min_value=-100.0,
            max_value=100.0,
            on_change=set_phase,
            step=0.1,
        ),
        direction="column",
    )

    return amplitude, frequency, phase, input_panel


@ui.component
def waves():
    amplitude, frequency, phase, wave_input = use_wave_input()

    tt = ui.use_memo(lambda: time_table("PT1s").update("x=i"), [])
    t = tt.update([f"y_sin={amplitude}*Math.sin({frequency}*x+{phase})"])

    return ui.flex(wave_input, t, flex_grow=1)


w = waves()

Wave Input

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

from deephaven import ui
from deephaven.plot.figure import Figure


@ui.component
def waves_with_plot():
    amplitude, frequency, phase, wave_input = use_wave_input()

    tt = ui.use_memo(lambda: time_table("PT1s").update("x=i"), [])
    t = ui.use_memo(
        lambda: tt.update(
            [
                f"y_sin={amplitude}*Math.sin({frequency}*x+{phase})",
            ]
        ),
        [amplitude, frequency, phase],
    )
    p = ui.use_memo(
        lambda: Figure().plot_xy(series_name="Sine", t=t, x="x", y="y_sin").show(), [t]
    )

    return ui.flex(wave_input, ui.flex(t, max_width=200), p, flex_grow=1)


wp = waves_with_plot()

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.

from deephaven import ui
from deephaven.plot.figure import Figure


@ui.component
def multiwave():
    amplitude, frequency, phase, wave_input = use_wave_input()

    tt = ui.use_memo(lambda: time_table("PT1s").update("x=i"), [])
    t = ui.use_memo(
        lambda: tt.update(
            [
                f"y_sin={amplitude}*Math.sin({frequency}*x+{phase})",
                f"y_cos={amplitude}*Math.cos({frequency}*x+{phase})",
                f"y_tan={amplitude}*Math.tan({frequency}*x+{phase})",
            ]
        ),
        [amplitude, frequency, phase],
    )
    p_sin = ui.use_memo(
        lambda: Figure().plot_xy(series_name="Sine", t=t, x="x", y="y_sin").show(), [t]
    )
    p_cos = ui.use_memo(
        lambda: Figure().plot_xy(series_name="Cosine", t=t, x="x", y="y_cos").show(),
        [t],
    )
    p_tan = ui.use_memo(
        lambda: Figure().plot_xy(series_name="Tangent", t=t, x="x", y="y_tan").show(),
        [t],
    )

    return [
        ui.panel(wave_input, title="Wave Input"),
        ui.panel(t, title="Wave Table"),
        ui.panel(p_sin, title="Sine"),
        ui.panel(p_cos, title="Cosine"),
        ui.panel(p_tan, title="Tangent"),
    ]


mw = multiwave()

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:

from deephaven import ui


@ui.component
def text_filter_table(source, column, default_value=""):
    value, set_value = ui.use_state(default_value)
    return ui.flex(
        ui.text_field(
            label=column, label_position="side", value=value, on_change=set_value
        ),
        source.where(f"{column}=`{value}`"),
        direction="column",
        flex_grow=1,
    )


@ui.component
def double_table(source):
    return ui.flex(
        text_filter_table(source, "Sym", "FISH"),
        text_filter_table(source, "Exchange", "PETX"),
        flex_grow=1,
    )


dt = double_table(stocks)

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.

from deephaven import ui
from deephaven import agg
import deephaven.plot.express as dx

stocks = dx.data.stocks()


def get_by_filter(**byargs):
    """
    Gets a by filter where the arguments are all args passed in where the value is true.
    e.g.
    get_by_filter(Sym=True, Exchange=False) == ["Sym"]
    get_by_filter(Exchange=False) == []
    get_by_filter(Sym=True, Exchange=True) == ["Sym", "Exchange"]

    """
    return [k for k in byargs if byargs[k]]


@ui.component
def stock_table(source):
    is_sym, set_is_sym = ui.use_state(False)
    is_exchange, set_is_exchange = ui.use_state(False)
    highlight, set_highlight = ui.use_state("")
    aggs, set_aggs = ui.use_state(agg.avg(cols=["Size", "Price", "Dollars"]))

    by = get_by_filter(sym=is_sym, exchange=is_exchange)

    formatted_table = ui.use_memo(
        lambda: source.format_row_where(f"Sym=`{highlight}`", "LEMONCHIFFON"),
        [source, highlight],
    )
    rolled_table = ui.use_memo(
        lambda: (
            formatted_table
            if len(by) == 0
            else formatted_table.rollup(aggs=aggs, by=by)
        ),
        [formatted_table, aggs, by],
    )

    return ui.flex(
        ui.flex(
            ui.toggle_button(ui.icon("vsSymbolMisc"), "By Sym", on_change=set_is_sym),
            ui.toggle_button(
                ui.icon("vsBell"), "By Exchange", on_change=set_is_exchange
            ),
            (
                ui.fragment(
                    ui.text_field(
                        label="Highlight Sym",
                        label_position="side",
                        value=highlight,
                        on_change=set_highlight,
                    ),
                    ui.contextual_help(
                        ui.heading("Highlight Sym"),
                        ui.content("Enter a sym you would like highlighted."),
                    ),
                )
                if not is_sym and not is_exchange
                else None
            ),
            align_items="center",
            gap="size-100",
            margin="size-100",
            margin_bottom="0",
        ),
        rolled_table,
        direction="column",
        flex_grow=1,
    )


st = stock_table(stocks)

Stock Rollup

Listening to Table Updates

You can use the use_table_listener hook to listen to changes to a table. In this example, we use the use_table_listener hook to listen to changes to the table then display the last changes.

This is an advanced feature, requiring understanding of how the table listeners work, and limitations of running code while the Update Graph is running. Most usages of this are more appropriate to implement with the table data hooks.

from deephaven import ui
from deephaven.table import Table
from deephaven import time_table, empty_table, merge
from deephaven import pandas as dhpd
import pandas as pd


def to_table(update):
    return dhpd.to_table(pd.DataFrame.from_dict(update))


def add_as_op(ls, t, op):
    t = t.update(f"type=`{op}`")
    ls.append(t)


@ui.component
def monitor_changed_data(source: Table):

    changed, set_changed = ui.use_state(empty_table(0))

    show_added, set_show_added = ui.use_state(True)
    show_removed, set_show_removed = ui.use_state(True)

    def listener(update, is_replay):

        to_merge = []

        if (added_dict := update.added()) and show_added:
            added = to_table(added_dict)
            add_as_op(to_merge, added, "added")

        if (removed_dict := update.removed()) and show_removed:
            removed = to_table(removed_dict)
            add_as_op(to_merge, removed, "removed")

        if to_merge:
            set_changed(merge(to_merge))
        else:
            set_changed(empty_table(0))

    ui.use_table_listener(source, listener, [])

    added_check = ui.checkbox(
        "Show Added", isSelected=show_added, on_change=set_show_added
    )

    removed_check = ui.checkbox(
        "Show Removed", isSelected=show_removed, on_change=set_show_removed
    )

    return [added_check, removed_check, changed]


t = time_table("PT1S").update(formulas=["X=i"]).tail(5)

monitor = monitor_changed_data(t)

Handling liveness in functions

Some functions which interact with a component will create live objects that need to be managed by the component to ensure they are kept active.

The primary use case for this is when creating tables outside the component’s own function, and passing them as state for the component’s next update:

from deephaven import ui, time_table


@ui.component
def resetable_table():
    table, set_table = ui.use_state(lambda: time_table("PT1s"))
    handle_press = ui.use_liveness_scope(lambda _: set_table(time_table("PT1s")), [])
    return [
        ui.action_button(
            "Reset",
            on_press=handle_press,
        ),
        table,
    ]


f = resetable_table()

Without the use_liveness_scope wrapping the lamdba, the newly created live tables it creates go out of scope before the component can make use of it.

For more information on liveness scopes and why they are needed, see the liveness scope documentation.

Change Monitor

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
from deephaven import ui


@ui.component
def ui_tabs():
    return ui.tabs(
        ui.tab("Content 1", title="Tab 1"),
        ui.tab("Content 2", title="Tab 2", key="Key 2"),
        ui.tab("Content 3", title="Tab 3", icon=ui.icon("vsGithubAlt")),
    )


my_tabs = ui_tabs()

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.

from deephaven import ui
from deephaven.plot import express as dx

stocks = dx.data.stocks()


@ui.component
def ui_tabs(source):
    return ui.tabs(
        ui.tab_list(
            ui.item("Unfiltered", key="Unfiltered"),
            ui.item(ui.icon("vsGithubAlt"), "CAT", key="CAT"),
            ui.item("DOG", key="DOG"),
        ),
        ui.tab_panels(
            ui.item(source, key="Unfiltered"),
            ui.item(source.where("Sym=`CAT`"), key="CAT"),
            ui.item(source.where("Sym=`DOG`"), key="DOG"),
        ),
        flex_grow=1,
        aria_label="Tabs",  # aria_label is set for aria accessibility and is otherwise optional
    )


my_tabs = ui_tabs(stocks)

Using Table Data Hooks

There are five different hooks that can be used to get data from a table:

  1. use_table_data: Returns a dictionary of rows and columns from the table.
  2. use_row_data: Returns a single row from the table as a dictionary
  3. use_row_list: Returns a single row from the table as a list
  4. use_column_data: Returns a single column from the table as a list
  5. use_cell_data: Returns a single cell from the table

In this example, the hooks are used to display various pieces of information about LIZARD trades.

from deephaven import ui
from deephaven.table import Table
from deephaven import time_table, agg
import deephaven.plot.express as dx

stocks = dx.data.stocks()


@ui.component
def watch_lizards(source: Table):

    sold_lizards = source.where(["Side in `sell`", "Sym in `LIZARD`"])
    exchange_count_table = sold_lizards.view(["Exchange"]).count_by(
        "Count", by=["Exchange"]
    )
    last_sell_table = sold_lizards.tail(1)
    max_size_and_price_table = sold_lizards.agg_by([agg.max_(cols=["Size", "Price"])])
    last_ten_sizes_table = sold_lizards.view("Size").tail(10)
    average_sell_table = (
        sold_lizards.view(["Size", "Dollars"])
        .tail(100)
        .sum_by()
        .view("Average = Dollars/Size")
    )

    exchange_count = ui.use_table_data(exchange_count_table)
    last_sell = ui.use_row_data(last_sell_table)
    max_size_and_price = ui.use_row_list(max_size_and_price_table)
    last_ten_sizes = ui.use_column_data(last_ten_sizes_table)
    average_sell = ui.use_cell_data(average_sell_table)

    exchange_count_view = ui.view(f"Exchange counts {exchange_count}")
    last_sell_view = ui.view(f"Last Sold LIZARD: {last_sell}")
    max_size_and_price_view = ui.view(f"Max size and max price: {max_size_and_price}")
    last_ten_sizes_view = ui.view(f"Last Ten Sizes: {last_ten_sizes}")
    average_sell_view = ui.view(f"Average LIZARD price: {average_sell}")

    return ui.flex(
        exchange_count_view,
        last_sell_view,
        max_size_and_price_view,
        last_ten_sizes_view,
        average_sell_view,
        margin=10,
        gap=10,
        direction="column",
    )


watch = watch_lizards(stocks)

Table Hooks

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.

import logging
import threading
import time
from deephaven import read_csv, ui


@ui.component
def csv_loader():
    # The render_queue we fetch using the `use_render_queue` hook at the top of the component
    render_queue = ui.use_render_queue()
    table, set_table = ui.use_state()
    error, set_error = ui.use_state()

    def handle_submit(data):
        # We define a callable that we'll queue up on our own thread
        def load_table():
            try:
                # Read the table from the URL
                t = read_csv(data["url"])

                # Define our state updates in another callable. We'll need to call this on the render thread
                def update_state():
                    set_error(None)
                    set_table(t)

                # Queue up the state update on the render thread
                render_queue(update_state)
            except Exception as e:
                # In case we have any errors, we should show the error to the user. We still need to call this from the render thread,
                # so we must assign the exception to a variable and call the render_queue with a callable that will set the error
                error_message = e

                def update_state():
                    set_table(None)
                    set_error(error_message)

                # Queue up the state update on the render thread
                render_queue(update_state)

        # Start our own thread loading the table
        threading.Thread(target=load_table).start()

    return [
        # Our form displaying input from the user
        ui.form(
            ui.flex(
                ui.text_field(
                    default_value="https://media.githubusercontent.com/media/deephaven/examples/main/DeNiro/csv/deniro.csv",
                    label="Enter URL",
                    label_position="side",
                    name="url",
                    flex_grow=1,
                ),
                ui.button(f"Load Table", type="submit"),
                gap=10,
            ),
            on_submit=handle_submit,
        ),
        (
            # Display a hint if the table is not loaded yet and we don't have an error
            ui.illustrated_message(
                ui.heading("Enter URL above"),
                ui.content("Enter a URL of a CSV above and click 'Load' to load it"),
            )
            if error is None and table is None
            else None
        ),
        # The loaded table. Doesn't show anything if it is not loaded yet
        table,
        # An error message if there is an error
        (
            ui.illustrated_message(
                ui.icon("vsWarning"),
                ui.heading("Error loading table"),
                ui.content(f"{error}"),
            )
            if error != None
            else None
        ),
    ]


my_loader = csv_loader()