Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
c315bb7
Init
vbabich Jul 17, 2025
3e1b47a
WIP
vbabich Jul 18, 2025
838ce81
WIP
vbabich Jul 23, 2025
89d10d3
Totals row
vbabich Jul 25, 2025
f1186a6
Unit tests for pivot viewport/snapshots. Expandable columns.
vbabich Aug 8, 2025
68ea770
Unit tests, fix viewport offset
vbabich Aug 12, 2025
bbe5d1d
Unit tests cleanup - extract makePivotTable and makeUpdateEvent
vbabich Aug 13, 2025
909f858
Cleanup
vbabich Aug 14, 2025
90793bd
Column viewport
vbabich Aug 22, 2025
d936338
WIP
vbabich Aug 26, 2025
5e0a0c0
WIP
vbabich Aug 28, 2025
c1d8d11
Groups
vbabich Aug 29, 2025
78b4c23
WIP
vbabich Sep 5, 2025
fe6b168
Cleanup
vbabich Sep 5, 2025
30fbfff
Cleanup
vbabich Sep 5, 2025
7fbf1ee
Cleanuo
vbabich Sep 5, 2025
c23ccbf
Cleanup, fix bugs, expand/collapse indicators
vbabich Sep 10, 2025
bb08948
Merge remote-tracking branch 'origin/main' into pivot-plugin
vbabich Sep 10, 2025
2bd8296
Fix manifest
vbabich Sep 10, 2025
900bbfd
Format values in column headers
vbabich Sep 10, 2025
cca12a5
Update plugins/pivot/src/js/src/PivotPlugin.ts
vbabich Sep 18, 2025
58d4d19
Update plugins/pivot/src/deephaven/pivot/register.py
vbabich Sep 18, 2025
7de499c
Update plugins/pivot/src/js/src/ExpandableColumnHeaderGroup.ts
vbabich Sep 18, 2025
a412653
Merge branch 'pivot-plugin' of https://github.com/vbabich/deephaven-p…
vbabich Sep 18, 2025
efe7527
Add missing IrisGridModel methods
vbabich Sep 18, 2025
94a48fa
Address review comments
vbabich Sep 19, 2025
ff1b00b
Address review comments
vbabich Sep 19, 2025
fe7547e
Address review comments
vbabich Sep 19, 2025
e21bbc6
Render IrisGridPanel in pivot plugin
vbabich Sep 19, 2025
7560ef6
Update package.json
vbabich Sep 19, 2025
cec8ebe
Update package.json
vbabich Sep 19, 2025
0dd7b3c
Update packages, clean up components
vbabich Sep 22, 2025
43e7072
Fix types, update unit tests
vbabich Sep 23, 2025
4952cba
package-lock reset
vbabich Sep 23, 2025
0b8da2e
package-lock reset
vbabich Sep 23, 2025
f430765
Merge remote-tracking branch 'origin/main' into pivot-plugin
vbabich Sep 23, 2025
ec2530d
Cleanup diff
vbabich Sep 23, 2025
5fdde35
Cleanup diff
vbabich Sep 23, 2025
7b2f2c0
Cleanup diff
vbabich Sep 23, 2025
4508d5c
Fix types
vbabich Sep 23, 2025
cea808c
Update coreplus types package
vbabich Sep 23, 2025
b05730b
Fix types
vbabich Sep 23, 2025
cd21395
Fix class name
vbabich Sep 23, 2025
e3764af
TODO
vbabich Sep 23, 2025
b6f18b5
Update readme
vbabich Sep 24, 2025
f7d1ce5
Version bump
vbabich Sep 24, 2025
0d63a3d
Disable provenance for js plugin alpha release
vbabich Sep 24, 2025
535e6ca
Fix click handling on nested header groups
vbabich Sep 24, 2025
ca674df
encode keys in group header names
vbabich Sep 24, 2025
a48eabc
Remove provenance: false
vbabich Sep 24, 2025
5b061b1
Add repository info in package.json
vbabich Sep 24, 2025
45c3a21
Add forward ref for state persistence
vbabich Sep 25, 2025
219e99d
Cleanup, review comments
vbabich Sep 29, 2025
e05048f
Delete comments
vbabich Sep 30, 2025
5d08b30
Add group column, add key column filters
vbabich Oct 1, 2025
4d3d127
Merge remote-tracking branch 'origin/main' into pivot-plugin-group-co…
vbabich Oct 3, 2025
112d56c
Mouse handlers, filters WIP
vbabich Oct 13, 2025
129c8b8
Sorts chunk 1
vbabich Nov 21, 2025
7155ee6
Sorts - use sort descriptors
vbabich Nov 21, 2025
261e24a
Render sort indicators
vbabich Nov 25, 2025
30a7f5e
Merge remote-tracking branch 'origin/main' into pivot-plugin-group-co…
vbabich Dec 16, 2025
7d43af9
package-lock update
vbabich Dec 16, 2025
558d1e6
WIP
vbabich Dec 17, 2025
e6a034a
usePivotMetricCalculator hook
vbabich Dec 17, 2025
0c6fa19
Optimize isFilterable
vbabich Dec 17, 2025
b32f76a
Cleanup
vbabich Dec 17, 2025
784242a
Cleanup
vbabich Dec 18, 2025
6a644aa
Merge remote-tracking branch 'upstream/main' into pivot-plugin-group-…
vbabich Dec 18, 2025
8fd6ace
Restore packagelcok
vbabich Dec 18, 2025
c37ef6b
PivotFilterMouseHandler
vbabich Dec 23, 2025
defc9a5
Filter UI metrics, event handlers, and renderer
vbabich Jan 5, 2026
a63cb30
Scroll debug
vbabich Jan 13, 2026
1a6971f
Fix scrolling behavior
vbabich Jan 14, 2026
26403ad
Cleanup
vbabich Jan 14, 2026
49af255
Rename sourceTextWidth to columnSourceLabelWidth
vbabich Jan 14, 2026
c7a472e
getColumnWidth
vbabich Jan 14, 2026
bf947f5
Fix types
vbabich Jan 14, 2026
55f7d63
Cleanup, fix scroll and resize issues on smaller viewports
vbabich Jan 15, 2026
1ab0263
Update Web UI packages to 0.85.41
vbabich Jan 15, 2026
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,661 changes: 760 additions & 2,901 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions plugins/pivot/src/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@
},
"dependencies": {
"@deephaven/components": "^0.85.35",
"@deephaven/dashboard": "^0.85.38",
"@deephaven/dashboard-core-plugins": "^0.85.37",
"@deephaven/grid": "^0.85.38",
"@deephaven/dashboard": "^0.85.41",
"@deephaven/dashboard-core-plugins": "^0.85.41",
"@deephaven/grid": "^0.85.41",
"@deephaven/icons": "^0.85.0",
"@deephaven/iris-grid": "^0.85.39",
"@deephaven/iris-grid": "^0.85.41",
"@deephaven/jsapi-bootstrap": "^0.85.35",
"@deephaven/jsapi-utils": "^0.85.39",
"@deephaven/log": "^0.85.19",
"@deephaven/plugin": "^0.85.37",
"@deephaven/plugin": "^0.85.41",
"@deephaven/utils": "^0.85.35",
"lodash.clamp": "^4.0.3",
"lodash.throttle": "^4.1.1",
Expand Down
323 changes: 323 additions & 0 deletions plugins/pivot/src/js/src/IrisGridPivotMetricCalculator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
import {
IrisGridMetricCalculator,
type IrisGridMetricState,
} from '@deephaven/iris-grid';
import memoize from 'memoize-one';
import {
GridUtils,
type BoxCoordinates,
type GridMetrics,
type ModelIndex,
type VisibleIndex,
} from '@deephaven/grid';
import Log from '@deephaven/log';
import IrisGridPivotModel, { isIrisGridPivotModel } from './IrisGridPivotModel';
import PivotColumnHeaderGroup, {
isPivotColumnHeaderGroup,
} from './PivotColumnHeaderGroup';
import {
type IrisGridPivotMetricState,
type IrisGridPivotRenderState,
type PivotGridMetrics,
} from './IrisGridPivotTypes';
import { getKeyColumnGroups } from './PivotUtils';

const log = Log.module(
'@deephaven/js-plugin-pivot/IrisGridPivotMetricCalculator'
);

/**
* Get the width of a column that may be not in the viewport,
* based on the user, calculated, and theme widths.
* @param column The visible index of the column
* @param metrics The grid metrics
* @param themeColumnWidth The default column width from the theme
* @returns The width of the column
*/
export function getColumnWidth(
column: VisibleIndex,
metrics: PivotGridMetrics,
themeColumnWidth: number
): number {
const {
firstColumn,
allColumnWidths,
calculatedColumnWidths,
userColumnWidths,
treePaddingX,
} = metrics;

const modelColumn = GridUtils.getModelIndex(column, metrics.movedColumns);

// userColumnWidths and allColumnWidths include the treePaddingX on the first column
// calculatedColumnWidths does not, so we need to account for that here
// See GridMetricCalculator.calculateColumnWidth for reference
return (
userColumnWidths.get(modelColumn) ??
allColumnWidths.get(column) ??
(calculatedColumnWidths.has(modelColumn)
? (calculatedColumnWidths.get(modelColumn) ?? 0) +
(column === firstColumn ? treePaddingX : 0)
: undefined) ??
themeColumnWidth
);
}

/**
* Get the coordinates of a column header group, not clamped to the viewport.
* @param state The current render state of the IrisGridPivot
* @param group The PivotColumnHeaderGroup for which to get coordinates
* @returns The BoxCoordinates for the group, or null if not visible
*/
export function getColumnHeaderCoordinates(
state: IrisGridPivotRenderState,
group: PivotColumnHeaderGroup
): BoxCoordinates | null {
const { metrics, theme } = state;
const { childIndexes, depth } = group;
const firstChildIndex = childIndexes[0];
const lastChildIndex = childIndexes[childIndexes.length - 1];
if (firstChildIndex == null || lastChildIndex == null) {
throw new Error('Group has no child columns');
}
const { left, right, allColumnXs, allColumnWidths, gridX, gridY } = metrics;
const {
filterBarHeight,
columnHeaderHeight,
columnWidth: themeColumnWidth,
} = theme;

const firstVisible = Math.max(left, firstChildIndex);
const lastVisible = Math.min(right, lastChildIndex);
if (firstVisible > lastChildIndex || lastVisible < firstChildIndex) {
// Group is not visible
return null;
}

// Calculate the left edge by summing widths of all columns before firstVisible
const firstVisibleX = allColumnXs.get(firstVisible);
if (firstVisibleX == null) {
return null;
}
let groupX1 = firstVisibleX;
for (let i = firstChildIndex; i < firstVisible; i += 1) {
groupX1 -= getColumnWidth(i, metrics, themeColumnWidth);
}

const lastColumnX = allColumnXs.get(lastVisible);
const lastColumnWidth = allColumnWidths.get(lastVisible);
if (lastColumnX == null || lastColumnWidth == null) {
return null;
}

let groupX2 = lastColumnX + lastColumnWidth;
for (let i = lastVisible + 1; i <= lastChildIndex; i += 1) {
groupX2 += getColumnWidth(i, metrics, themeColumnWidth);
}

return {
x1: gridX + groupX1,
y1: gridY - filterBarHeight - (depth + 1) * columnHeaderHeight,
x2: gridX + groupX2,
y2: gridY - filterBarHeight - depth * columnHeaderHeight,
};
}

/**
* Type predicate to check if a metric calculator is an IrisGridPivotMetricCalculator
* @param calculator The metric calculator to check
* @returns True if the calculator is an IrisGridPivotMetricCalculator
*/
export function isIrisGridPivotMetricCalculator(
calculator: IrisGridMetricCalculator
): calculator is IrisGridPivotMetricCalculator {
return calculator instanceof IrisGridPivotMetricCalculator;
}

class IrisGridPivotMetricCalculator extends IrisGridMetricCalculator {
private getCachedColumnSourceLabelWidth = memoize(
(
keyColumnGroups: PivotColumnHeaderGroup[],
headerHorizontalPadding: number,
maxColumnWidth: number,
state: IrisGridPivotMetricState
): number => {
let result = 0;
keyColumnGroups.forEach(group => {
const sourceIndex = -group.depth;
const width = this.getColumnHeaderGroupTextWidth(
sourceIndex,
0,
state,
maxColumnWidth
);
result = Math.max(result, width + headerHorizontalPadding);
});
return result;
}
);

// Gets the text width for a column header group, including padding
getColumnHeaderGroupTextWidth(
modelColumn: ModelIndex,
depth: number,
state: IrisGridPivotMetricState,
maxColumnWidth: number
): number {
return super.getColumnHeaderGroupWidth(
modelColumn,
depth,
state,
maxColumnWidth
);
}

getColumnHeaderGroupWidth(
modelColumn: ModelIndex,
depth: number,
state: IrisGridPivotMetricState,
maxColumnWidth: number
): number {
return this.getColumnHeaderGroupTextWidth(
modelColumn,
depth,
state,
maxColumnWidth
);
}

/**
* Calculate the width needed for the column source labels
* @param model The IrisGridPivotModel instance
* @param state The current IrisGridPivotMetricState
* @returns The calculated width for the column source labels
*/
calculateColumnSourceLabelWidth(
model: IrisGridPivotModel,
state: IrisGridPivotMetricState
): number {
const { theme } = state;
const { headerHorizontalPadding, maxColumnWidth } = theme;
const keyColumnGroups = getKeyColumnGroups(model);

return this.getCachedColumnSourceLabelWidth(
keyColumnGroups,
headerHorizontalPadding,
maxColumnWidth,
state
);
}

/**
* Gets the metrics for the current state. This method has to be called before setColumnSize or resetColumnSize.
* @param state The current IrisGridPivotMetricState
* @returns The metrics for the current state
*/
getMetrics(state: IrisGridPivotMetricState): PivotGridMetrics {
const { model } = state;

if (!isIrisGridPivotModel(model)) {
throw new Error('Model is not an IrisGridPivotModel');
}

// Update column widths if columns in the cached model don't match the current model passed in the state
const columnSourceLabelWidth = this.calculateColumnSourceLabelWidth(
model,
state
);

return {
...super.getMetrics(state),
columnSourceLabelWidth,
};
}

/**
* Get metrics for positioning the filter bar input field.
* @param index The visible index of the column to get the filter box coordinates for
* @param state The current IrisGridMetricState
* @param metrics The grid metrics
* @returns Coordinates for the filter input field, or null if positioning cannot be calculated
*/
// eslint-disable-next-line class-methods-use-this
getFilterInputCoordinates(
index: VisibleIndex,
state: IrisGridPivotMetricState,
metrics: PivotGridMetrics
): { x: number; y: number; width: number; height: number } | null {
if (index >= 0) {
log.debug('getFilterInputCoordinates for index:', index);
return super.getFilterInputCoordinates(index, state, metrics);
}

const { model, theme } = state;
if (!isIrisGridPivotModel(model)) {
return null;
}

const { gridY, columnSourceLabelWidth } = metrics;

const { columnSourceFilterMinWidth, filterBarHeight } = theme;

// Find the key column group for this source index
// index is negative (-1, -2, etc.), and depth is positive (1, 2, etc.)
const depth = -index;
const keyColumnGroup = model.getColumnHeaderGroup(0, depth);

if (
keyColumnGroup == null ||
!isPivotColumnHeaderGroup(keyColumnGroup) ||
!keyColumnGroup.isKeyColumnGroup
) {
return null;
}

// Get the coordinates of the key column group
const groupCoords = getColumnHeaderCoordinates(
{ metrics, theme, model } as IrisGridPivotRenderState,
keyColumnGroup
);

if (groupCoords == null) {
return null;
}

const { x1, x2 } = groupCoords;
const columnSourceFilterWidth = Math.max(
columnSourceFilterMinWidth,
x2 - x1 - columnSourceLabelWidth
);

const x = x2 - columnSourceFilterWidth;
const y =
gridY - theme.columnHeaderHeight - (1 - index) * (filterBarHeight ?? 0);

return {
x,
y,
width: columnSourceFilterWidth + 1, // cover right border
height: (filterBarHeight ?? 0) - 1, // remove bottom border
};
}

/**
* Calculate the new left index to bring the given column into view.
* @param column The column that should be scrolled into view
* @param state The current IrisGridMetricState
* @param metrics The grid metrics
* @returns The left column index to scroll to, or null if no scroll is needed
*/
getScrollLeftForColumn(
column: VisibleIndex,
state: IrisGridMetricState,
metrics: GridMetrics
): VisibleIndex | null {
if (column < 0) {
return null;
}

return super.getScrollLeftForColumn(column, state, metrics);
}
}

export default IrisGridPivotMetricCalculator;
12 changes: 6 additions & 6 deletions plugins/pivot/src/js/src/IrisGridPivotModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,13 @@ class IrisGridPivotModel<R extends UIPivotRow = UIPivotRow>
dh: typeof CorePlusDhType;

get filter(): readonly DhType.FilterCondition[] {
return EMPTY_ARRAY;
return this.pivotTable.filter;
}

set filter(_: readonly DhType.FilterCondition[]) {
// No-op
// TODO: DH-20363: Add support for Pivot filters
set filter(filters: DhType.FilterCondition[]) {
log.debug2('Setting filter on pivot table', filters);
this.pivotTable.applyFilter(filters);
this.applyViewport();
}

hydratePivotSort(
Expand Down Expand Up @@ -627,8 +628,7 @@ class IrisGridPivotModel<R extends UIPivotRow = UIPivotRow>
}

isFilterable(columnIndex: ModelIndex): boolean {
// TODO: DH-20363: Add support for Pivot filters
return false;
return this.columns[columnIndex]?.isFilterable ?? false;
}

isColumnSortable(columnIndex: ModelIndex): boolean {
Expand Down
Loading
Loading