Skip to content

Commit

Permalink
feat: UI Table databars (#736)
Browse files Browse the repository at this point in the history
Fixes #441

```py
from deephaven import time_table, empty_table
from deephaven import ui
from deephaven.plot import express as dx

_stocks = dx.data.stocks()
_t = time_table("PT1S").update(["X=ii", "Y=X", "Z=X", "A=X", "B=X", "C=X", "HalfX=X/2", "LogX=X > 0 ? log(X) : 0", "X2=X*2"])

t = ui.table(_t, databars=[
    {
        "column": "X",
        "color": 'negative',
        "opacity": 0.4,
        "markers": [{ "value": "HalfX", "color":  "limegreen" }, { "value": 1000, "color": "accent" }]
    },
    {
        "column": "Y",
        "value_column": "LogX",
        "color": 'positive'
    },
    {
        "column": "Z",
        "max": 10,
        "color": ['notice', 'positive', 'dodgerblue']
    },
    {
        "column": "A",
        "max": "X2",
        "color": ['info', 'magenta-600']
    },
    {
        "column": "B",
        "color": ['#f3cd5b', '#9edc6f']
    },
    {
        "column": "C",
        "color": ['#f3cd5b', '#9edc6f']
    },
])

t2 = ui.table(_stocks, databars=[
    {
        "column": "random",
        "value_placement": "hide"
    },
    {
        "column": "SPet500",
        "color": 'info',
        "value_placement": "overlap"
    },
    {
        "column": "size",
        "max": 1000,
        "direction": "RTL",
        "color": ['notice', 'positive']
    },
    {
        "column": "sym",
        "value_column": "price",
        "color": ['magenta-200', 'magenta-800']
    },
])
```
  • Loading branch information
mattrunyon authored Aug 30, 2024
1 parent fb7bd78 commit ada20a3
Show file tree
Hide file tree
Showing 8 changed files with 1,270 additions and 1,988 deletions.
2,594 changes: 644 additions & 1,950 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions plugins/ui/src/deephaven/ui/components/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
ColumnGroup,
ColumnName,
ColumnPressCallback,
DatabarConfig,
QuickFilterExpression,
RowPressCallback,
ResolvableContextMenuItem,
Expand Down Expand Up @@ -39,6 +40,7 @@ def table(
context_header_menu: (
ResolvableContextMenuItem | list[ResolvableContextMenuItem] | None
) = None,
databars: list[DatabarConfig] | None = None,
) -> UITable:
"""
Customization to how a table is displayed, how it behaves, and listen to UI events.
Expand Down
70 changes: 67 additions & 3 deletions plugins/ui/src/deephaven/ui/types/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,9 +455,6 @@ class SliderChange(TypedDict):
]
ContextMenuModeOption = Literal["CELL", "ROW_HEADER", "COLUMN_HEADER"]
ContextMenuMode = Union[ContextMenuModeOption, List[ContextMenuModeOption], None]
DataBarAxis = Literal["PROPORTIONAL", "MIDDLE", "DIRECTIONAL"]
DataBarDirection = Literal["LTR", "RTL"]
DataBarValuePlacement = Literal["BESIDE", "OVERLAP", "HIDE"]
# TODO: Fill in the list of Deephaven Colors we allow
LockType = Literal["shared", "exclusive"]
QuickFilterExpression = str
Expand Down Expand Up @@ -509,3 +506,70 @@ class SliderChange(TypedDict):
TabDensity = Literal["compact", "regular"]
Dependencies = Union[Tuple[Any], List[Any]]
Selection = Sequence[Key]

DataBarAxis = Literal["PROPORTIONAL", "MIDDLE", "DIRECTIONAL"]
DataBarDirection = Literal["LTR", "RTL"]
DataBarValuePlacement = Literal["BESIDE", "OVERLAP", "HIDE"]


class DatabarConfig(TypedDict):
"""
Configuration for displaying a databar.
"""

column: ColumnName
"""
Name of the column to display as a databar.
"""

value_column: NotRequired[ColumnName]
"""
Name of the column to use as the value for the databar.
If not provided, the databar will use the column value.
This can be useful if you want to display a databar with
a log scale, but display the actual value in the cell.
In this case, the value_column would be the log of the actual value.
"""

min: NotRequired[Union[ColumnName, float]]
"""
Minimum value for the databar. Defaults to the minimum value in the column.
If a column name is provided, the minimum value will be the value in that column.
If a constant is providded, the minimum value will be that constant.
"""

max: NotRequired[Union[ColumnName, float]]
"""
Maximum value for the databar. Defaults to the maximum value in the column.
If a column name is provided, the maximum value will be the value in that column.
If a constant is providded, the maximum value will be that constant.
"""

axis: NotRequired[DataBarAxis]
"""
Whether the databar 0 value should be proportional to the min and max values,
in the middle of the cell, or on one side of the databar based on direction.
"""

direction: NotRequired[DataBarDirection]
"""
Direction of the databar.
"""

value_placement: NotRequired[DataBarValuePlacement]
"""
Placement of the value relative to the databar.
"""

color: NotRequired[Color]
"""
Color of the databar.
"""

opacity: NotRequired[float]
"""
Opacity of the databar fill.
"""
32 changes: 16 additions & 16 deletions plugins/ui/src/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,23 @@
"react-dom": "^17.0.2"
},
"dependencies": {
"@deephaven/chart": "^0.87.0",
"@deephaven/components": "^0.87.0",
"@deephaven/dashboard": "^0.86.0",
"@deephaven/dashboard-core-plugins": "^0.86.1",
"@deephaven/golden-layout": "^0.87.0",
"@deephaven/grid": "^0.87.0",
"@deephaven/icons": "^0.87.0",
"@deephaven/iris-grid": "^0.87.0",
"@deephaven/jsapi-bootstrap": "^0.87.0",
"@deephaven/jsapi-components": "^0.87.0",
"@deephaven/chart": "^0.91.0",
"@deephaven/components": "^0.91.0",
"@deephaven/dashboard": "^0.91.0",
"@deephaven/dashboard-core-plugins": "^0.91.0",
"@deephaven/golden-layout": "^0.91.0",
"@deephaven/grid": "^0.91.0",
"@deephaven/icons": "^0.91.0",
"@deephaven/iris-grid": "^0.91.0",
"@deephaven/jsapi-bootstrap": "^0.91.0",
"@deephaven/jsapi-components": "^0.91.0",
"@deephaven/jsapi-types": "^1.0.0-dev0.35.0",
"@deephaven/jsapi-utils": "^0.87.0",
"@deephaven/log": "^0.87.0",
"@deephaven/plugin": "^0.86.0",
"@deephaven/react-hooks": "^0.87.0",
"@deephaven/redux": "^0.86.0",
"@deephaven/utils": "^0.87.0",
"@deephaven/jsapi-utils": "^0.91.0",
"@deephaven/log": "^0.91.0",
"@deephaven/plugin": "^0.91.0",
"@deephaven/react-hooks": "^0.91.0",
"@deephaven/redux": "^0.91.0",
"@deephaven/utils": "^0.91.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@internationalized/date": "^3.5.5",
"classnames": "^2.5.1",
Expand Down
106 changes: 104 additions & 2 deletions plugins/ui/src/js/src/elements/UITable/JsTableProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,23 @@ interface JsTableProxy extends dh.Table {}
* 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 {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
class JsTableProxy implements dh.Table {
static HIDDEN_COLUMN_SUFFIXES = ['__DATABAR_Min', '__DATABAR_Max'];

private table: dh.Table;

/**
* Keep a stable reference to all, visible, and hidden columns.
* Only update when needed.
*/
private stableColumns: {
allColumns: dh.Column[];
visibleColumns: dh.Column[];
hiddenColumns: dh.Column[];
};

layoutHints: dh.LayoutHints | null = null;

constructor({
Expand All @@ -32,6 +46,12 @@ class JsTableProxy {
}) {
this.table = table;

this.stableColumns = {
allColumns: [],
visibleColumns: [],
hiddenColumns: [],
};

const {
frontColumns = null,
frozenColumns = null,
Expand Down Expand Up @@ -87,14 +107,96 @@ class JsTableProxy {
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(target), prop)
?.set != null;

if (proxyHasSetter) {
const proxyHasProp = Object.prototype.hasOwnProperty.call(target, prop);

if (proxyHasSetter || proxyHasProp) {
return Reflect.set(target, prop, value, target);
}

return Reflect.set(target.table, prop, value, target.table);
},
});
}

/**
* Update the stable columns object if needed.
* This lets us keep a stable array for columns unless the underlying table changes.
*/
private updateDisplayedColumns(): void {
if (this.stableColumns.allColumns !== this.table.columns) {
this.stableColumns.allColumns = this.table.columns;

this.stableColumns.visibleColumns = this.table.columns.filter(
column =>
!JsTableProxy.HIDDEN_COLUMN_SUFFIXES.some(suffix =>
column.name.endsWith(suffix)
)
);

this.stableColumns.hiddenColumns = this.table.columns.filter(column =>
JsTableProxy.HIDDEN_COLUMN_SUFFIXES.some(suffix =>
column.name.endsWith(suffix)
)
);
}
}

get columns(): dh.Column[] {
this.updateDisplayedColumns();
return this.stableColumns.visibleColumns;
}

get hiddenColumns(): dh.Column[] {
this.updateDisplayedColumns();
return this.stableColumns.hiddenColumns;
}

setViewport(
firstRow: number,
lastRow: number,
columns?: Array<dh.Column> | undefined | null,
updateIntervalMs?: number | undefined | null
): dh.TableViewportSubscription {
if (columns == null) {
return this.table.setViewport(
firstRow,
lastRow,
columns,
updateIntervalMs
);
}

const allColumns = columns.concat(this.hiddenColumns);
const viewportSubscription = this.table.setViewport(
firstRow,
lastRow,
allColumns,
updateIntervalMs
);
return new Proxy(viewportSubscription, {
get: (target, prop, receiver) => {
// Need to proxy setViewport on the subscription in case it is changed
// without creating an entirely new subscription
if (prop === 'setViewport') {
return (
first: number,
last: number,
cols?: dh.Column[] | null,
interval?: number | null
) => {
if (cols == null) {
return target.setViewport(first, last, cols, interval);
}

const proxyAllColumns = cols.concat(this.hiddenColumns);

return target.setViewport(first, last, proxyAllColumns, interval);
};
}
return Reflect.get(target, prop, receiver);
},
});
}
}

export default JsTableProxy;
Loading

0 comments on commit ada20a3

Please sign in to comment.