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: UI table formatting #950

Merged
merged 23 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions plugins/ui/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
# options for sphinx_autodoc_typehints
always_use_bars_union = True

strip_signature_backslash = True

from deephaven_server import Server

# need a server instance to pull types from the autodocs
Expand Down
153 changes: 146 additions & 7 deletions plugins/ui/docs/components/table.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,143 @@ _t = empty_table(10).update("X=i")
t = ui.table(_t)
```

## UI Recommendations
## UI recommendations

1. It is not necessary to use a UI table if you do not need any of its properties. You can just use the Deephaven table directly.
2. Use a UI table to show properties like filters as if the user had created them in the UI. Users can change the default values provided by the UI table, such as filters.
3. UI tables handle ticking tables automatically, so you can pass any Deephaven table to a UI table.

## Formatting

You can format the table using the `format_` prop. This prop takes a `ui.TableFormmat` object or list of `ui.TableFormat` objects. `ui.TableFormat` is a dataclass that encapsulates the formatting options for a table. The full list of formatting options can be found in the [API Reference](#tableformat).

### Formatting rows and columns

Every formatting rule may optionally specify `cols` and `if_` properties. The `cols` property is a column name or list of column names to which the formatting rule applies. If `cols` is omitted, then the rule will be applied to the entire row. The `if_` property is a Deephaven formula that indicates a formatting rule should be applied conditionally. The `if_` property _must_ evaluate to a boolean. If `if_` is omitted, then the rule will be applied to every row. These may be combined to apply formatting to specific columns only when a condition is met.

> [!NOTE]
> The `if_` property is a Deephaven formula evaluated in the engine. You can think of it like adding a new boolean column using [`update_view`](https://deephaven.io/core/docs/reference/table-operations/select/update-view/).

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add a sections for each formatter? Helps both SEO and shows a functional example of how to accomplish each task

#### Formatting table background colors

#### Formatting table decimals

#### Formatting table dates
...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it would make more sense to do Formatting Colors and Formatting Values unless you want each of those sections to be 1 or 2 sentences. That feels like overkill to me to show someone what "changing the background color to red" does or "changing text alignment to left".

I think formatting colors would explain what kind of color values you can use and what values can take colors.

Formatting values would link out to appropriate docs about value format strings (I think they're Java formatting strings, but need to verify)

Copy link
Contributor

Choose a reason for hiding this comment

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

Formatting colors and Formatting values sound reasonable in terms of avoiding super short sections; however, if a user is likely to search "Formatting table dates" and similar, it might be worth it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I ended up adding Formatting Colors as an h3 to describe you can (and should) use theme colors. Then text/background color as h4.

For values I added an h3 for numeric and datetime

The following example shows how to format the `Sym` and `Exchange` columns with a red background and white text when the `Sym` is `DOG`.

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

t = ui.table(
dx.data.stocks(),
format_=[
ui.TableFormat(
cols=["Sym", "Exchange"],
if_="Sym = `DOG`",
background_color="red",
color="white",
mattrunyon marked this conversation as resolved.
Show resolved Hide resolved
)
],
)
```

### Formatting rule priority

The last matching formatting rule for each property will be applied. This means the lowest priority rules should be first in the list with higher priority rules at the end.

In the following example, the `Sym` column will have a red background with white text, and the rest of the table will have a blue background with white text.

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

t = ui.table(
dx.data.stocks(),
format_=[
ui.TableFormat(background_color="blue", color="white"),
ui.TableFormat(cols="Sym", background_color="red"),
],
)
```

### Formatting color

Formatting rules for colors support Deephaven theme colors, hex colors, or any valid CSS color (e.g., `red`, `#ff0000`, `rgb(255, 0, 0)`). It is **recommended to use Deephaven theme colors** when possible to maintain a consistent look and feel across the UI. Theme colors will also automatically update if the user changes the theme.

#### Formatting text color

The `color` property sets the text color of the cell. If a cell has a `background_color`, but no `color` set, the text color will be set to black or white depending on which contrasts better with the background color. Setting the `color` property will override this behavior.

The following example will make all text the foreground color except the `Sym` column, which will be white. In dark mode, the foreground color is white, and in light mode, it is black. In light mode, the `Sym` column will be nearly invisible because it is not a theme color.

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

t = ui.table(
dx.data.stocks(),
format_=[
ui.TableFormat(color="fg"),
ui.TableFormat(cols="Sym", color="white"),
],
)
```

#### Formatting background color

The `background_color` property sets the background color of the cell. Setting the `background_color` without setting `color` will result in the text color automatically being set to black or white based on the contrast with the `background_color`.

The following example will make all the background color what is usually the foreground color. This means the table will have a white background with black text in dark theme and a black background with white text in light theme. The `Sym` column text will be the accent color in both themes.

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

t = ui.table(
dx.data.stocks(),
format_=[
ui.TableFormat(background_color="fg"),
ui.TableFormat(cols="Sym", color="accent"),
],
)
```

### Formatting numeric values

> [!WARNING]
> Datetime values are considered numeric. If you provide a default format for numeric values, it will also apply to datetime values. It is recommended to specify `cols` when applying value formats.

