Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: "Group" column for rollup/tree tables #1636

Merged
merged 15 commits into from
Dec 15, 2023
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
14 changes: 9 additions & 5 deletions packages/grid/src/GridRange.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
export type GridRangeIndex = number | null;
export type RequiredGridRangeIndex = number;
type LeftIndex = GridRangeIndex;
type RightIndex = GridRangeIndex;
type TopIndex = GridRangeIndex;
type BottomIndex = GridRangeIndex;

export type GridCell = { column: number; row: number };
export type GridCell = {
column: RequiredGridRangeIndex;
row: RequiredGridRangeIndex;
};

export interface BoundedGridRange extends GridRange {
startColumn: number;
startRow: number;
endColumn: number;
endRow: number;
startColumn: RequiredGridRangeIndex;
startRow: RequiredGridRangeIndex;
endColumn: RequiredGridRangeIndex;
endRow: RequiredGridRangeIndex;
}

// Also exported via GridRange.SELECTION_DIRECTION
Expand Down
17 changes: 7 additions & 10 deletions packages/iris-grid/src/ColumnStatistics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@ import classNames from 'classnames';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, CopyButton, LoadingSpinner } from '@deephaven/components';
import { dhFreeze, dhRefresh, dhSortSlash, vsLock } from '@deephaven/icons';
import type {
Column,
ColumnStatistics as APIColumnStatistics,
} from '@deephaven/jsapi-types';
import type { ColumnStatistics as APIColumnStatistics } from '@deephaven/jsapi-types';
import Log from '@deephaven/log';
import { CancelablePromise, PromiseUtils } from '@deephaven/utils';
import { isExpandableGridModel } from '@deephaven/grid';
import './ColumnStatistics.scss';
import IrisGridModel from './IrisGridModel';
import IrisGridModel, { DisplayColumn } from './IrisGridModel';

