Skip to content

Commit d0c8091

Browse files
committed
Totals row snapshot and cell color
1 parent 0b2ebd3 commit d0c8091

1 file changed

Lines changed: 147 additions & 20 deletions

File tree

packages/iris-grid/src/IrisGridSimplePivotModel.ts

Lines changed: 147 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,22 @@
22
import memoize from 'memoize-one';
33
import type { dh as DhType, Iterator } from '@deephaven/jsapi-types';
44
import Log from '@deephaven/log';
5-
import { Formatter } from '@deephaven/jsapi-utils';
5+
import { Formatter, TableUtils } from '@deephaven/jsapi-utils';
66
import {
7+
assertNotNull,
78
EventShimCustomEvent,
89
PromiseUtils,
910
type CancelablePromise,
1011
} from '@deephaven/utils';
11-
import { type ModelIndex } from '@deephaven/grid';
12+
import { GridRange, type ModelIndex } from '@deephaven/grid';
1213
import { type ColumnName, type UITotalsTableConfig } from './CommonTypes';
1314
import type { DisplayColumn } from './IrisGridModel';
1415
import ColumnHeaderGroup from './ColumnHeaderGroup';
1516
import IrisGridModel from './IrisGridModel';
1617
import IrisGridTableModel from './IrisGridTableModel';
1718
import { isIrisGridTableModelTemplate } from './IrisGridTableModelTemplate';
19+
import IrisGridUtils from './IrisGridUtils';
20+
import type { IrisGridThemeType } from './IrisGridTheme';
1821

1922
const log = Log.module('IrisGridSimplePivotModel');
2023

@@ -37,8 +40,15 @@ export type KeyColumnArray = (readonly [string, string])[];
3740

3841
export type SimplePivotColumnMap = ReadonlyMap<string, string>;
3942

43+
const GRAND_TOTAL_VALUE = 'Grand Total';
44+
4045
// TODO:
41-
// - textSnapshot, snapshot for the totals row
46+
// - totals row formatting [DONE]
47+
// - totals row: copy cell unformatted
48+
// - copy selection with headers - fix column mapping, fix case with only totals row selected
49+
// - totals column move to back
50+
// - col based operations
51+
// - flags to hide unsupported table options - go to row, filters, search, organize columns, etc
4252

