Pivot

The cross-tabulation engine on its own: a flat sequence in, two trees of headings and the values where they cross out, with no markup. Give PivotBuilder fields for the rows, fields for the columns and one or more measures, and it works out every heading and every cell.

Build It, Then Render It Yourself

Build returns a PivotTable: two trees of headings, the measures, and GetValue to read where a row and a column cross. The table below is drawn in this page from RowLeaves, ColumnLeaves and GetValue — nothing else. Values are worked out the first time they are asked for and then kept, so reading the same cell twice costs nothing the second time.

A Total Is Worked Out From Every Item Under It

Items are bucketed in one pass and each bucket keeps its items, so a total re-runs the aggregate over everything beneath it rather than adding up the cells it covers. For a Sum the two answers agree and nobody notices. For an Average they do not, and an average of averages is wrong in a way that is invisible until someone checks. The two columns below show the difference on the same data.

Label Formats, It Does Not Group

The grouping and the sorting still use the real value. Group on the month number and label it with the month name, and the headings come out Jan, Feb, Mar. Group on the name and they come out Apr, Aug, Dec.

Drilling Down

GetItems returns the source items behind a cell, which is what a drill-down shows. A total's items are every item under it, not the cells it sums — which is the same mechanism that makes a custom aggregate come out right on a total.

LeafIndex and DataIndex Are Not the Same Sequence

Totals are drawn between the data, so the drawn order and the data order diverge. Use LeafIndex to place a cell and DataIndex to address a bucket. A total reports DataIndex of -1.