const log = Log.module('ColumnStatistics');
const STATS_LABEL_OVERRIDES: Record<string, string> = {
Expand All @@ -26,14 +23,14 @@ interface Statistic {
}

interface ColumnStatisticsProps {
column: Column;
column: DisplayColumn;
model: IrisGridModel;
onStatistics: () => void;
}
interface ColumnStatisticsState {
error: unknown;
loading: boolean;
statistics: Statistic[] | null;
statistics: readonly Statistic[] | null;
numRows: number;
}

Expand Down Expand Up @@ -84,15 +81,15 @@ class ColumnStatistics extends Component<
cancelablePromise: CancelablePromise<APIColumnStatistics> | null;

maybeGenerateStatistics(): void {
const { model } = this.props;
const { column, model } = this.props;

const numRows =
model.rowCount -
model.pendingRowCount -
model.floatingBottomRowCount -
model.floatingTopRowCount;
this.setState({ numRows });
if (!model.isColumnStatisticsAvailable) {
if (!model.isColumnStatisticsAvailable || column.isProxy === true) {
this.setState({ loading: false });
} else if (numRows < ColumnStatistics.AUTO_GENERATE_LIMIT) {
this.handleGenerateStatistics();
Expand Down Expand Up @@ -200,7 +197,7 @@ class ColumnStatistics extends Component<
return (
<div className="column-statistics">
<div className="column-statistics-title">
{column.name}
{column.displayName ?? column.name}
<span className="column-statistics-type">&nbsp;({columnType})</span>
<CopyButton
className="column-statistics-copy"
Expand Down
39 changes: 22 additions & 17 deletions packages/iris-grid/src/CrossColumnSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
import { TableUtils } from '@deephaven/jsapi-utils';
import './CrossColumnSearch.scss';
import { ColumnName } from './CommonTypes';
import { DisplayColumn } from './IrisGridModel';

interface CrossColumnSearchProps {
value: string;
Expand All @@ -27,7 +28,7 @@ interface CrossColumnSearchProps {
selectedColumns: readonly ColumnName[],
invertSelection: boolean
) => void;
columns: readonly Column[];
columns: readonly DisplayColumn[];
}

interface CrossColumnSearchState {
Expand Down Expand Up @@ -233,23 +234,27 @@ class CrossColumnSearch extends PureComponent<
<div className="cross-column-popup">
Searched Columns
<div className="cross-column-scroll">
{columns.map(column => (
<React.Fragment key={column.name}>
<Checkbox
className="cross-column-checkbox"
checked={
invertSelection
? !selectedColumns.includes(column.name)
: selectedColumns.includes(column.name)
}
onChange={() => this.toggleColumn(column.name)}
>
{column.name}
</Checkbox>
{columns.map(column => {
if (column.isProxy === true) return null;

{column.type.substring(column.type.lastIndexOf('.') + 1)}
</React.Fragment>
))}
return (
<React.Fragment key={column.name}>
<Checkbox
className="cross-column-checkbox"
checked={
invertSelection
? !selectedColumns.includes(column.name)
: selectedColumns.includes(column.name)
}
onChange={() => this.toggleColumn(column.name)}
>
{column.name}
</Checkbox>

{column.type.substring(column.type.lastIndexOf('.') + 1)}
</React.Fragment>
);
})}
</div>
<div className="cross-column-button-bar">
<button
Expand Down
43 changes: 28 additions & 15 deletions packages/iris-grid/src/IrisGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,20 +171,21 @@ import { ChartBuilderSettings } from './sidebar/ChartBuilder';
import AggregationOperation from './sidebar/aggregations/AggregationOperation';
import { UIRollupConfig } from './sidebar/RollupRows';
import {
ReadonlyAdvancedFilterMap,
ColumnName,
ReadonlyQuickFilterMap,
ReadonlyAggregationMap,
ReadonlyOperationMap,
Action,
OptionItem,
UITotalsTableConfig,
InputFilter,
PendingDataMap,
AdvancedFilterMap,
AdvancedFilterOptions,
ColumnName,
InputFilter,
OperationMap,
OptionItem,
PendingDataErrorMap,
PendingDataMap,
QuickFilterMap,
OperationMap,
ReadonlyAdvancedFilterMap,
ReadonlyAggregationMap,
ReadonlyOperationMap,
ReadonlyQuickFilterMap,
UITotalsTableConfig,
} from './CommonTypes';
import ColumnHeaderGroup from './ColumnHeaderGroup';
import { IrisGridThemeContext } from './IrisGridThemeProvider';
Expand Down Expand Up @@ -1636,19 +1637,31 @@ export class IrisGrid extends Component<IrisGridProps, IrisGridState> {
});
}

removeColumnFilter(modelColumn: ModelIndex): void {
removeColumnFilter(modelRange: ModelIndex | BoundedAxisRange): void {
this.startLoading('Filtering...', true);

const clearRange: BoundedAxisRange = Array.isArray(modelRange)
? modelRange
: [modelRange, modelRange];

this.setState(
({ advancedFilters, quickFilters }: Partial<IrisGridState>) => {
const newAdvancedFilters = advancedFilters
const newAdvancedFilters: AdvancedFilterMap = advancedFilters
? new Map(advancedFilters)
: new Map();
const newQuickFilters = quickFilters
const newQuickFilters: QuickFilterMap = quickFilters
? new Map(quickFilters)
: new Map();
newQuickFilters.delete(modelColumn);
newAdvancedFilters.delete(modelColumn);
newAdvancedFilters.forEach((_, column) => {
if (column >= clearRange[0] && column <= clearRange[1]) {
newAdvancedFilters.delete(column);
}
});
newQuickFilters.forEach((_, column) => {
if (column >= clearRange[0] && column <= clearRange[1]) {
newQuickFilters.delete(column);
}
});

return {
quickFilters: newQuickFilters,
Expand Down
77 changes: 63 additions & 14 deletions packages/iris-grid/src/IrisGridModel.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* eslint-disable class-methods-use-this */
import type { Event, EventTarget } from 'event-target-shim';
import {
BoundedAxisRange,
DataBarGridModel,
DataBarOptions,
GridCell,
GridModel,
GridRange,
GridThemeType,
Expand Down Expand Up @@ -33,6 +35,21 @@ import {
} from './CommonTypes';
import ColumnHeaderGroup from './ColumnHeaderGroup';

export type DisplayColumn = Column & {
/**
* Name to display with the column.
* The `name` property on `Column` is a unique identifier and must be a valid Java identifier,
* whereas `displayName` can be any string and does not need to be unique.
*/
displayName?: string;

/**
* Whether this column is a proxy column for other columns or not.
* If it's a proxy column, it may not appear in some lists.
*/
isProxy?: boolean;
};

type IrisGridModelEventNames =
(typeof IrisGridModel.EVENT)[keyof typeof IrisGridModel.EVENT];

Expand Down Expand Up @@ -133,7 +150,23 @@ abstract class IrisGridModel<
* Gets the columns for this model
* @returns All columns in the model
*/
abstract get columns(): readonly Column[];
abstract get columns(): readonly DisplayColumn[];

/**
* Retrieve the grouped columns for this model
* @returns The columns that are grouped
*/
get groupedColumns(): readonly DisplayColumn[] {
return EMPTY_ARRAY;
}

/**
* Gets the columns for the model before any transformations (such as rollups) are applied.
* @returns All original columns in the model.
*/
get originalColumns(): readonly DisplayColumn[] {
return this.columns;
}

/**
* Gets the column index for this model
Expand All @@ -143,26 +176,42 @@ abstract class IrisGridModel<
abstract getColumnIndexByName(name: string): ModelIndex | undefined;

/**
* Gets the columns for the model before any transformations (such as rollups) are applied.
* @returns All original columns in the model.
* Retrieve the source cell for a given cell. Returns something different if the cell is a proxied cell
* that retrieves data from another cell.
* @param column Column to get the source for
* @param row Row to get the source for
* @returns Source cell where the data is coming from
*/
get originalColumns(): readonly Column[] {
return this.columns;
sourceForCell(column: ModelIndex, row: ModelIndex): GridCell {
return { column, row };
}

abstract get initialMovedColumns(): readonly MoveOperation[];
/**
* Retrieve the range of columns to clear filters on for a given column.
* @param column Column to get the range of filters to clear.
* @returns Axis range of the column filters to clear, or `null` if this should not have a clear filter option.
*/
getClearFilterRange(column: ModelIndex): BoundedAxisRange | null {
if (this.isFilterable(column)) {
return [column, column];
}
return null;
}

/** List of column movements defined by the model. Used as initial movements for IrisGrid */
get initialMovedColumns(): readonly MoveOperation[] {
return EMPTY_ARRAY;
}

/** List of row movements defined by the model. Used as initial movements for IrisGrid */
abstract get initialMovedRows(): readonly MoveOperation[];
get initialMovedRows(): readonly MoveOperation[] {
return EMPTY_ARRAY;
}

/** List of column header groups defined by the model */
abstract get initialColumnHeaderGroups(): readonly ColumnHeaderGroup[];

/**
* Retrieve the grouped columns for this model
* @returns The columns that are groupe
*/
abstract get groupedColumns(): readonly Column[];
get initialColumnHeaderGroups(): readonly ColumnHeaderGroup[] {
return EMPTY_ARRAY;
}

/**
* @param column The model column index
Expand Down
6 changes: 6 additions & 0 deletions packages/iris-grid/src/IrisGridProxyModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,12 @@ class IrisGridProxyModel extends IrisGridModel {
return this.model.groupedColumns;
}

sourceForCell: IrisGridModel['sourceForCell'] = (...args) =>
this.model.sourceForCell(...args);

getClearFilterRange: IrisGridModel['getClearFilterRange'] = (...args) =>
this.model.getClearFilterRange(...args);

get description(): string {
return this.model.description;
}
Expand Down
Loading
Loading