Numeric values can be formatted using the `value` property. The `value` property is a string that follows [the GWT Java NumberFormat syntax](https://www.gwtproject.org/javadoc/latest/com/google/gwt/i18n/client/NumberFormat.html). If a numeric format is applied to a non-numeric column, it will be ignored.

This example will format the `Price` and `Dollars` columns with the dollar sign, a comma separator for every 3 digits, 2 decimal places, and a minimum of 1 digit to the left of the decimal point. The `Random` column will be formatted with 3 decimal places and will drop the leading zero if the absolute value is less than 1.

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

t = ui.table(
dx.data.stocks(),
format_=[
ui.TableFormat(cols=["Price", "Dollars"], value="$#,##0.00"),
ui.TableFormat(cols="Random", value="#.000")
],
)
```

### Formatting datetime and timestamp values

Datetime and timestamp values can be formatted using the `value` property. The `value` property is a string that follows [the GWT Java DateTimeFormat syntax](https://www.gwtproject.org/javadoc/latest/com/google/gwt/i18n/client/DateTimeFormat.html) with additional support for nanoseconds. You may provide up to 9 `S` characters after the decimal to represent partial seconds down to nanoseconds.

The following example formats the `Timestamp` column to show the short date of the week, day of the month, short month name, full year, hours, minutes, seconds, and microseconds with the user selected timezone.

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

t = ui.table(
dx.data.stocks(),
format_=[
ui.TableFormat(cols="Timestamp", value="E, dd MMM yyyy HH:mm:ss.SSSSSS z"),
],
)
```

## Events

You can listen for different user events on a `ui.table`. There is both a `press` and `double_press` event for `row`, `cell`, and `column`. These events typically correspond to a click or double click on the table. The event payloads include table data related to the event. For `row` and `column` events, the corresponding data within the viewport will be sent to the event handler. The viewport is typically the visible area ± a window equal to the visible area (e.g., if rows 5-10 are visible, rows 0-15 will be in the viewport).
Expand All @@ -42,7 +173,7 @@ t = ui.table(
)
```

## Context Menu
## Context menu

Items can be added to the bottom of the `ui.table` context menu (right-click menu) by using the `context_menu` or `context_header_menu` props. The `context_menu` prop adds items to the cell context menu, while the `context_header_menu` prop adds items to the column header context menu. You can pass either a single dictionary for a single item or a list of dictionaries for multiple items.

Expand Down Expand Up @@ -106,7 +237,7 @@ t = ui.table(
)
```

### Dynamic Menu Items
### Dynamic menu items

Menu items can be dynamically created by passing a function as the context item. The function will be called with the data of the cell that was clicked when the menu was opened, and must return the menu items or None if you do not want to add context menu items based on the cell info.

Expand All @@ -130,7 +261,7 @@ t = ui.table(
)
```

## Column Order and Visibility
## Column order and visibility

You can freeze columns to the front of the table using the `frozen_columns` prop. Frozen columns will always be visible on the left side of the table, even when the user scrolls horizontally. The `frozen_columns` prop takes a list of column names to freeze.

Expand All @@ -153,7 +284,7 @@ t = ui.table(

![Example of column order and visibility](../_assets/table_column_order.png)

## Grouping Columns
## Grouping columns

Columns can be grouped visually using the `column_groups` prop. Columns in a column group are moved so they are next to each other, and a header spanning all columns in the group is added. Columns can be rearranged within a group, but they cannot be moved outside of the group without using the table sidebar menu.

Expand Down Expand Up @@ -195,7 +326,7 @@ t = ui.table(

![Example of column groups](../_assets/table_column_groups.png)

## Always Fetching Some Columns
## Always fetching some columns

Deephaven only fetches data for visible rows and columns within a window around the viewport (typically the viewport plus 1 page in all directions). This reduces the amount of data transferred between the server and client and allows displaying tables with billions of rows. Sometimes you may need to always fetch columns, such as a key column for a row press event. You can use the `always_fetch_columns` prop to specify columns that should always be fetched regardless of their visibility.

Expand All @@ -218,7 +349,7 @@ t = ui.table(
)
```

## Quick Filters
## Quick filters

Quick filters are an easy way to filter the table while also showing the user what filters are currently applied. These filters are applied on the server via request from the client, so users may change the filters without affecting other users. Unlike a `where` statement to filter a table on the server, quick filters can be easily changed by the user.

Expand Down Expand Up @@ -265,6 +396,14 @@ t = ui.table(

## API Reference

### Table

```{eval-rst}
.. dhautofunction:: deephaven.ui.table
```

### TableFormat

```{eval-rst}
.. dhautofunction:: deephaven.ui.TableFormat
```
4 changes: 3 additions & 1 deletion plugins/ui/src/deephaven/ui/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
from .tab_list import tab_list
from .tab_panels import tab_panels
from .tab import tab
from .table import table
from .table import table, TableDatabar, TableFormat
from .tabs import tabs
from .text import text
from .text_area import text_area
Expand Down Expand Up @@ -108,6 +108,8 @@
"stack",
"switch",
"table",
"TableDatabar",
"TableFormat",
"tab_list",
"tab_panels",
"tabs",
Expand Down
Loading
Loading