diff --git a/web-common/src/features/dashboards/pivot/pivot-data-store.ts b/web-common/src/features/dashboards/pivot/pivot-data-store.ts index 142f293fbac..32566346695 100644 --- a/web-common/src/features/dashboards/pivot/pivot-data-store.ts +++ b/web-common/src/features/dashboards/pivot/pivot-data-store.ts @@ -54,6 +54,7 @@ import { getTimeGrainFromDimension, getTotalColumnCount, isTimeDimension, + sortNumericDimensionAxes, splitPivotChips, } from "./pivot-utils"; import { @@ -271,6 +272,9 @@ export function createPivotDataStore( if (columnDimensionAxes?.error && columnDimensionAxes?.error.length) { return columnSet(getErrorState(columnDimensionAxes.error)); } + const columnDimensionAxesData = sortNumericDimensionAxes( + columnDimensionAxes?.data, + ); const anchorDimension = rowDimensionNames[0]; const rowPage = config.pivot.rowPage; @@ -293,7 +297,7 @@ export function createPivotDataStore( } = getSortForAccessor( anchorDimension, config, - columnDimensionAxes?.data, + columnDimensionAxesData, ); const { sortFilteredMeasureBody, isMeasureSortAccessor, sortAccessor } = @@ -369,7 +373,7 @@ export function createPivotDataStore( totalsRowQuery = getTotalsRowQuery( ctx, config, - columnDimensionAxes?.data, + columnDimensionAxesData, ); } @@ -390,7 +394,7 @@ export function createPivotDataStore( ) { const skeletonTotalsRowData = getTotalsRowSkeleton( config, - columnDimensionAxes?.data, + columnDimensionAxesData, ); return axesSet({ isFetching: true, @@ -437,7 +441,7 @@ export function createPivotDataStore( const totalsRowData = getTotalsRow( config, - columnDimensionAxes?.data, + columnDimensionAxesData, totalsRowResponse?.data?.data, globalTotalsResponse?.data?.data, ); @@ -495,7 +499,7 @@ export function createPivotDataStore( ) { const slicedAxesDataForDef = sliceColumnAxesDataForDef( config, - columnDimensionAxes?.data, + columnDimensionAxesData, totalsRowData, ); @@ -508,7 +512,7 @@ export function createPivotDataStore( tableCellQuery = createTableCellQuery( ctx, config, - columnDimensionAxes?.data, + columnDimensionAxesData, totalsRowData, rowDimensionValues, isFlat ? NUM_ROWS_PER_PAGE.toString() : "5000", @@ -517,7 +521,7 @@ export function createPivotDataStore( } else { columnDef = getColumnDefForPivot( config, - columnDimensionAxes?.data, + columnDimensionAxesData, totalsRowData, ); } @@ -602,7 +606,7 @@ export function createPivotDataStore( config, anchorDimension, rowDimensionValues || [], - columnDimensionAxes?.data || {}, + columnDimensionAxesData, pivotSkeleton as PivotDataRow[], cellData, ); @@ -614,7 +618,7 @@ export function createPivotDataStore( ctx, config, pivotData, - columnDimensionAxes?.data, + columnDimensionAxesData, totalsRowData, ); @@ -636,7 +640,7 @@ export function createPivotDataStore( config, pivotData, rowDimensionNames, - columnDimensionAxes?.data || {}, + columnDimensionAxesData, expandedRowMeasureValues, ); @@ -668,7 +672,7 @@ export function createPivotDataStore( config, activeCell.rowId, activeCell.columnId, - columnDimensionAxes?.data, + columnDimensionAxesData, tableDataExpanded, ); } diff --git a/web-common/src/features/dashboards/pivot/pivot-utils.ts b/web-common/src/features/dashboards/pivot/pivot-utils.ts index 21c29083b78..7a0870c6cab 100644 --- a/web-common/src/features/dashboards/pivot/pivot-utils.ts +++ b/web-common/src/features/dashboards/pivot/pivot-utils.ts @@ -178,6 +178,49 @@ export function createIndexMap(arr: T[]): Map { return indexMap; } +function isNumericAxisValue(value: unknown): boolean { + if (value === null || value === undefined) return false; + if (typeof value === "string" && value.trim() === "") return false; + + const numericValue = Number(value); + return Number.isFinite(numericValue); +} + +/** + * Sort axis values when all non-null values are numeric-like. + * This keeps dimensions such as month offsets in natural numeric order. + */ +export function sortNumericDimensionAxisValues(values: T[]): T[] { + const definedValues = values.filter( + (value): value is T => value !== null && value !== undefined, + ); + + if (!definedValues.length) return values; + if (!definedValues.every((value) => isNumericAxisValue(value))) return values; + + return [...values].sort((a, b) => { + const aIsNumeric = isNumericAxisValue(a); + const bIsNumeric = isNumericAxisValue(b); + + if (!aIsNumeric && !bIsNumeric) return 0; + if (!aIsNumeric) return 1; + if (!bIsNumeric) return -1; + + return Number(a) - Number(b); + }); +} + +export function sortNumericDimensionAxes( + axes: Record = {}, +): Record { + return Object.fromEntries( + Object.entries(axes).map(([dimension, values]) => [ + dimension, + sortNumericDimensionAxisValues(values), + ]), + ); +} + /** * Returns total number of columns for the table * excluding row and group totals columns diff --git a/web-common/src/features/dashboards/pivot/tests/pivot-utilts.test.ts b/web-common/src/features/dashboards/pivot/tests/pivot-utilts.test.ts index 0d38a89223c..bcf92f41075 100644 --- a/web-common/src/features/dashboards/pivot/tests/pivot-utilts.test.ts +++ b/web-common/src/features/dashboards/pivot/tests/pivot-utilts.test.ts @@ -1,4 +1,7 @@ -import { sortAcessors } from "@rilldata/web-common/features/dashboards/pivot/pivot-utils"; +import { + sortAcessors, + sortNumericDimensionAxisValues, +} from "@rilldata/web-common/features/dashboards/pivot/pivot-utils"; import { describe, expect, it } from "vitest"; describe("sortAcessors function", () => { @@ -26,3 +29,22 @@ describe("sortAcessors function", () => { expect(sortAcessors(input)).toEqual(expected); }); }); + +describe("sortNumericDimensionAxisValues", () => { + it("sorts numeric dimension values in ascending order", () => { + const input = ["5", "6", "0", "2"]; + const expected = ["0", "2", "5", "6"]; + expect(sortNumericDimensionAxisValues(input)).toEqual(expected); + }); + + it("sorts signed and decimal numeric values", () => { + const input = ["3.5", "-1", "0", "2"]; + const expected = ["-1", "0", "2", "3.5"]; + expect(sortNumericDimensionAxisValues(input)).toEqual(expected); + }); + + it("does not reorder non-numeric dimensions", () => { + const input = ["north", "south", "east"]; + expect(sortNumericDimensionAxisValues(input)).toEqual(input); + }); +});