Skip to content

Commit

Permalink
WIP render comopnents when decoding
Browse files Browse the repository at this point in the history
- This way components are rendered directly
- Still need to automatically wrap tables, figures, and top level to a panel to keep that behaviour

Clean up a bit

- it's working for a simple counter example
- Still need to wrap in a panel automatically, wrap in UITable/UIFigure automatically, etc

Add tabs

- They work in the most basic example, but they ain't working with tables:
```
from deephaven import ui

@ui.component
def tab_test():
    return ui.panel(
        ui.tabs(
            ui.tab_list(
                ui.item('Hello'),
                ui.item('World')
            ),
            ui.tab_panels(
                ui.item('Foo'),
                ui.item('Bar')
            )
        )
    )

tt = tab_test()
```

Clean up how objects are implicitly rendered

- Add a ui.object_view to explicitly wrap an exported object in an ObjectView
- ExportedObjects that are passed as children of an element are implicitly converted to an ObjectView when decoding the JSON in the WidgetHandler
- Enforce no mixed panel/non-panels for the document root, as well as implicitly wrapping the root array in a panel if necessary

Clean up all tests affected

- They all pass again

Clean up tables a bit

- They now appear correctly in the Tabs component

Fix issue where opening a widget twice wouldn't re-open it correctly

- Wasn't memoizing the onOpen/onClose callbacks correctly in DocumentHandler

Add a spec for ui.fragment and implementation

- Also provided an example for it

Clean up example a bit

Some fixes after Don reviewing

- Automatically map "string" that is set as a children in a spectrum element to a `Text` element. Makes padding more correct for buttons/tabs and things
- Provide an example for Tabs
- Handle object lifecycle in WidgetHandler
  - This will conflict with my other PR, and the two need to merge
  - Also need to handle how widget plugins handle the exported object. Not all objects can be copied (only Table can be), and we may also need to fetch an object twice if it's re-used.
  • Loading branch information
mofojed committed Dec 13, 2023
1 parent be85375 commit a1769b2
Show file tree
Hide file tree
Showing 33 changed files with 583 additions and 378 deletions.
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
77 changes: 57 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,40 @@ 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.panel(
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)
```
8 changes: 8 additions & 0 deletions plugins/ui/src/deephaven/ui/components/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from .icon import icon
from .make_component import make_component as component
from .fragment import fragment
from .object_view import object_view
from .panel import panel
from .spectrum import *
from .table import table
Expand All @@ -16,19 +18,25 @@
"contextual_help",
"flex",
"form",
"fragment",
"grid",
"heading",
"icon",
"icon_wrapper",
"illustrated_message",
"html",
"number_field",
"item",
"object_view",
"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)
15 changes: 15 additions & 0 deletions plugins/ui/src/deephaven/ui/components/object_view.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 object_view(obj: Any):
"""
A wrapper for an exported object that can be rendered as a view.
E.g. A Table will be rendered as a Grid view.
Args:
obj: The object to display
"""
return BaseElement("deephaven.ui.components.Object", object=obj)
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 a1769b2

Please sign in to comment.