Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ test-results/
playwright-report/
blob-report/
playwright/.cache/

# Ignore docs
**/*.md
18 changes: 18 additions & 0 deletions plugins/ui/docs/components/table.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,24 @@ t = ui.table(
)
```

### Formatting values from a column source

Any string value for a formatting rule can be read from a column by specifying the column name as the value. Note that if a value matches a column name, it will always be used (i.e., the theme color `positive` can not be used as a direct value if there is also a column called `positive`). The following example sets the `background_color` of column `x` using the value of the `bg_color` column.

```py
from deephaven import ui

_t = empty_table(100).update(["x = i", "y = sin(i)", "bg_color = x % 2 == 0 ? `positive` : `negative`"])

t = ui.table(
_t,
format_=[
ui.TableFormat(cols="x", background_color="bg_color"),
],
hidden_columns=["bg_color"],
)
```

### 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.
Expand Down
65 changes: 47 additions & 18 deletions plugins/ui/src/js/src/elements/UITable/UITable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useSelector } from 'react-redux';
import classNames from 'classnames';
import {
Expand All @@ -10,6 +16,7 @@ import {
IrisGridUtils,
} from '@deephaven/iris-grid';
import {
ColorValues,
colorValueStyle,
resolveCssVariablesInRecord,
useStyleProps,
Expand Down Expand Up @@ -39,9 +46,14 @@ const log = Log.module('@deephaven/js-plugin-ui/UITable');
* @returns A stable array if none of the elements have changed
*/
function useStableArray<T>(array: T[]): T[] {
// eslint-disable-next-line react-hooks/exhaustive-deps
const stableArray = useMemo(() => array, [...array]);
return stableArray;
const stableArray = useRef<T[]>(array);
if (
array.length !== stableArray.current.length ||
!array.every((v, i) => v === stableArray.current[i])
) {
stableArray.current = array;
}
return stableArray.current;
}

export function UITable({
Expand Down Expand Up @@ -162,17 +174,10 @@ export function UITable({
});
});

format.forEach(rule => {
const { color, background_color: backgroundColor } = rule;
if (color != null) {
colorSet.add(color);
}
if (backgroundColor != null) {
colorSet.add(backgroundColor);
}
});

const colorRecord: Record<string, string> = {};
ColorValues.forEach(c => {
colorRecord[c] = colorValueStyle(c);
});
colorSet.forEach(c => {
colorRecord[c] = colorValueStyle(c);
});
Expand All @@ -183,7 +188,7 @@ export function UITable({
newColorMap.set(key, value);
});
return newColorMap;
}, [databars, format, theme]);
}, [theme, databars]);

if (model) {
model.setColorMap(colorMap);
Expand Down Expand Up @@ -256,11 +261,35 @@ export function UITable({
};
}, [databars, dh, exportedTable, layoutHints, format, columnDisplayNames]);

// Get any format values that match column names
// Assume the format value is derived from the column
const formatColumnSources = useMemo(() => {
if (columns == null) {
return [];
}
const columnSet = new Set(columns.map(column => column.name));
const alwaysFetch: string[] = [];
format.forEach(rule => {
Object.entries(rule).forEach(([key, value]) => {
if (
key !== 'cols' &&
key !== 'if_' &&
typeof value === 'string' &&
columnSet.has(value)
) {
alwaysFetch.push(value);
}
});
});
return alwaysFetch;
}, [format, columns]);

const modelColumns = model?.columns ?? EMPTY_ARRAY;

const alwaysFetchColumnsArray = useStableArray(
ensureArray(alwaysFetchColumnsProp)
);
const alwaysFetchColumnsArray = useStableArray([
...ensureArray(alwaysFetchColumnsProp),
...formatColumnSources,
]);

const alwaysFetchColumns = useMemo(() => {
if (alwaysFetchColumnsArray[0] === true) {
Expand Down
23 changes: 21 additions & 2 deletions plugins/ui/src/js/src/elements/UITable/UITableModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,20 +439,39 @@ class UITableModel extends IrisGridModel {
// eslint-disable-next-line no-continue
continue;
}

let resolvedFormatValue = formatValue;
const columnSourceIndex =
typeof formatValue === 'string'
? this.getColumnIndexByName(formatValue)
: null;
if (columnSourceIndex != null) {
const columnSource = this.columns[columnSourceIndex];
if (!TableUtils.isStringType(columnSource.type)) {
throw new Error(
`Column ${columnSource.name} which provides TableFormat values for ${formatKey} is of type ${columnSource.type}. Columns that provide TableFormat values must be of type string.`
);
}
resolvedFormatValue = this.valueForCell(
columnSourceIndex,
row
) as NonNullable<FormattingRule[K]>;
}

if (
cols == null ||
this.formatColumnMatch(ensureArray(cols), columnName)
) {
if (if_ == null) {
return formatValue;
return resolvedFormatValue;
}
const rowValues = this.model.row(row)?.data;
if (rowValues == null) {
return undefined;
}
const whereValue = rowValues.get(getFormatCustomColumnName(i))?.value;
if (whereValue === true) {
return formatValue;
return resolvedFormatValue;
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions tests/app.d/ui_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@
],
)

t_color_column_source = ui.table(
_t.update("bg_color = x % 2 == 0 ? `positive` : `negative`"),
format_=[
ui.TableFormat(cols="x", background_color="bg_color"),
],
hidden_columns=["bg_color"],
)

t_priority = ui.table(
_t,
format_=[
Expand Down
1 change: 1 addition & 0 deletions tests/ui_table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ test.describe('UI flex components', () => {
't_alignment',
't_background_color',
't_color',
't_color_column_source',
't_priority',
't_value_format',
't_display_names',
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.