From f8655b4721722e75085b969d01ed1fc0aadd7684 Mon Sep 17 00:00:00 2001 From: Chris Matzenbach Date: Tue, 28 Jul 2026 14:08:30 -0500 Subject: [PATCH 01/10] Update sorting func and gradient color math to support bigints --- .../src/pivot-table/PivotedStatTable.tsx | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx b/ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx index c2c4da1e1..a4eacfe7d 100644 --- a/ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx +++ b/ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx @@ -1,8 +1,8 @@ -// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import { useMemo, useState, useCallback, useEffect, useRef } from 'react'; -import type { ColumnDef, OnChangeFn, SortingState } from '@tanstack/react-table'; +import type { ColumnDef, OnChangeFn, SortingFn, SortingState } from '@tanstack/react-table'; import { GroupedDataTable } from './GroupedDataTable'; import { cn } from '@quent/utils'; import type { AggMode, PivotedRow, HoveredStatInfo, PivotedStatTableSchema } from './types'; @@ -36,6 +36,16 @@ import { const HIGHLIGHT_WASH = 'inset 0 0 0 999px hsl(var(--primary) / 0.07)'; +const numericSortingFn: SortingFn = (rowA, rowB, columnId) => { + const a = rowA.getValue(columnId); + const b = rowB.getValue(columnId); + if (a === b) return 0; + if (a == null) return 1; + if (b == null) return -1; + if (typeof a === typeof b) return a < b ? -1 : 1; + return Number(a) < Number(b) ? -1 : 1; +}; + function DataHeader({ stat, sortInfo, onSort, className, style }: DataHeaderProps) { const { dnd, interaction, derived } = usePivotTableRenderContext(); const hoveredStatName = interaction.hoveredStat?.name; @@ -382,8 +392,10 @@ export function PivotedStatTable({ for (const row of visiblePivotedRows) { const v = getSortValue(row, stat, isAggregating, aggMode); if (v !== null) { - if (v < min) min = v; - if (v > max) max = v; + // Convert to number for gradient color math — precision loss is acceptable here + const n = Number(v); + if (n < min) min = n; + if (n > max) max = n; } } if (min !== Infinity) ranges.set(stat, { min, max }); @@ -566,6 +578,7 @@ export function PivotedStatTable({ header: stat, enableSorting: true, sortUndefined: 'last', + sortingFn: numericSortingFn, accessorFn: (row: PivotedRow) => getSortValue(row, stat, isAggregating, aggMode) ?? undefined, })); return [...groupCols, ...statCols]; From 289e0561be2484f780cd5837200fbbc8ca1b0c07 Mon Sep 17 00:00:00 2001 From: Chris Matzenbach Date: Tue, 28 Jul 2026 14:09:09 -0500 Subject: [PATCH 02/10] Update tests --- .../components/src/pivot-table/buildPivotedRows.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/packages/@quent/components/src/pivot-table/buildPivotedRows.test.ts b/ui/packages/@quent/components/src/pivot-table/buildPivotedRows.test.ts index 7a29806d1..ccf9bbd4e 100644 --- a/ui/packages/@quent/components/src/pivot-table/buildPivotedRows.test.ts +++ b/ui/packages/@quent/components/src/pivot-table/buildPivotedRows.test.ts @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import { describe, it, expect } from 'vitest'; @@ -163,10 +163,10 @@ describe('buildPivotedRows row clustering', () => { ]; const out = buildPivotedRows(rows, [brandIdx, fuelIdx], true); const agg = out[0]?.aggs.get('output_rows'); - expect(agg?.sum).toBe(4000); + expect(agg?.sum).toBe(4000n); expect(agg?.mean).toBe(2000); - expect(agg?.min).toBe(1000); - expect(agg?.max).toBe(3000); + expect(agg?.min).toBe(1000n); + expect(agg?.max).toBe(3000n); expect(agg?.count).toBe(2); expect(agg?.isNumeric).toBe(true); }); From c627b04d2fa868dffd47be73424db2b556578d90 Mon Sep 17 00:00:00 2001 From: Chris Matzenbach Date: Tue, 28 Jul 2026 14:10:04 -0500 Subject: [PATCH 03/10] Add bigint type support and bigint math support --- .../components/src/pivot-table/types.ts | 8 ++--- .../components/src/pivot-table/utils.ts | 36 ++++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/ui/packages/@quent/components/src/pivot-table/types.ts b/ui/packages/@quent/components/src/pivot-table/types.ts index bdac9a9c5..7049085f6 100644 --- a/ui/packages/@quent/components/src/pivot-table/types.ts +++ b/ui/packages/@quent/components/src/pivot-table/types.ts @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import type { StatValue, ContinuousPaletteName } from '@quent/utils'; @@ -137,10 +137,10 @@ export interface GroupKeyEntry { } export interface PivotedRowAgg { - sum: number | null; + sum: number | bigint | null; mean: number | null; - min: number | null; - max: number | null; + min: number | bigint | null; + max: number | bigint | null; stdev: number | null; count: number; isNumeric: boolean; diff --git a/ui/packages/@quent/components/src/pivot-table/utils.ts b/ui/packages/@quent/components/src/pivot-table/utils.ts index 361d18374..9fa471146 100644 --- a/ui/packages/@quent/components/src/pivot-table/utils.ts +++ b/ui/packages/@quent/components/src/pivot-table/utils.ts @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import { inferFieldFormatter, isNumericValue } from '@quent/utils'; @@ -56,14 +56,17 @@ export function formatStatValue(value: StatValue, statName: string): string { // --- color gradient --- export function gradientBg( - value: number, - min: number, - max: number, + value: number | bigint, + min: number | bigint, + max: number | bigint, palette: ContinuousPaletteName = 'blue', darkMode = false ): string | undefined { - if (min === max) return undefined; - const t = (value - min) / (max - min); + const vn = Number(value); + const mn = Number(min); + const mx = Number(max); + if (mn === mx) return undefined; + const t = (vn - mn) / (mx - mn); return continuousColor(t, palette, darkMode); } @@ -169,11 +172,11 @@ export function getSortValue( stat: string, isAgg: boolean, aggMode: AggMode -): number | null { +): number | bigint | null { if (!isAgg) { const v = row.values.get(stat); if (v === undefined) return null; - return isNumericValue(v) ? Number(v) : null; + return isNumericValue(v) ? v : null; } const agg = row.aggs.get(stat); if (!agg || !agg.isNumeric) return null; @@ -274,24 +277,25 @@ export function buildPivotedRows( : [...bucket.nums, ...bucket.bigints.map(Number)]; const hasNum = allNums.length > 0; - let sum: number | null = null; - let min: number | null = null; - let max: number | null = null; + let sum: number | bigint | null = null; + let min: number | bigint | null = null; + let max: number | bigint | null = null; let mean: number | null = null; let stdev: number | null = null; if (hasNum) { if (onlyBigints) { - // Use bigint arithmetic for sum/min/max to avoid precision loss - sum = Number(bucket.bigints.reduce((a, b) => a + b, 0n)); - min = Number(bucket.bigints.reduce((a, b) => (a < b ? a : b))); - max = Number(bucket.bigints.reduce((a, b) => (a > b ? a : b))); + // Use bigint arithmetic for sum/min/max — lossless, no Number() conversion + sum = bucket.bigints.reduce((a, b) => a + b, 0n); + min = bucket.bigints.reduce((a, b) => (a < b ? a : b)); + max = bucket.bigints.reduce((a, b) => (a > b ? a : b)); + mean = Number(sum) / bucket.bigints.length; } else { sum = allNums.reduce((a, b) => a + b, 0); min = Math.min(...allNums); max = Math.max(...allNums); + mean = sum / allNums.length; } - mean = sum / allNums.length; if (allNums.length > 1) { const variance = allNums.reduce((acc, v) => acc + (v - mean!) ** 2, 0) / (allNums.length - 1); From 3cba8dcf82fd0ddb8f018f67cedae4b730ed27fe Mon Sep 17 00:00:00 2001 From: Chris Matzenbach Date: Wed, 29 Jul 2026 09:49:28 -0500 Subject: [PATCH 04/10] cleanup --- ui/packages/@quent/components/src/pivot-table/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/packages/@quent/components/src/pivot-table/utils.ts b/ui/packages/@quent/components/src/pivot-table/utils.ts index 9fa471146..0ccae3f0c 100644 --- a/ui/packages/@quent/components/src/pivot-table/utils.ts +++ b/ui/packages/@quent/components/src/pivot-table/utils.ts @@ -285,7 +285,7 @@ export function buildPivotedRows( if (hasNum) { if (onlyBigints) { - // Use bigint arithmetic for sum/min/max — lossless, no Number() conversion + // Use bigint arithmetic for sum/min/max sum = bucket.bigints.reduce((a, b) => a + b, 0n); min = bucket.bigints.reduce((a, b) => (a < b ? a : b)); max = bucket.bigints.reduce((a, b) => (a > b ? a : b)); From 47d59c62bcb698c12fa4f885a18fea1a3bc8cc66 Mon Sep 17 00:00:00 2001 From: Chris Matzenbach Date: Wed, 29 Jul 2026 11:50:25 -0500 Subject: [PATCH 05/10] Don't coerce - < can compare number to bigint natively --- .../@quent/components/src/pivot-table/PivotedStatTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx b/ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx index a4eacfe7d..8d00d4149 100644 --- a/ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx +++ b/ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx @@ -43,7 +43,7 @@ const numericSortingFn: SortingFn = (rowA, rowB, columnId) => { if (a == null) return 1; if (b == null) return -1; if (typeof a === typeof b) return a < b ? -1 : 1; - return Number(a) < Number(b) ? -1 : 1; + return (a as number) < (b as number) ? -1 : 1; }; function DataHeader({ stat, sortInfo, onSort, className, style }: DataHeaderProps) { From b2b4cc3bf6cde77b1b60949b76483d2161dc5e4c Mon Sep 17 00:00:00 2001 From: Chris Matzenbach Date: Wed, 29 Jul 2026 13:55:17 -0500 Subject: [PATCH 06/10] Move utility fn to utils --- .../components/src/pivot-table/PivotedStatTable.tsx | 11 +---------- .../@quent/components/src/pivot-table/utils.ts | 10 ++++++++++ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx b/ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx index 8d00d4149..03ae644c0 100644 --- a/ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx +++ b/ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx @@ -22,6 +22,7 @@ import { getSortValue, gradientBg, itemHasId, + numericSortingFn, } from './utils'; import type { GroupedDataTableGroupRenderMode, @@ -36,16 +37,6 @@ import { const HIGHLIGHT_WASH = 'inset 0 0 0 999px hsl(var(--primary) / 0.07)'; -const numericSortingFn: SortingFn = (rowA, rowB, columnId) => { - const a = rowA.getValue(columnId); - const b = rowB.getValue(columnId); - if (a === b) return 0; - if (a == null) return 1; - if (b == null) return -1; - if (typeof a === typeof b) return a < b ? -1 : 1; - return (a as number) < (b as number) ? -1 : 1; -}; - function DataHeader({ stat, sortInfo, onSort, className, style }: DataHeaderProps) { const { dnd, interaction, derived } = usePivotTableRenderContext(); const hoveredStatName = interaction.hoveredStat?.name; diff --git a/ui/packages/@quent/components/src/pivot-table/utils.ts b/ui/packages/@quent/components/src/pivot-table/utils.ts index 0ccae3f0c..be2fb5446 100644 --- a/ui/packages/@quent/components/src/pivot-table/utils.ts +++ b/ui/packages/@quent/components/src/pivot-table/utils.ts @@ -32,6 +32,16 @@ export function formatNumericStat(n: number | bigint | null, statName: string): return inferFieldFormatter(statName)(n); } +export function numericSortingFn(rowA, rowB, columnId): SortingFn { + const a = rowA.getValue(columnId); + const b = rowB.getValue(columnId); + if (a === b) return 0; + if (a == null) return 1; + if (b == null) return -1; + if (typeof a === typeof b) return a < b ? -1 : 1; + return (a as number) < (b as number) ? -1 : 1; +}; + /** * Returns true when any id in `items` is present in `target`. Equivalent to * `[...items].some(id => target.has(id))` but without allocating an From 9de474914cdfcb7791c6194e7b1003dfa14e4d6e Mon Sep 17 00:00:00 2001 From: Chris Matzenbach Date: Wed, 29 Jul 2026 14:00:19 -0500 Subject: [PATCH 07/10] linting --- .../@quent/components/src/pivot-table/PivotedStatTable.tsx | 2 +- ui/packages/@quent/components/src/pivot-table/utils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx b/ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx index 03ae644c0..17fa3636b 100644 --- a/ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx +++ b/ui/packages/@quent/components/src/pivot-table/PivotedStatTable.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { useMemo, useState, useCallback, useEffect, useRef } from 'react'; -import type { ColumnDef, OnChangeFn, SortingFn, SortingState } from '@tanstack/react-table'; +import type { ColumnDef, OnChangeFn, SortingState } from '@tanstack/react-table'; import { GroupedDataTable } from './GroupedDataTable'; import { cn } from '@quent/utils'; import type { AggMode, PivotedRow, HoveredStatInfo, PivotedStatTableSchema } from './types'; diff --git a/ui/packages/@quent/components/src/pivot-table/utils.ts b/ui/packages/@quent/components/src/pivot-table/utils.ts index be2fb5446..98914bdee 100644 --- a/ui/packages/@quent/components/src/pivot-table/utils.ts +++ b/ui/packages/@quent/components/src/pivot-table/utils.ts @@ -40,7 +40,7 @@ export function numericSortingFn(rowA, rowB, columnId): SortingFn { if (b == null) return -1; if (typeof a === typeof b) return a < b ? -1 : 1; return (a as number) < (b as number) ? -1 : 1; -}; +} /** * Returns true when any id in `items` is present in `target`. Equivalent to From 10b4eb9c1d2ddfeb7e7a08151b1a8cadc8ec7ef8 Mon Sep 17 00:00:00 2001 From: Chris Matzenbach Date: Wed, 29 Jul 2026 14:01:41 -0500 Subject: [PATCH 08/10] more linting --- ui/packages/@quent/components/src/pivot-table/utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/packages/@quent/components/src/pivot-table/utils.ts b/ui/packages/@quent/components/src/pivot-table/utils.ts index 98914bdee..76ae78077 100644 --- a/ui/packages/@quent/components/src/pivot-table/utils.ts +++ b/ui/packages/@quent/components/src/pivot-table/utils.ts @@ -4,6 +4,7 @@ import { inferFieldFormatter, isNumericValue } from '@quent/utils'; import type { StatValue, ContinuousPaletteName } from '@quent/utils'; import { continuousColor } from '@quent/utils'; +import type { SortingFn } from '@tanstack/react-table'; // Re-exported for consumers that still import it from here; defined in `@quent/utils` export { isNumericValue }; From aa3e0f54850c45a8a1ef945e46b380ae0d37fcd2 Mon Sep 17 00:00:00 2001 From: Chris Matzenbach Date: Wed, 29 Jul 2026 14:06:07 -0500 Subject: [PATCH 09/10] Convert back to arrow fn - tanstack expects a SortingFn return type --- ui/packages/@quent/components/src/pivot-table/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/packages/@quent/components/src/pivot-table/utils.ts b/ui/packages/@quent/components/src/pivot-table/utils.ts index 76ae78077..4bcfe77fb 100644 --- a/ui/packages/@quent/components/src/pivot-table/utils.ts +++ b/ui/packages/@quent/components/src/pivot-table/utils.ts @@ -33,7 +33,7 @@ export function formatNumericStat(n: number | bigint | null, statName: string): return inferFieldFormatter(statName)(n); } -export function numericSortingFn(rowA, rowB, columnId): SortingFn { +export const numericSortingFn: SortingFn = (rowA, rowB, columnId) => { const a = rowA.getValue(columnId); const b = rowB.getValue(columnId); if (a === b) return 0; From 52504f1fa09027cf3a6e5977ba9778da5de26abb Mon Sep 17 00:00:00 2001 From: Chris Matzenbach Date: Wed, 29 Jul 2026 14:08:52 -0500 Subject: [PATCH 10/10] more linting --- ui/packages/@quent/components/src/pivot-table/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/packages/@quent/components/src/pivot-table/utils.ts b/ui/packages/@quent/components/src/pivot-table/utils.ts index 4bcfe77fb..d415f8ffc 100644 --- a/ui/packages/@quent/components/src/pivot-table/utils.ts +++ b/ui/packages/@quent/components/src/pivot-table/utils.ts @@ -41,7 +41,7 @@ export const numericSortingFn: SortingFn = (rowA, rowB, columnId) => if (b == null) return -1; if (typeof a === typeof b) return a < b ? -1 : 1; return (a as number) < (b as number) ? -1 : 1; -} +}; /** * Returns true when any id in `items` is present in `target`. Equivalent to