Skip to content

Commit 013991a

Browse files
author
Dylan Huang
committed
Enhance pivot functionality by adding support for column totals using various aggregation methods (avg, count) and updating tests to validate these changes.
1 parent 8a86827 commit 013991a

2 files changed

Lines changed: 69 additions & 1 deletion

File tree

vite-app/src/util/pivot.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,55 @@ describe('computePivot', () => {
187187
expect(res.grandTotal).toBe(210) // 10 + 200
188188
})
189189

190+
it('column totals use same aggregation method as cells', () => {
191+
const res = computePivot<Row>({
192+
data: rows,
193+
rowFields: ['region'],
194+
columnFields: ['product'],
195+
valueField: 'amount',
196+
aggregator: 'avg',
197+
})
198+
199+
// Row totals should sum the cell values (for display purposes)
200+
expect(res.rowTotals['East']).toBe(105) // (10 + 200) / 2 = 105
201+
expect(res.rowTotals['West']).toBe(105) // (90 + 120) / 2 = 105
202+
203+
// Column totals should use the same aggregation method (avg) over all records in that column
204+
// Gadget column: values [90, 10] -> avg = 50
205+
expect(res.colTotals['Gadget']).toBe(50)
206+
// Widget column: values [120, 200] -> avg = 160
207+
expect(res.colTotals['Widget']).toBe(160)
208+
209+
// Grand total should also use avg over all records
210+
expect(res.grandTotal).toBe(105) // (90 + 10 + 120 + 200) / 4 = 105
211+
})
212+
213+
it('column totals with count aggregator', () => {
214+
const res = computePivot<Row>({
215+
data: rows,
216+
rowFields: ['region'],
217+
columnFields: ['product'],
218+
aggregator: 'count', // No valueField, just count records
219+
})
220+
221+
// Each cell should count records
222+
expect(res.cells['East']['Gadget'].value).toBe(2) // 2 records
223+
expect(res.cells['East']['Widget'].value).toBe(1) // 1 record
224+
expect(res.cells['West']['Gadget'].value).toBe(1) // 1 record
225+
expect(res.cells['West']['Widget'].value).toBe(1) // 1 record
226+
227+
// Row totals should sum the counts
228+
expect(res.rowTotals['East']).toBe(3) // 2 + 1
229+
expect(res.rowTotals['West']).toBe(2) // 1 + 1
230+
231+
// Column totals should count total records in each column
232+
expect(res.colTotals['Gadget']).toBe(3) // 2 + 1 = 3 records
233+
expect(res.colTotals['Widget']).toBe(2) // 1 + 1 = 2 records
234+
235+
// Grand total should count all records
236+
expect(res.grandTotal).toBe(5) // Total records
237+
})
238+
190239
it('supports custom aggregator', () => {
191240
const maxAgg: Aggregator<Row> = (values) =>
192241
values.length ? Math.max(...values) : 0

vite-app/src/util/pivot.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,10 +316,29 @@ export function computePivot<T extends Record<string, unknown>>({
316316
const value = aggregate(values, records, aggregator);
317317
cells[rKey][cKey] = { value, records };
318318
rowTotals[rKey] += value;
319-
colTotals[cKey] += value;
320319
}
321320
}
322321

322+
// Calculate column totals using the same aggregation method
323+
for (const cKey of Object.keys(colTotals)) {
324+
const columnRecords: T[] = [];
325+
const columnValues: number[] = [];
326+
327+
for (const rKey of Object.keys(cellRecords)) {
328+
if (cellRecords[rKey][cKey]) {
329+
columnRecords.push(...cellRecords[rKey][cKey]);
330+
if (valueField != null) {
331+
for (const rec of cellRecords[rKey][cKey]) {
332+
const v = getNumber(rec[valueField]);
333+
if (v != null) columnValues.push(v);
334+
}
335+
}
336+
}
337+
}
338+
339+
colTotals[cKey] = aggregate(columnValues, columnRecords, aggregator);
340+
}
341+
323342
// Grand total should follow the same aggregation semantics over the entire dataset
324343
// rather than summing per-row/per-column aggregates (which can be incorrect for
325344
// non-additive aggregations like "avg").

0 commit comments

Comments
 (0)