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

feat: Table hooks #168

Merged
merged 8 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from 6 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
19 changes: 13 additions & 6 deletions plugins/ui/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -1449,6 +1449,8 @@ use_table_listener(
##### use_table_data

Capture the data in a table. If the table is still loading, a sentinel value will be returned.
A transform function can be used to transform the data from a pandas Dataframe to a custom object, but this should
not be used to perform large filtering operations.
Data should already be filtered to the desired rows and columns before passing to this hook as it is best to filter before data is retrieved.
Use functions such as [head](https://deephaven.io/core/docs/reference/table-operations/filter/head/) or [slice](https://deephaven.io/core/docs/reference/table-operations/filter/slice/) to retrieve specific rows and functions such
as [select or view](https://deephaven.io/core/docs/how-to-guides/use-select-view-update/) to retrieve specific columns.
Expand All @@ -1458,16 +1460,20 @@ as [select or view](https://deephaven.io/core/docs/how-to-guides/use-select-view
```py
use_table_data(
table: Table,
sentinel: Sentinel = None
) -> TableData | Sentinel:
sentinel: Sentinel = None,
transform: Callable[
[pd.DataFrame | Sentinel, bool], TransformedData | Sentinel
] = None,
) -> TableData | Sentinel | TransformedData:
```

###### Parameters

| Parameter | Type | Description |
|--------------------|--------------------------------------|------------------------------------------------------------------------------|
| `table` | `Table` | The table to retrieve data from. |
| `sentinel` | `Sentinel` | A sentinel value to return if the viewport is still loading. Default `None`. |
| Parameter | Type | Description |
|--------------------|----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `table` | `Table` | The table to retrieve data from. |
| `sentinel` | `Sentinel` | A sentinel value to return if the viewport is still loading. Default `None`. |
| `transform` | `Callable[[pd.DataFrame, bool], Any]` | A function to transform the data from a pandas Dataframe to a custom object. The function takes a pandas dataframe or `Sentinel` as the first value and as a second value `bool` that is `True` if the the first value is the sentinel. |
jnumainville marked this conversation as resolved.
Show resolved Hide resolved


##### use_column_data
Expand Down Expand Up @@ -1596,6 +1602,7 @@ SelectionMode = Literal["CELL", "ROW", "COLUMN"]
Sentinel = Any
SortDirection = Literal["ASC", "DESC"]
TableData = dict[ColumnName, ColumnData]
TransformedData = Any

# Set a filter for a dashboard. Filter will apply to all items with a matching column/type, except for items specified in the `exclude_ids` parameter
class DashboardFilter(TypedDict):
Expand Down
68 changes: 67 additions & 1 deletion plugins/ui/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -710,4 +710,70 @@ t = time_table("PT1S").update(formulas=["X=i"]).tail(5)
monitor = monitor_changed_data(t)
```

![Stock Rollup](assets/change_monitor.png)
![Change Monitor](assets/change_monitor.png)

## 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
import deephaven.ui as 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)
Binary file added plugins/ui/examples/assets/table_hooks.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions plugins/ui/src/deephaven/ui/hooks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
from .use_state import use_state
from .use_ref import use_ref
from .use_table_listener import use_table_listener
from .use_table_data import use_table_data
from .use_column_data import use_column_data
from .use_row_data import use_row_data
from .use_row_list import use_row_list
from .use_cell_data import use_cell_data


__all__ = [
"use_callback",
Expand All @@ -12,4 +18,9 @@
"use_state",
"use_ref",
"use_table_listener",
"use_table_data",
"use_column_data",
"use_row_data",
"use_row_list",
"use_cell_data",
]
41 changes: 41 additions & 0 deletions plugins/ui/src/deephaven/ui/hooks/use_cell_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from __future__ import annotations

from typing import Any
import pandas as pd

from deephaven.table import Table

from .use_table_data import use_table_data
from ..types import Sentinel


def _cell_data(data: pd.DataFrame, is_sentinel: bool) -> None:
"""
Return the first cell of the table.

Args:
data: pd.DataFrame: The table to extract the cell from.
is_sentinel: bool: Whether the sentinel value was returned.

Returns:
Any: The first cell of the table.
"""
try:
return data if is_sentinel else data.iloc[0, 0]
except IndexError:
# if there is a static table with no rows, we will get an IndexError
raise IndexError("Cannot get row list from an empty table")


def use_cell_data(table: Table, sentinel: Sentinel = None) -> Any:
"""
Return the first cell of the table. The table should already be filtered to only have a single cell.

Args:
table: Table: The table to extract the cell from.
sentinel: Sentinel: The sentinel value to return if the table is ticking but empty. Defaults to None.

Returns:
Any: The first cell of the table.
"""
return use_table_data(table, sentinel, _cell_data)
41 changes: 41 additions & 0 deletions plugins/ui/src/deephaven/ui/hooks/use_column_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from __future__ import annotations

import pandas as pd

from deephaven.table import Table

from .use_table_data import use_table_data
from ..types import Sentinel, ColumnData


def _column_data(data: pd.DataFrame, is_sentinel: bool) -> ColumnData:
"""
Return the first column of the table as a list.

Args:
data: pd.DataFrame: The table to extract the column from.
is_sentinel: bool: Whether the sentinel value was returned.

Returns:
ColumnData: The first column of the table as a list.
"""
try:
return data if is_sentinel else data.iloc[:, 0].tolist()
except IndexError:
# if there is a static table with no columns, we will get an IndexError
raise IndexError("Cannot get column data from an empty table")


def use_column_data(table: Table, sentinel: Sentinel = None) -> ColumnData | Sentinel:
"""
Return the first column of the table as a list. The table should already be filtered to only have a single column.

Args:
table: Table: The table to extract the column from.
sentinel: Sentinel: The sentinel value to return if the table is ticking but empty. Defaults to None.

Returns:
ColumnData | Sentinel: The first column of the table as a list or the
sentinel value.
"""
return use_table_data(table, sentinel, _column_data)
40 changes: 40 additions & 0 deletions plugins/ui/src/deephaven/ui/hooks/use_row_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from __future__ import annotations

import pandas as pd

from deephaven.table import Table

from .use_table_data import use_table_data
from ..types import Sentinel, RowData


def _row_data(data: pd.DataFrame, is_sentinel: bool) -> RowData:
"""
Return the first row of the table as a dictionary.

Args:
data: pd.DataFrame: The dataframe to extract the row from or the sentinel value.
is_sentinel: bool: Whether the sentinel value was returned.

Returns:
RowData: The first row of the table as a dictionary.
"""
try:
return data if is_sentinel else data.iloc[0].to_dict()
except IndexError:
# if there is a static table with no rows, we will get an IndexError
raise IndexError("Cannot get row data from an empty table")


def use_row_data(table: Table, sentinel: Sentinel = None) -> RowData | Sentinel:
"""
Return the first row of the table as a dictionary. The table should already be filtered to only have a single row.

Args:
table: Table: The table to extract the row from.
sentinel: Sentinel: The sentinel value to return if the table is ticking but empty. Defaults to None.

Returns:
RowData | Sentinel: The first row of the table as a dictionary or the sentinel value.
"""
return use_table_data(table, sentinel, _row_data)
41 changes: 41 additions & 0 deletions plugins/ui/src/deephaven/ui/hooks/use_row_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from __future__ import annotations

from typing import Any
import pandas as pd

from deephaven.table import Table

from .use_table_data import use_table_data
from ..types import Sentinel


def _row_list(data: pd.DataFrame, is_sentinel: bool) -> list[Any]:
"""
Return the first row of the table as a list.

Args:
data: pd.DataFrame | Sentinel: The dataframe to extract the row from or the sentinel value.
is_sentinel: bool: Whether the sentinel value was returned.

Returns:
list[Any]: The first row of the table as a list.
"""
try:
return data if is_sentinel else data.iloc[0].values.tolist()
except IndexError:
# if there is a static table with no rows, we will get an IndexError
raise IndexError("Cannot get row list from an empty table")


def use_row_list(table: Table, sentinel: Sentinel = None) -> list[Any] | Sentinel:
"""
Return the first row of the table as a list. The table should already be filtered to only have a single row.

Args:
table: Table: The table to extract the row from.
sentinel: Sentinel: The sentinel value to return if the table is ticking but empty. Defaults to None.

Returns:
list[Any] | Sentinel: The first row of the table as a list or the sentinel value.
"""
return use_table_data(table, sentinel, _row_list)
Loading