4353
/**
4454
* Model which proxies calls to IrisGridModel.
@@ -284,6 +294,7 @@ class IrisGridSimplePivotModel extends IrisGridModel {
284294
}
285295

286296
sourceColumn(x: ModelIndex, _: ModelIndex): DhType.Column {
297+
// TODO:
287298
return this.columns[x]; // - this.schema.rowColNames.length];
288299
}
289300

@@ -297,27 +308,32 @@ class IrisGridSimplePivotModel extends IrisGridModel {
297308
return undefined;
298309
}
299310

311+
if (!isIrisGridTableModelTemplate(this.model)) {
312+
throw new Error('Invalid model, textValueForCell not available');
313+
}
314+
300315
const column = this.sourceColumn(x, y);
316+
301317
// TODO:
302-
// const hasCustomColumnFormat = this.getCachedCustomColumnFormatFlag(
303-
// this.formatter,
304-
// column.name,
305-
// column.type
306-
// );
307-
// let formatOverride;
308-
// if (!hasCustomColumnFormat) {
309-
// const formatForCell = this.formatForCell(x, y);
310-
// if (formatForCell?.formatString != null) {
311-
// formatOverride = formatForCell;
312-
// }
313-
// }
314-
const text = this.displayString(
318+
const hasCustomColumnFormat = this.model.getCachedCustomColumnFormatFlag(
319+
this.formatter,
320+
column.name,
321+
column.type
322+
);
323+
let formatOverride;
324+
if (!hasCustomColumnFormat) {
325+
const formatForCell = this.formatForCell(x, y);
326+
if (formatForCell?.formatString != null) {
327+
formatOverride = formatForCell;
328+
}
329+
}
330+
const text = this.model.displayString(
315331
value,
316332
column.type,
317-
column.name
318-
// formatOverride
333+
column.name,
334+
formatOverride
319335
);
320-
// this.cacheFormattedValue(x, y, text);
336+
this.model.cacheFormattedValue(x, y, text);
321337
return text;
322338
}
323339

@@ -326,7 +342,7 @@ class IrisGridSimplePivotModel extends IrisGridModel {
326342
if (x >= this.schema.rowColNames.length) {
327343
return this.textValueForCell(x, y) ?? '';
328344
}
329-
return x === 0 ? 'Grand Total' : '';
345+
return x === 0 ? GRAND_TOTAL_VALUE : '';
330346
}
331347
return this.model.textForCell(x, y);
332348
}
@@ -630,6 +646,117 @@ class IrisGridSimplePivotModel extends IrisGridModel {
630646
});
631647
}
632648

649+
async snapshot(
650+
ranges: readonly GridRange[],
651+
includeHeaders = false,
652+
formatValue: (value: unknown, column: DhType.Column) => unknown = value =>
653+
value,
654+
consolidateRanges = true
655+
): Promise<unknown[][]> {
656+
if (!isIrisGridTableModelTemplate(this.model)) {
657+
throw new Error('Invalid model, snapshot not available');
658+
}
659+
660+
const consolidated = consolidateRanges
661+
? GridRange.consolidate(ranges)
662+
: ranges;
663+
if (!IrisGridUtils.isValidSnapshotRanges(consolidated)) {
664+
throw new Error(`Invalid snapshot ranges ${ranges}`);
665+
}
666+
667+
let hasTotals = false;
668+
const tableRanges: GridRange[] = [];
669+
670+
const tableSize = this.model.table.size;
671+
672+
for (let i = 0; i < consolidated.length; i += 1) {
673+
const range = consolidated[i];
674+
assertNotNull(range.endRow);
675+
assertNotNull(range.startRow);
676+
// Separate out the range that is part of the actual table
677+
if (range.endRow === tableSize) {
678+
hasTotals = true;
679+
if (range.startRow < tableSize) {
680+
tableRanges.push(
681+
new GridRange(
682+
range.startColumn,
683+
range.startRow,
684+
range.endColumn,
685+
range.endRow - 1
686+
)
687+
);
688+
}
689+
} else {
690+
tableRanges.push(range);
691+
}
692+
}
693+
const result =
694+
tableRanges.length === 0
695+
? []
696+
: await this.model.snapshot(
697+
tableRanges,
698+
includeHeaders,
699+
formatValue,
700+
consolidateRanges
701+
);
702+
703+
const columns = IrisGridUtils.columnsFromRanges(consolidated, this.columns);
704+
705+
if (hasTotals) {
706+
const rowData = columns.map(column => {
707+
const index = this.getColumnIndexByName(column.name);
708+
assertNotNull(index);
709+
return index === 0
710+
? GRAND_TOTAL_VALUE
711+
: formatValue(this.valueForCell(index, tableSize), column);
712+
});
713+
result.push(rowData);
714+
}
715+
716+
return result;
717+
}
718+
719+
colorForCell(x: ModelIndex, y: ModelIndex, theme: IrisGridThemeType): string {
720+
if (!isIrisGridTableModelTemplate(this.model)) {
721+
throw new Error('Invalid model, colorForCell not available');
722+
}
723+
724+
if (this.schema.hasTotals && y === this.rowCount - 1) {
725+
if (x >= this.schema.rowColNames.length) {
726+
const value = this.valueForCell(x, y);
727+
if (value == null || value === '') {
728+
assertNotNull(theme.nullStringColor);
729+
return theme.nullStringColor;
730+
}
731+
732+
// Format based on the value/type of the cell
733+
if (value != null) {
734+
const column = this.sourceColumn(x, y);
735+
if (TableUtils.isDateType(column.type) || column.name === 'Date') {
736+
assertNotNull(theme.dateColor);
737+
return theme.dateColor;
738+
}
739+
if (TableUtils.isNumberType(column.type)) {
740+
if ((value as number) > 0) {
741+
assertNotNull(theme.positiveNumberColor);
742+
return theme.positiveNumberColor;
743+
}
744+
if ((value as number) < 0) {
745+
assertNotNull(theme.negativeNumberColor);
746+
return theme.negativeNumberColor;
747+
}
748+
assertNotNull(theme.zeroNumberColor);
749+
return theme.zeroNumberColor;
750+
}
751+
}
752+
}
753+
754+
return theme.textColor;
755+
}
756+
757+
return this.model.colorForCell(x, y, theme);
758+
}
759+
633760
startListening(): void {
634761
super.startListening();
635762

0 commit comments

Comments
 (0)