Skip to content

Commit

Permalink
feat: Column sources for ui.table formatting (#1010)
Browse files Browse the repository at this point in the history
Fixes #984

Allows all string format values to instead specify a column as the
source. Ensures the column is always fetched and throws if the column is
not a string type (can cause some really bad performance issues if value
formatting is invalid). Also modified to just resolve all theme colors
since this change could lead to unresolved theme colors in a column
source.

The example in the docs or this example shows the feature working.
Change the String cast on line 6 to non-string to test the error is
thrown and displayed.

This example uses an input table joined to the table so you can adjust
the formatting colors via input table (something Raffi asked about
specifically)

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

_stocks = dx.data.stocks()
_source = _stocks.select_distinct("Sym").update("Color=(String)null")

color_source = input_table(init_table=_source, key_cols="Sym")

t = ui.table(
    _stocks.natural_join(color_source, "Sym", "SymColor=Color"),
    hidden_columns=["SymColor"],
    format_=[
        ui.TableFormat(cols="Sym", background_color="SymColor")
    ],
)
```
  • Loading branch information
mattrunyon authored Nov 12, 2024
1 parent 1343ec8 commit c25f578
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 20 deletions.
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.

0 comments on commit c25f578

Please sign in to comment.