From 88a92ad9e57d423cc7756d4ca169f4a17c33fc79 Mon Sep 17 00:00:00 2001 From: Matthew Runyon Date: Thu, 27 Jun 2024 11:35:29 -0500 Subject: [PATCH 1/4] feat: ui.table front, back, frozen, hidden columns and column groups --- .../ui/src/deephaven/ui/components/table.py | 14 +++ plugins/ui/src/deephaven/ui/types/types.py | 40 ++++++- .../js/src/elements/UITable/JsTableProxy.ts | 100 ++++++++++++++++++ .../js/src/elements/{ => UITable}/UITable.tsx | 25 ++++- .../{utils => UITable}/UITableMouseHandler.ts | 0 .../{utils => UITable}/UITableUtils.tsx | 12 ++- plugins/ui/src/js/src/elements/index.ts | 2 +- plugins/ui/src/js/src/elements/utils/index.ts | 4 +- plugins/ui/test/deephaven/ui/test_ui_table.py | 69 ++++++++++++ 9 files changed, 251 insertions(+), 15 deletions(-) create mode 100644 plugins/ui/src/js/src/elements/UITable/JsTableProxy.ts rename plugins/ui/src/js/src/elements/{ => UITable}/UITable.tsx (87%) rename plugins/ui/src/js/src/elements/{utils => UITable}/UITableMouseHandler.ts (100%) rename plugins/ui/src/js/src/elements/{utils => UITable}/UITableUtils.tsx (83%) diff --git a/plugins/ui/src/deephaven/ui/components/table.py b/plugins/ui/src/deephaven/ui/components/table.py index 4b120b2ca..b0c92e655 100644 --- a/plugins/ui/src/deephaven/ui/components/table.py +++ b/plugins/ui/src/deephaven/ui/components/table.py @@ -4,6 +4,7 @@ from ..elements import UITable from ..types import ( CellPressCallback, + ColumnGroup, ColumnName, ColumnPressCallback, QuickFilterExpression, @@ -23,6 +24,11 @@ def table( quick_filters: dict[ColumnName, QuickFilterExpression] | None = None, show_quick_filters: bool = False, show_search: bool = False, + front_columns: list[ColumnName] | None = None, + back_columns: list[ColumnName] | None = None, + frozen_columns: list[ColumnName] | None = None, + hidden_columns: list[ColumnName] | None = None, + column_groups: list[ColumnGroup] | None = None, ) -> UITable: """ Customization to how a table is displayed, how it behaves, and listen to UI events. @@ -48,6 +54,14 @@ def table( quick_filters: The quick filters to apply to the table. Dictionary of column name to filter value. show_quick_filters: Whether to show the quick filter bar by default. show_search: Whether to show the search bar by default. + front_columns: The columns to pin to the front of the table. These will not be movable by the user. + back_columns: The columns to pin to the back of the table. These will not be movable by the user. + frozen_columns: The columns to freeze at the front of the table. + These will always be visible regardless of horizontal scrolling. + hidden_columns: The columns to hide by default. Users may show the columns by expanding them. + column_groups: Columns to group together. The groups will be shown in the table header. + Group names must be unique within the column and group names. + Groups may be nested by providing the group name as a child of another group. """ props = locals() del props["table"] diff --git a/plugins/ui/src/deephaven/ui/types/types.py b/plugins/ui/src/deephaven/ui/types/types.py index a69f6e021..63af59722 100644 --- a/plugins/ui/src/deephaven/ui/types/types.py +++ b/plugins/ui/src/deephaven/ui/types/types.py @@ -1,6 +1,4 @@ -import datetime -import pandas -import numpy +import sys from typing import ( Any, Dict, @@ -10,9 +8,18 @@ List, Tuple, Callable, - TypedDict, Sequence, ) + +if sys.version_info < (3, 11): + from typing_extensions import TypedDict, NotRequired +else: + from typing import TypedDict, NotRequired + +import datetime +import pandas +import numpy + from deephaven import SortDirection from deephaven.dtypes import DType @@ -54,6 +61,31 @@ class RowDataValue(CellData): """ +class ColumnGroup(TypedDict): + """ + Group of columns in a table. + Groups are displayed in the table header. + Groups may be nested. + """ + + name: str + """ + Name of the column group. + Must follow column naming rules and be unique within the column and group names. + """ + + children: List[str] + """ + List of child columns or groups in the group. + Names are other columns or groups. + """ + + color: NotRequired[str] + """ + Color for the group header. + """ + + class SliderChange(TypedDict): """ Data for a range slider change event. diff --git a/plugins/ui/src/js/src/elements/UITable/JsTableProxy.ts b/plugins/ui/src/js/src/elements/UITable/JsTableProxy.ts new file mode 100644 index 000000000..db1b5cfa9 --- /dev/null +++ b/plugins/ui/src/js/src/elements/UITable/JsTableProxy.ts @@ -0,0 +1,100 @@ +import type { dh } from '@deephaven/jsapi-types'; + +export interface UITableLayoutHints { + frontColumns?: string[]; + frozenColumns?: string[]; + backColumns?: string[]; + hiddenColumns?: string[]; + columnGroups?: dh.ColumnGroup[]; +} + +// This tricks TS into believing the class extends dh.Table +// Even though it is through a proxy +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface JsTableProxy extends dh.Table {} + +/** + * Class to proxy JsTable. + * Any methods implemented in this class will be utilized over the underlying JsTable methods. + * Any methods not implemented in this class will be proxied to the table. + */ +class JsTableProxy { + private table: dh.Table; + + layoutHints: dh.LayoutHints | null = null; + + constructor({ + table, + layoutHints, + }: { + table: dh.Table; + layoutHints: UITableLayoutHints; + }) { + this.table = table; + + const { + frontColumns = null, + frozenColumns = null, + backColumns = null, + hiddenColumns = null, + columnGroups = null, + } = layoutHints; + + this.layoutHints = { + frontColumns, + frozenColumns, + backColumns, + hiddenColumns, + columnGroups, + areSavedLayoutsAllowed: false, + }; + + // eslint-disable-next-line no-constructor-return + return new Proxy(this, { + // We want to use any properties on the proxy model if defined + // If not, then proxy to the underlying model + get(target, prop, receiver) { + // Does this class have a getter for the prop + // Getter functions are on the prototype + const proxyHasGetter = + Object.getOwnPropertyDescriptor(Object.getPrototypeOf(target), prop) + ?.get != null; + + if (proxyHasGetter) { + return Reflect.get(target, prop, receiver); + } + + // Does this class implement the property + const proxyHasProp = Object.prototype.hasOwnProperty.call(target, prop); + + // Does the class implement a function for the property + const proxyHasFn = Object.prototype.hasOwnProperty.call( + Object.getPrototypeOf(target), + prop + ); + + const trueTarget = proxyHasProp || proxyHasFn ? target : target.table; + const value = Reflect.get(trueTarget, prop, receiver); + + if (typeof value === 'function') { + return value.bind(trueTarget); + } + + return value; + }, + set(target, prop, value) { + const proxyHasSetter = + Object.getOwnPropertyDescriptor(Object.getPrototypeOf(target), prop) + ?.set != null; + + if (proxyHasSetter) { + return Reflect.set(target, prop, value, target); + } + + return Reflect.set(target.table, prop, value, target.table); + }, + }); + } +} + +export default JsTableProxy; diff --git a/plugins/ui/src/js/src/elements/UITable.tsx b/plugins/ui/src/js/src/elements/UITable/UITable.tsx similarity index 87% rename from plugins/ui/src/js/src/elements/UITable.tsx rename to plugins/ui/src/js/src/elements/UITable/UITable.tsx index 53e2b008d..11cdd8f51 100644 --- a/plugins/ui/src/js/src/elements/UITable.tsx +++ b/plugins/ui/src/js/src/elements/UITable/UITable.tsx @@ -13,8 +13,9 @@ import type { dh } from '@deephaven/jsapi-types'; import Log from '@deephaven/log'; import { getSettings, RootState } from '@deephaven/redux'; import { EMPTY_ARRAY } from '@deephaven/utils'; -import { UITableProps } from './utils/UITableUtils'; -import UITableMouseHandler from './utils/UITableMouseHandler'; +import { UITableProps } from './UITableUtils'; +import UITableMouseHandler from './UITableMouseHandler'; +import JsTableProxy from './JsTableProxy'; const log = Log.module('@deephaven/js-plugin-ui/UITable'); @@ -31,12 +32,24 @@ export function UITable({ table: exportedTable, showSearch: showSearchBar, showQuickFilters, + frontColumns, + backColumns, + frozenColumns, + hiddenColumns, + columnGroups, }: UITableProps): JSX.Element | null { const dh = useApi(); const [model, setModel] = useState(); const [columns, setColumns] = useState(); const utils = useMemo(() => new IrisGridUtils(dh), [dh]); const settings = useSelector(getSettings); + const [layoutHints] = useState({ + frontColumns, + backColumns, + frozenColumns, + hiddenColumns, + columnGroups, + }); const hydratedSorts = useMemo(() => { if (sorts !== undefined && columns !== undefined) { @@ -74,7 +87,11 @@ export function UITable({ let isCancelled = false; async function loadModel() { const reexportedTable = await exportedTable.reexport(); - const newTable = (await reexportedTable.fetch()) as dh.Table; + const table = await reexportedTable.fetch(); + const newTable = new JsTableProxy({ + table: table as dh.Table, + layoutHints, + }); const newModel = await IrisGridModelFactory.makeModel(dh, newTable); if (!isCancelled) { setColumns(newTable.columns); @@ -87,7 +104,7 @@ export function UITable({ return () => { isCancelled = true; }; - }, [dh, exportedTable]); + }, [dh, exportedTable, layoutHints]); const mouseHandlers = useMemo( () => diff --git a/plugins/ui/src/js/src/elements/utils/UITableMouseHandler.ts b/plugins/ui/src/js/src/elements/UITable/UITableMouseHandler.ts similarity index 100% rename from plugins/ui/src/js/src/elements/utils/UITableMouseHandler.ts rename to plugins/ui/src/js/src/elements/UITable/UITableMouseHandler.ts diff --git a/plugins/ui/src/js/src/elements/utils/UITableUtils.tsx b/plugins/ui/src/js/src/elements/UITable/UITableUtils.tsx similarity index 83% rename from plugins/ui/src/js/src/elements/utils/UITableUtils.tsx rename to plugins/ui/src/js/src/elements/UITable/UITableUtils.tsx index 1abb6aa1c..fccd6e0ff 100644 --- a/plugins/ui/src/js/src/elements/utils/UITableUtils.tsx +++ b/plugins/ui/src/js/src/elements/UITable/UITableUtils.tsx @@ -1,6 +1,6 @@ import type { dh } from '@deephaven/jsapi-types'; import { ColumnName, DehydratedSort, RowIndex } from '@deephaven/iris-grid'; -import { ELEMENT_KEY, ElementNode, isElementNode } from './ElementUtils'; +import { ELEMENT_KEY, ElementNode, isElementNode } from '../utils/ElementUtils'; import { ELEMENT_NAME, ElementName } from '../model/ElementConstants'; export type CellData = { @@ -18,7 +18,7 @@ export type ColumnIndex = number; export type RowDataMap = Record; -export interface UITableProps { +export type UITableProps = { table: dh.WidgetExportedObject; onCellPress?: (cellIndex: [ColumnIndex, RowIndex], data: CellData) => void; onCellDoublePress?: ( @@ -34,8 +34,12 @@ export interface UITableProps { sorts?: DehydratedSort[]; showSearch: boolean; showQuickFilters: boolean; - [key: string]: unknown; -} + frontColumns?: string[]; + backColumns?: string[]; + frozenColumns?: string[]; + hiddenColumns?: string[]; + columnGroups?: dh.ColumnGroup[]; +}; export type UITableNode = Required< ElementNode diff --git a/plugins/ui/src/js/src/elements/index.ts b/plugins/ui/src/js/src/elements/index.ts index d05cabf01..b91c6c9ba 100644 --- a/plugins/ui/src/js/src/elements/index.ts +++ b/plugins/ui/src/js/src/elements/index.ts @@ -15,5 +15,5 @@ export * from './RangeSlider'; export * from './Slider'; export * from './TabPanels'; export * from './TextField'; -export * from './UITable'; +export * from './UITable/UITable'; export * from './utils'; diff --git a/plugins/ui/src/js/src/elements/utils/index.ts b/plugins/ui/src/js/src/elements/utils/index.ts index 69419497a..9945404e1 100644 --- a/plugins/ui/src/js/src/elements/utils/index.ts +++ b/plugins/ui/src/js/src/elements/utils/index.ts @@ -2,5 +2,5 @@ export * from './ElementUtils'; export * from './EventUtils'; export * from './HTMLElementUtils'; export * from './IconElementUtils'; -export * from './UITableMouseHandler'; -export * from './UITableUtils'; +export * from '../UITable/UITableMouseHandler'; +export * from '../UITable/UITableUtils'; diff --git a/plugins/ui/test/deephaven/ui/test_ui_table.py b/plugins/ui/test/deephaven/ui/test_ui_table.py index 86a9713b7..1a232dd85 100644 --- a/plugins/ui/test/deephaven/ui/test_ui_table.py +++ b/plugins/ui/test/deephaven/ui/test_ui_table.py @@ -218,3 +218,72 @@ def test_sort(self): ) self.assertRaises(ValueError, ui_table.sort, ["X", "Y"], ["INVALID"]) + + def test_front_columns(self): + import deephaven.ui as ui + + t = ui.table(self.source, front_columns=["X"]) + + self.expect_render( + t, + { + "frontColumns": ["X"], + }, + ) + + def test_back_columns(self): + import deephaven.ui as ui + + t = ui.table(self.source, back_columns=["X"]) + + self.expect_render( + t, + { + "backColumns": ["X"], + }, + ) + + def test_frozen_columns(self): + import deephaven.ui as ui + + t = ui.table(self.source, frozen_columns=["X"]) + + self.expect_render( + t, + { + "frozenColumns": ["X"], + }, + ) + + def test_hidden_columns(self): + import deephaven.ui as ui + + t = ui.table(self.source, hidden_columns=["X"]) + + self.expect_render( + t, + { + "hiddenColumns": ["X"], + }, + ) + + def test_column_groups(self): + import deephaven.ui as ui + + t = ui.table( + self.source, + column_groups=[{"name": "Group", "children": ["X"], "color": "red"}], + ) + + self.expect_render( + t, + { + "columnGroups": [ + { + "name": "Group", + "children": ["X"], + "color": "red", + } + ], + }, + ) From 89becb970e8344665dcd1a91f73ffad6bf9e3e02 Mon Sep 17 00:00:00 2001 From: Matthew Runyon Date: Thu, 27 Jun 2024 14:56:43 -0500 Subject: [PATCH 2/4] Update design doc --- plugins/ui/DESIGN.md | 105 +++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/plugins/ui/DESIGN.md b/plugins/ui/DESIGN.md index e720cc6a7..82cdd8a40 100644 --- a/plugins/ui/DESIGN.md +++ b/plugins/ui/DESIGN.md @@ -1011,7 +1011,6 @@ def my_dashboard(): d = my_dashboard() ``` - ##### ui.tabs A tabs component can be used to organize content in a collection of tabs, allowing users to navigating between the different tabs. Children (the tabs) can be specified in one of two ways: @@ -1021,13 +1020,13 @@ A tabs component can be used to organize content in a collection of tabs, allowi ###### Parameters -| Parameter | Type | Description | -| ----------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `*children` | `Item \| TabList \| TabPanels` | The tab panels to render within the tabs component. | -| `on_change` | `Callable[[Key], None] \| None` | Alias of `on_selection_change`. Handler that is called when the tab selection changes. | -| `**props` | `Any` | Any other [Tabs](https://react-spectrum.adobe.com/react-spectrum/Tabs.html#tabs-props) prop -| +| Parameter | Type | Description | +| ----------- | ------------------------------- | ------------------------------------------------------------------------------------------- | +| `*children` | `Item \| TabList \| TabPanels` | The tab panels to render within the tabs component. | +| `on_change` | `Callable[[Key], None] \| None` | Alias of `on_selection_change`. Handler that is called when the tab selection changes. | +| `**props` | `Any` | Any other [Tabs](https://react-spectrum.adobe.com/react-spectrum/Tabs.html#tabs-props) prop | +| ###### Tabs using `ui.tab` @@ -1074,7 +1073,7 @@ ui.tabs( ###### Tabs using `ui.tab_list` and `ui.tab_panels` -If you need more control over the layout, types, and styling of the tabs, you can specify tabs using `ui.tab_list` and `ui.tab_panels` with `ui.tabs`. This approach provides greater flexibility for complex or customized tab structures, compared to the concise method of passing `ui.tab` to `ui.tabs`. +If you need more control over the layout, types, and styling of the tabs, you can specify tabs using `ui.tab_list` and `ui.tab_panels` with `ui.tabs`. This approach provides greater flexibility for complex or customized tab structures, compared to the concise method of passing `ui.tab` to `ui.tabs`. With this method, the keys must be provided and match for the tabs declared in the `ui.tab_list` and `ui.tab_panels`. @@ -1138,7 +1137,6 @@ t4 = ui.tabs( ) ``` - #### Components ##### ui.list_action_group @@ -1189,7 +1187,6 @@ def list_action_menu( | `on_open_change` | `Callable[[bool, Key], None] \| None` | The first argument is a boolean indicating if the menu is open, the second argument is the key of the list_view item. | | `**props` | `Any` | Any other [ActionMenu](https://react-spectrum.adobe.com/react-spectrum/ActionMenu.html) prop. | - ##### ui.list_view A list view that can be used to create a list of items. Children should be one of three types: @@ -1309,8 +1306,6 @@ list_view5 = ui.list_view( ``` - - ###### ui.date_picker A date picker that can be used to select a date. @@ -1750,6 +1745,7 @@ picker7 = ui.picker( on_selection_change=set_color ) ``` + ##### ui.section A section that can be added to a menu, such as a `ui.picker`. Children are the dropdown options. @@ -1790,7 +1786,6 @@ ui.item( | `*children` | `Stringable` | The options to render within the item. | | `**props` | `Any` | Any other Item prop | - ##### ui.item_table_source An item table source wraps a Table or PartitionedTable to provide additional information for @@ -1824,7 +1819,6 @@ ui.item_table_source( | `title_column` | `ColumnName \| None` | Only valid if table is of type `PartitionedTable`. The column of values to display as section names. Should be the same for all values in the constituent `Table`. If not specified, the section titles will be created from the `key_columns` of the `PartitionedTable`. | | `actions` | `ListActionGroupElement \| ListActionMenuElement \| None` | The action group or menus to render for all elements within the component, if supported. | - #### ui.table `ui.table` is a wrapper for a Deephaven `Table` object that allows you to add UI customizations or callbacks. The basic syntax for creating a `UITable` is: @@ -1860,11 +1854,12 @@ Other props that can be passed into `ui.table` are defined below. ```py ui_table( table: Table, - always_fetch_columns: ColumnNameCombination | None, - back_columns: ColumnNameCombination | None, - freeze_columns: ColumnNameCombination | None, - front_columns: ColumnNameCombination | None, - hide_columns: ColumnNameCombination | None, + always_fetch_columns: list[ColumnName] | None, + front_columns: list[ColumnName] | None, + back_columns: list[ColumnName] | None, + frozen_columns: list[ColumnName] | None, + hidden_columns: list[ColumnName] | None, + column_groups: list[ColumnGroup] | None, quick_filters: dict[ColumnName, QuickFilterExpression] | None, show_search: bool, show_quick_filters: bool, @@ -1891,36 +1886,37 @@ ui_table( ) -> UITable ``` -| Parameter | Type | Description | -| ------------------------ | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `always_fetch_columns` | `ColumnNameCombination \| None` | The columns to always fetch from the server. May be a single column name. These will not be affected by the users current viewport/horizontal scrolling. Useful if you have a column with key value data that you want to always include in the data sent for row click operations. | -| `back_columns` | `ColumnNameCombination \| None` | The columns to show at the back of the table. May be a single column name. These will not be moveable in the UI. | -| `freeze_columns` | `ColumnNameCombination \| None` | The columns to freeze to the front of the table. May be a single column name. These will always be visible and not affected by horizontal scrolling. | -| `front_columns` | `ColumnNameCombination \| None` | The columns to show at the front of the table. May be a single column name. These will not be moveable in the UI. | -| `hide_columns` | `ColumnNameCombination \| None` | The columns to hide by default from the table. May be a single column name. The user can still resize the columns to view them. | -| `quick_filters` | `dict[ColumnName, QuickFilterExpression] \| None` | Quick filters for the UI to apply to the table. | -| `show_search` | `bool` | `True` to show the search bar by default, `False` to not. `False` by default. | -| `show_quick_filters` | `bool` | `True` to show the quick filters by default, `False` to not. `False` by default. | -| `show_column_headers` | `bool \| None` | `True` to show the column headers by default, `False` to not. | -| `selection_mode` | `SelectionMode \| None` | Can be `MULTIPLE` to allow multiple selection or `SINGLE` to not allow it. | -| `selection_area` | `SelectionArea \| None` | The unit that is selected on press. Can be `ROW`, `COLUMN`, or `CELL`. | -| `selection_style` | `SelectionStyleCombination \| None` | The style of the selection. Can be `HIGHLIGHT`, `CHECKBOX`, or a combination of those. | -| `selected_rows` | `RowIndexCombination \| None` | The rows that are selected by default. Only valid if `selection_area` is `ROW`. | -| `selected_columns` | `ColumnIndexCombination \| None` | The columns that are selected by default. Only valid if `selection_area` is `COLUMN`. | -| `selected_cells` | `CellIndexCombination \| None` | The cells that are selected by default. Only valid if `selection_area` is `CELL`. | -| `density` | `DensityMode \| None` | The density of the table. Can be `COMPACT`, `REGULAR`, or `SPACIOUS`. | -| `column_display_names` | `dict[ColumnName, ColumnNameCombination] \| None` | The display names. If a sequence of column names is provided for a column, the display name will be set to the longest column name that can be fully displayed. | -| `on_row_press` | `Callable[[RowIndex, RowData], None] \| None` | The callback function to run when a cell in a row is released (such as a click). The first parameter is the row index, and the second is the row data provided in a dictionary where the column names are the keys. | -| `on_row_double_press` | `Callable[[RowIndex, RowData], None] \| None` | The callback function to run when a cell in a row is double pressed. The first parameter is the row index, and the second is the row data provided in a dictionary where the column names are the keys. | -| `on_cell_press` | `Callable[[CellIndex, CellData], None] \| None` | The callback function to run when a cell is released (such as a click). The first parameter is the cell index, and the second is the cell data. | -| `on_cell_double_press` | `Callable[[CellIndex, CellData], None] \| None` | The callback function to run when a cell is double pressed. The first parameter is the cell index, and the second is the cell data. | -| `on_column_press` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is released (such as a click). The only parameter is the column name. | -| `on_column_double_press` | `Callable[[ColumnName], None] \| None` | The callback function to run when a cell in a column is double pressed. The only parameter is the column name. | -| `on_search` | `Callable[[str], None] \| None` | The callback function to run when the search bar is used. The only parameter is the search string. | -| `on_quick_filter` | `Callable[[ColumnName, QuickFilterExpression], None] \| None` | The callback function to run when a quick filter is applied. The first parameter is the column name, and the second is the quick filter expression. | -| `on_freeze_column` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is frozen. The only parameter is the frozen column name. | -| `on_hide_column` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is hidden. The only parameter is the hidden column name. | -| `on_sort` | `Callable[[ColumnName, LiteralSortDirection], None] \| None` | The callback function to run when a column is sorted. The first parameter is the column name, and the second is the sort direction. | +| Parameter | Type | Description | +| ------------------------ | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `always_fetch_columns` | `list[ColumnName] \| None` | The columns to always fetch from the server. These will not be affected by the users current viewport/horizontal scrolling. Useful if you have a column with key value data that you want to always include in the data sent for row click operations. | +| `front_columns` | `list[ColumnName] \| None` | The columns to show at the front of the table. These will not be moveable in the UI. | +| `back_columns` | `list[ColumnName] \| None` | The columns to show at the back of the table. These will not be moveable in the UI. | +| `frozen_columns` | `list[ColumnName] \| None` | The columns to freeze to the front of the table. These will always be visible and not affected by horizontal scrolling. | +| `hidden_columns` | `list[ColumnName] \| None` | The columns to hide by default from the table. The user can still resize the columns to view them. | +| `column_groups` | `list[ColumnGroup] \| None` | The column groups to show in the table. Columns will be moved adjacent to the first child in the group. Groups will be shown in the table header. | +| `quick_filters` | `dict[ColumnName, QuickFilterExpression] \| None` | Quick filters for the UI to apply to the table. | +| `show_search` | `bool` | `True` to show the search bar by default, `False` to not. `False` by default. | +| `show_quick_filters` | `bool` | `True` to show the quick filters by default, `False` to not. `False` by default. | +| `show_column_headers` | `bool \| None` | `True` to show the column headers by default, `False` to not. | +| `selection_mode` | `SelectionMode \| None` | Can be `MULTIPLE` to allow multiple selection or `SINGLE` to not allow it. | +| `selection_area` | `SelectionArea \| None` | The unit that is selected on press. Can be `ROW`, `COLUMN`, or `CELL`. | +| `selection_style` | `SelectionStyleCombination \| None` | The style of the selection. Can be `HIGHLIGHT`, `CHECKBOX`, or a combination of those. | +| `selected_rows` | `RowIndexCombination \| None` | The rows that are selected by default. Only valid if `selection_area` is `ROW`. | +| `selected_columns` | `ColumnIndexCombination \| None` | The columns that are selected by default. Only valid if `selection_area` is `COLUMN`. | +| `selected_cells` | `CellIndexCombination \| None` | The cells that are selected by default. Only valid if `selection_area` is `CELL`. | +| `density` | `DensityMode \| None` | The density of the table. Can be `COMPACT`, `REGULAR`, or `SPACIOUS`. | +| `column_display_names` | `dict[ColumnName, ColumnNameCombination] \| None` | The display names. If a sequence of column names is provided for a column, the display name will be set to the longest column name that can be fully displayed. | +| `on_row_press` | `Callable[[RowIndex, RowData], None] \| None` | The callback function to run when a cell in a row is released (such as a click). The first parameter is the row index, and the second is the row data provided in a dictionary where the column names are the keys. | +| `on_row_double_press` | `Callable[[RowIndex, RowData], None] \| None` | The callback function to run when a cell in a row is double pressed. The first parameter is the row index, and the second is the row data provided in a dictionary where the column names are the keys. | +| `on_cell_press` | `Callable[[CellIndex, CellData], None] \| None` | The callback function to run when a cell is released (such as a click). The first parameter is the cell index, and the second is the cell data. | +| `on_cell_double_press` | `Callable[[CellIndex, CellData], None] \| None` | The callback function to run when a cell is double pressed. The first parameter is the cell index, and the second is the cell data. | +| `on_column_press` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is released (such as a click). The only parameter is the column name. | +| `on_column_double_press` | `Callable[[ColumnName], None] \| None` | The callback function to run when a cell in a column is double pressed. The only parameter is the column name. | +| `on_search` | `Callable[[str], None] \| None` | The callback function to run when the search bar is used. The only parameter is the search string. | +| `on_quick_filter` | `Callable[[ColumnName, QuickFilterExpression], None] \| None` | The callback function to run when a quick filter is applied. The first parameter is the column name, and the second is the quick filter expression. | +| `on_freeze_column` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is frozen. The only parameter is the frozen column name. | +| `on_hide_column` | `Callable[[ColumnName], None] \| None` | The callback function to run when a column is hidden. The only parameter is the hidden column name. | +| `on_sort` | `Callable[[ColumnName, LiteralSortDirection], None] \| None` | The callback function to run when a column is sorted. The first parameter is the column name, and the second is the sort direction. | `ui.table` will also support the below methods. @@ -2406,6 +2402,17 @@ class LinkPoint(TypedDict): # Column to link to column: str + +# Typed dictionary for a column group +class ColumnGroup(TypedDict): + # Name of the group + name: str + + # Children columns/groups in the group + children: list[str] + + # Optional background color of the group header + color: NotRequired[str] ``` #### Context From f2874ab0d283a6ad18635eb62db57e4aa83ebefa Mon Sep 17 00:00:00 2001 From: Matthew Runyon Date: Fri, 28 Jun 2024 17:08:21 -0500 Subject: [PATCH 3/4] Add default to docstring --- plugins/ui/DESIGN.md | 4 ++-- plugins/ui/src/deephaven/ui/components/table.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/ui/DESIGN.md b/plugins/ui/DESIGN.md index 4119b9f2b..b82315151 100644 --- a/plugins/ui/DESIGN.md +++ b/plugins/ui/DESIGN.md @@ -1893,9 +1893,9 @@ ui_table( | `always_fetch_columns` | `list[ColumnName] \| None` | The columns to always fetch from the server. These will not be affected by the users current viewport/horizontal scrolling. Useful if you have a column with key value data that you want to always include in the data sent for row click operations. | | `front_columns` | `list[ColumnName] \| None` | The columns to show at the front of the table. These will not be moveable in the UI. | | `back_columns` | `list[ColumnName] \| None` | The columns to show at the back of the table. These will not be moveable in the UI. | -| `frozen_columns` | `list[ColumnName] \| None` | The columns to freeze to the front of the table. These will always be visible and not affected by horizontal scrolling. | +| `frozen_columns` | `list[ColumnName] \| None` | The columns to freeze by default to the front of the table. These will always be visible and not affected by horizontal scrolling. | | `hidden_columns` | `list[ColumnName] \| None` | The columns to hide by default from the table. The user can still resize the columns to view them. | -| `column_groups` | `list[ColumnGroup] \| None` | The column groups to show in the table. Columns will be moved adjacent to the first child in the group. Groups will be shown in the table header. | +| `column_groups` | `list[ColumnGroup] \| None` | The column groups to show in the table by default. Columns will be moved adjacent to the first child in the group. Groups will be shown in the table header. | | `quick_filters` | `dict[ColumnName, QuickFilterExpression] \| None` | Quick filters for the UI to apply to the table. | | `show_search` | `bool` | `True` to show the search bar by default, `False` to not. `False` by default. | | `show_quick_filters` | `bool` | `True` to show the quick filters by default, `False` to not. `False` by default. | diff --git a/plugins/ui/src/deephaven/ui/components/table.py b/plugins/ui/src/deephaven/ui/components/table.py index b03ce2317..443d5d1f3 100644 --- a/plugins/ui/src/deephaven/ui/components/table.py +++ b/plugins/ui/src/deephaven/ui/components/table.py @@ -63,10 +63,11 @@ def table( show_search: Whether to show the search bar by default. front_columns: The columns to pin to the front of the table. These will not be movable by the user. back_columns: The columns to pin to the back of the table. These will not be movable by the user. - frozen_columns: The columns to freeze at the front of the table. + frozen_columns: The columns to freeze by default at the front of the table. These will always be visible regardless of horizontal scrolling. + The user may unfreeze columns or freeze additional columns. hidden_columns: The columns to hide by default. Users may show the columns by expanding them. - column_groups: Columns to group together. The groups will be shown in the table header. + column_groups: Columns to group together by default. The groups will be shown in the table header. Group names must be unique within the column and group names. Groups may be nested by providing the group name as a child of another group. context_menu: The context menu items to show when a cell is right clicked. From ae8de531ff4e2d2077b28f76fdcd068e1c917373 Mon Sep 17 00:00:00 2001 From: Matthew Runyon Date: Fri, 5 Jul 2024 16:18:05 -0500 Subject: [PATCH 4/4] Address review comment --- plugins/ui/src/deephaven/ui/types/types.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/ui/src/deephaven/ui/types/types.py b/plugins/ui/src/deephaven/ui/types/types.py index a5c8228ac..b027e876b 100644 --- a/plugins/ui/src/deephaven/ui/types/types.py +++ b/plugins/ui/src/deephaven/ui/types/types.py @@ -23,6 +23,10 @@ from deephaven import SortDirection from deephaven.dtypes import DType +DeephavenColor = Literal["salmon", "lemonchiffon"] +HexColor = str +Color = Union[DeephavenColor, HexColor] + class CellData(TypedDict): """ @@ -80,7 +84,7 @@ class ColumnGroup(TypedDict): Names are other columns or groups. """ - color: NotRequired[str] + color: Color """ Color for the group header. """ @@ -246,9 +250,6 @@ class SliderChange(TypedDict): "UNIQUE", "SKIP", ] -DeephavenColor = Literal["salmon", "lemonchiffon"] -HexColor = str -Color = Union[DeephavenColor, HexColor] ContextMenuModeOption = Literal["CELL", "ROW_HEADER", "COLUMN_HEADER"] ContextMenuMode = Union[ContextMenuModeOption, List[ContextMenuModeOption], None] DataBarAxis = Literal["PROPORTIONAL", "MIDDLE", "DIRECTIONAL"]