Skip to content

Commit

Permalink
feat: ui.fragment, ui.tabs, ui.tab_list, ui.tab_panels (#138)
Browse files Browse the repository at this point in the history
- When decoding the JSON document, convert the element nodes right to
their components then
- Eliminates wrapper components like `ElementView`, which was causing
some issues when used within components that expected children of a
certain type (such as Tabs/TabPanels/TabList from spectrum)
- Wraps exported objects in the `ObjectView` only when they are rendered
as children of an element
- Still checks for mixed panel/non-panels at root, and implicitly wraps
root if no panels
- Wrap children elements of spectrum components that are just `string`
type to a `Text` element
  - Fixes up padding issues within Spectrum components
- Fix styling on table to `display: contents`
  - Now it sizes correctly by default when used within tabs
- Use a context (ReactPanelManagerContext) to handle the opening/closing
of react panels within a document
  - Concept can be extended for opening/closing dashboards as well
- Add an example for `Tabs`/`ui.tabs`
- Looked into automatically mapping `ui.item`, but was annoying with
setting the keys, not bothering to deviate right now
- Handle exported object lifecycle more correctly
  - Now the `WidgetHandler` handles closing all the exported objects
- This will conflict with
#129, and needs
further discussion on how best to handle this, as some objects can't be
copied (only `Table` can be) and we may have the same object twice in a
document
  - Fixes #127
  • Loading branch information
mofojed authored Dec 20, 2023
1 parent d6d0416 commit cedcd3c
Show file tree
Hide file tree
Showing 32 changed files with 566 additions and 378 deletions.
29 changes: 29 additions & 0 deletions docker/data/storage/notebooks/DEMO.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,32 @@ def order_table():

result = order_table()
```

## Using Tabs

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 tabbed panel with multiple tabs:

- Unfiltered table
- Table filtered on sym `CAT`. We also include an icon in the tab header.
- Table filtered on sym `DOG`

```python
@ui.component
def table_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,
)


result = table_tabs(stocks)
```
67 changes: 35 additions & 32 deletions plugins/ui/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -1405,6 +1405,15 @@ ui_table.sort(
| `by` | `str \| Sequence[str]` | The column(s) to sort by. May be a single column name, or a list of column names. |
| `direction` | `SortDirection \| Sequence[SortDirection] \| None` | The sort direction(s) to use. If provided, that must match up with the columns provided. Defaults to "ASC". |

#### ui.fragment

A fragment maps to a [React.Fragment](https://react.dev/reference/react/Fragment). This lets you group elements without using a wrapper node. It only takes children, and does not take any additional props.

```py
import deephaven.ui as ui
ui_fragment = ui.fragment(*children: Element) -> Element
```

#### Deprecations

The functionality provided my `ui.table` replaces some of the existing functions on `Table`. Below are the functions that are planned for deprecation/deletion of the `Table` interface, and their replacements with the new `ui.table` interface.
Expand Down Expand Up @@ -1450,7 +1459,7 @@ use_table_listener(

Capture the data in a table. If the table is still loading, a sentinel value will be returned.
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
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.

###### Syntax
Expand All @@ -1464,17 +1473,16 @@ use_table_data(

###### 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`. |

##### use_column_data

Capture the data in a column. If the table is still loading, a sentinel value will be returned.
Data should already be filtered to desired rows and a specific column before passing to this hook as it is best to filter before data is retrieved and this hook will only return data for the first column.
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
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 a specific column.

