Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: use_render_queue, use_liveness_scope, use_table_listener docs #1044

Merged
merged 20 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 0 additions & 165 deletions plugins/ui/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1448,104 +1448,6 @@ st = stock_table(stocks)

![Stock Rollup](_assets/stock_rollup.png)

## 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](https://deephaven.io/core/docs/how-to-guides/table-listeners-python/) 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](#using-table-data-hooks).

```python
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:

```python
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](https://deephaven.io/core/docs/conceptual/liveness-scope-concept/).

![Change Monitor](_assets/change_monitor.png)

## Tabs using ui.tab

You can add [Tabs](https://react-spectrum.adobe.com/react-spectrum/Tabs.html) 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.
Expand Down Expand Up @@ -1607,73 +1509,6 @@ def ui_tabs(source):
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.

```python
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](_assets/table_hooks.png)

## 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.
Expand Down
Binary file removed plugins/ui/docs/_assets/change_monitor.png
Binary file not shown.
7 changes: 4 additions & 3 deletions plugins/ui/docs/components/toast.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ my_mount_example = ui_toast_on_mount()

## Toast from table example

This example shows how to create a toast from the latest update of a ticking table. It is recommended to auto dismiss these toasts with a `timeout` and to avoid ticking faster than the value of the `timeout`.
This example shows how to create a toast from the latest update of a ticking table. It is recommended to auto dismiss these toasts with a `timeout` and to avoid ticking faster than the value of the `timeout`. Note that the toast must be triggered on the render thread, whereas the table listener may be fired from another thread. Therefore you must use the render queue to trigger the toast.

```python
from deephaven import time_table
Expand All @@ -123,7 +123,7 @@ def toast_table(t):
data_added = update.added()["X"][0]
render_queue(lambda: ui.toast(f"added {data_added}", timeout=5000))

ui.use_table_listener(t, listener_function, [t])
ui.use_table_listener(t, listener_function, [])
return t


Expand All @@ -141,7 +141,8 @@ 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
# The render_queue we fetch using the `use_render_queue` hook at the top of the component.
# The toast must be triggered from the render queue.
render_queue = ui.use_render_queue()
table, set_table = ui.use_state()
error, set_error = ui.use_state()
Expand Down
7 changes: 5 additions & 2 deletions plugins/ui/docs/hooks/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,12 @@ _Performance_ hooks let you optimize components for performance. They allow you

_Data_ hooks let you use data from within a Deephaven table in your component.

- [`use_table_data`](use_table_data.md) lets you use the full table contents.
- [`use_column_data`](use_column_data.md) lets you use the data of one column.
- [`use_table_data`](use_table_data.md) lets you use the full table contents as a dictionary of rows and columns.
- [`use_column_data`](use_column_data.md) lets you use the data of one column as a list.
- [`use_row_data`](use_row_data.md) lets you use the data of one row as a dictionary.
- [`use_row_list`](use_row_list.md) lets you use the data of one row as a list.
- [`use_cell_data`](use_cell_data.md) lets you use the data of one cell.
- [`use_table_listener`](use_table_listener.md) lets you listen to a table for updates.

## Create custom hooks

Expand Down
62 changes: 62 additions & 0 deletions plugins/ui/docs/hooks/use_liveness_scope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# use_liveness_scope
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question for @dsmmcken: When Salmon goes live, will these headings have to change the docs syntax:

---
id: whatever
title: whatever
sidebar_label: ...
---

And if so, is there a transition tool that will make changing them quick?


`use_liveness_scope` allows your to interact with the [liveness scope](https://deephaven.io/core/docs/conceptual/liveness-scope-concept/) for a component. 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.
mofojed marked this conversation as resolved.
Show resolved Hide resolved

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. If the table is not kept alive by the component, it will be garbage collected and the component will not be able to update with the new data.
mofojed marked this conversation as resolved.
Show resolved Hide resolved

## Example
mofojed marked this conversation as resolved.
Show resolved Hide resolved

```python
from deephaven import ui, time_table


@ui.component
def ui_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,
]


resetable_table = ui_resetable_table()
```

## UI recommendations

1. **Avoid using `use_liveness_scope` unless necessary**: This is an advanced feature that should only be used when you need to manage the liveness of objects outside of the component's own function. Prefer instead to derive a live component based on state rather than setting a live component within state.
mofojed marked this conversation as resolved.
Show resolved Hide resolved
2. **Use `use_liveness_scope` to manage live objects**: If you need to manage the liveness of objects created outside of the component's own function, use `use_liveness_scope` to ensure they are kept alive. For more information on liveness scopes and why they are needed, see the [liveness scope documentation](https://deephaven.io/core/docs/conceptual/liveness-scope-concept/).

## Refactoring to avoid liveness scope
mofojed marked this conversation as resolved.
Show resolved Hide resolved

In the above example, we could refactor the component to avoid using `use_liveness_scope` by deriving the table from state instead of setting it directly:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which is the better approach? If this is better than using a liveness scope, then why present the first example at all?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's kind of an escape hatch if you need it. Ideally you refactor such that you don't need to use it.


```python
from deephaven import ui, time_table


@ui.component
def ui_resetable_table():
iteration, set_iteration = ui.use_state(0)
table = ui.use_memo(lambda: time_table("PT1s"), [iteration])
return [
ui.action_button(
"Reset",
on_press=lambda: set_iteration(iteration + 1),
),
table,
]


resetable_table = ui_resetable_table()
```

## API Reference

```{eval-rst}
.. dhautofunction:: deephaven.ui.use_liveness_scope
```
Loading
Loading