###### Syntax
Expand All @@ -1488,17 +1496,16 @@ use_column_data(

###### Parameters

| Parameter | Type | Description |
|-------------------|-----------------|----------------------------------------------------------------------------|
| `table` | `Table` | The table to create a viewport on. |
| `sentinel` | `Sentinel` | A sentinel value to return if the column is still loading. Default `None`. |

| Parameter | Type | Description |
| ---------- | ---------- | -------------------------------------------------------------------------- |
| `table` | `Table` | The table to create a viewport on. |
| `sentinel` | `Sentinel` | A sentinel value to return if the column is still loading. Default `None`. |

##### use_row_data

Capture the data in a row. If the table is still loading, a sentinel value will be returned.
Data should already be filtered to a single row and desired columns before passing to this hook as it is best to filter before data is retrieved and this hook will only return data for the first row.
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 a specific row and functions such
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 a specific row and functions such
as [select or view](https://deephaven.io/core/docs/how-to-guides/use-select-view-update/) to retrieve specific columns.

###### Syntax
Expand All @@ -1512,17 +1519,16 @@ use_row_data(

###### Parameters

| Parameter | Type | Description |
|------------|--------------------------------------|----------------------------------------------------------------------------------|
| `table` | `Table` | The table to create a viewport on. |
| `sentinel` | `Sentinel` | A sentinel value to return if the row is still loading. Default `None`. |

| Parameter | Type | Description |
| ---------- | ---------- | ----------------------------------------------------------------------- |
| `table` | `Table` | The table to create a viewport on. |
| `sentinel` | `Sentinel` | A sentinel value to return if the row is still loading. Default `None`. |

##### use_row_list

Capture the data in a row. If the table is still loading, a sentinel value will be returned. This function is identical to `use_row_data` except that it always returns a list of data instead of a `RowData` object for convenience.
Data should already be filtered to a single row and desired columns before passing to this hook as it is best to filter before data is retrieved and this hook will only return data for the first row.
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 a specific row and functions such
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 a specific row and functions such
as [select or view](https://deephaven.io/core/docs/how-to-guides/use-select-view-update/) to retrieve specific columns.

###### Syntax
Expand All @@ -1536,18 +1542,18 @@ use_row_list(

###### Parameters

| Parameter | Type | Description |
|------------|--------------------------------------|----------------------------------------------------------------------------------|
| `table` | `Table` | The table to create a viewport on. |
| `sentinel` | `Sentinel` | A sentinel value to return if the row is still loading. Default `None`. |

| Parameter | Type | Description |
| ---------- | ---------- | ----------------------------------------------------------------------- |
| `table` | `Table` | The table to create a viewport on. |
| `sentinel` | `Sentinel` | A sentinel value to return if the row is still loading. Default `None`. |

##### use_cell_data

Capture the data in a cell. If the table is still loading, a sentinel value will be returned.
Data should already be filtered to a single row and column before passing to this hook as it is best to filter before data is retrieved and this hook will only return data for the first cell.
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 a specific row and functions such
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 a specific row and functions such
as [select or view](#https://deephaven.io/core/docs/how-to-guides/use-select-view-update/) to retrieve a specific column.

```py
use_cell_data(
table: Table,
Expand All @@ -1557,11 +1563,10 @@ use_cell_data(

###### Parameters

| Parameter | Type | Description |
|-------------|--------------------------------------|--------------------------------------------------------------------------|
| `table` | `Table` | The table to create a viewport on. |
| `sentinel` | `Sentinel` | A sentinel value to return if the cell is still loading. Default `None`. |

| Parameter | Type | Description |
| ---------- | ---------- | ------------------------------------------------------------------------ |
| `table` | `Table` | The table to create a viewport on. |
| `sentinel` | `Sentinel` | A sentinel value to return if the cell is still loading. Default `None`. |

#### Custom Types

Expand Down Expand Up @@ -1635,8 +1640,6 @@ class LinkPoint(TypedDict):

```


#### Context

By default, the context of a `@ui.component` will be created per client session (same as [Parameterized Query's "parallel universe" today](https://github.com/deephaven-ent/iris/blob/868b868fc9e180ee948137b10b6addbac043605e/ParameterizedQuery/src/main/java/io/deephaven/query/parameterized/impl/ParameterizedQueryServerImpl.java#L140)). However, it would be interesting if it were possible to share a context among all sessions for the current user, and/or share a context with other users even; e.g. if one user selects and applies a filter, it updates immediately for all other users with that dashboard open. So three cases:
Expand Down
75 changes: 55 additions & 20 deletions plugins/ui/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ dt = double_table(stocks)

## 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](https://react-spectrum.adobe.com/react-spectrum/ToggleButton.html). You can also highlight a specific stock by entering the symbol in the text field.
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](https://react-spectrum.adobe.com/react-spectrum/ToggleButton.html). 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.

```python
import deephaven.ui as ui
Expand All @@ -584,11 +584,10 @@ 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.
Examples:
get_by_filter(sym=True, exchange=False) == ["sym"]
get_by_filter(exchange=False) == []
get_by_filter(sym=True, exchange=True) == ["sym", "exchange"]
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]]
Expand All @@ -608,10 +607,7 @@ def stock_table(source):
[source, highlight],
)
rolled_table = use_memo(
lambda: formatted_table
if len(by) == 0
else formatted_table.rollup(aggs=aggs, by=by),
[formatted_table, aggs, by],
lambda: t if len(by) == 0 else t.rollup(aggs=aggs, by=by), [t, aggs, by]
)

return ui.flex(
Expand All @@ -620,16 +616,20 @@ def stock_table(source):
ui.toggle_button(
ui.icon("vsBell"), "By Exchange", on_change=set_is_exchange
),
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."),
),
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",
Expand Down Expand Up @@ -711,3 +711,38 @@ monitor = monitor_changed_data(t)
```

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

## Tabs

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 tabbed panel with multiple tabs:

- Unfiltered table
- Table filtered on sym `CAT`. We also include an icon in the tab header.
- Table filtered on sym `DOG`

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

stocks = dx.data.stocks()


@ui.component
def table_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,
)


tt = table_tabs(stocks)
```
6 changes: 6 additions & 0 deletions plugins/ui/src/deephaven/ui/components/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .icon import icon
from .make_component import make_component as component
from .fragment import fragment
from .panel import panel
from .spectrum import *
from .table import table
Expand All @@ -16,19 +17,24 @@
"contextual_help",
"flex",
"form",
"fragment",
"grid",
"heading",
"icon",
"icon_wrapper",
"illustrated_message",
"html",
"number_field",
"item",
"panel",
"range_slider",
"slider",
"spectrum_element",
"switch",
"table",
"tab_list",
"tab_panels",
"tabs",
"text",
"text_field",
"toggle_button",
Expand Down
15 changes: 15 additions & 0 deletions plugins/ui/src/deephaven/ui/components/fragment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from __future__ import annotations

from typing import Any
from ..elements import BaseElement


def fragment(*children: Any):
"""
A React.Fragment: https://react.dev/reference/react/Fragment.
Used to group elements together without a wrapper node.
Args:
children: The children in the fragment.
"""
return BaseElement("deephaven.ui.components.Fragment", children=children)
32 changes: 32 additions & 0 deletions plugins/ui/src/deephaven/ui/components/spectrum/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ def icon_wrapper(*children, **props):
return spectrum_element("Icon", *children, **props)


def item(*children, **props):
"""
Python implementation for the Adobe React Spectrum Item component.
Used with Tabs: https://react-spectrum.adobe.com/react-spectrum/Tabs.html
"""
return spectrum_element("Item", *children, **props)


def illustrated_message(*children, **props):
"""
Python implementation for the Adobe React Spectrum IllustratedMessage component.
Expand Down Expand Up @@ -131,6 +139,30 @@ def switch(*children, **props):
return spectrum_element("Switch", *children, **props)


def tabs(*children, **props):
"""
Python implementation for the Adobe React Spectrum Tabs component.
https://react-spectrum.adobe.com/react-spectrum/Tabs.html
"""
return spectrum_element("Tabs", *children, **props)


def tab_list(*children, **props):
"""
Python implementation for the Adobe React Spectrum TabList component.
https://react-spectrum.adobe.com/react-spectrum/Tabs.html
"""
return spectrum_element("TabList", *children, **props)


def tab_panels(*children, **props):
"""
Python implementation for the Adobe React Spectrum TabPanels component.
https://react-spectrum.adobe.com/react-spectrum/Tabs.html
"""
return spectrum_element("TabPanels", *children, **props)


def text(*children, **props):
"""
Python implementation for the Adobe React Spectrum Text component.
Expand Down
Loading

0 comments on commit cedcd3c

Please sign in to comment.