Skip to content

PivotTables: label, value and Top 10 filters - #498

Open
prxnvt wants to merge 3 commits into
storytold:mainfrom
prxnvt:pr/pivot-more
Open

prxnvt wants to merge 3 commits into
storytold:mainfrom
prxnvt:pr/pivot-more

Conversation

@prxnvt

@prxnvt prxnvt commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #474. This branch is based on #474's two commits. Only the last commit belongs to this pull request.

Summary

This pull request adds Excel's label filters, value filters and Top 10 filters to the row and column fields of a PivotTable. Until now a PivotTable could only filter by selecting items (pivot.filter). The filters are stored in the model, evaluated when the report is computed, and round-tripped through XLSX as the <filters> element of the PivotTable definition.

Four commands are added:

  • pivot.labelFilter compares each item's caption with a value or a range: equals, begins with, ends with, contains, their negations, greater than, less than, between and not between. The text comparisons accept * and ? wildcards. Numbers compare as numbers, and case does not matter.
  • pivot.valueFilter compares each item's total of a value field with a number or a range. The value field defaults to the first.
  • pivot.top10 keeps the top or bottom items by a value field, counted as a number of items, a percent of the total or a sum.
  • pivot.clearFilter clears the filters of one field, or of every field when no field is given.

Sum of Sales by Region and Product, with a Top 10 filter that keeps the top product in each region. Subtotals and the grand total count only the items shown:

top10

A label filter on Region (contains "st") combined with a value filter on the Product column field (total of at least 200):

label-value

Changes

Model

  • New types: PivotCompare (the fourteen comparisons), PivotTopBy (items, percent, sum) and PivotRule. PivotRule is a label rule, a value rule or a Top 10 rule. The value and Top 10 rules name their value field by caption.
  • PivotFilter gains rule: Option<PivotRule>.
  • As in Excel when "Allow multiple filters per field" is off, a field has one kind of filter at a time. A rule replaces the field's item selection, and selecting items clears the rule.

Engine

  • Label rules are applied per record, together with the existing item selection.
  • Value and Top 10 rules are applied among the sibling items under each parent item, using each item's total. When they drop items, the records are aggregated again without those items, so subtotals and grand totals count only the items shown, as in Excel. The record paths for this second pass are kept only when a value or Top 10 rule is present.
  • Items whose total is not a number fail a value filter and are ranked last by Top 10.
  • Calculated fields can serve as the value field of a rule. The value computation is shared with the report body.
  • Rules on a field in the Filters area are ignored. Upstream's report filters and slicers are not affected; slicers apply to tables only.

Commands

  • The four new commands are listed above. Each refuses a field that is not in the rows or columns area, an unknown comparison, a range without value2, a text comparison in a value filter, a value field that is not in the Values area, and Top 10 counts outside the valid range (a whole number of at least 1, or a percent greater than 0 and at most 100).
  • A rule follows its field between rows and columns and is dropped when the field moves to the filters area.
  • Value and Top 10 rules follow a renamed value field and are removed with a removed value field.
  • Regrouping a field clears its label filter, whose captions no longer apply.
  • pivot.filter with selected: null keeps the field's rule.
  • pivot.list and pivot.fields report each rule in the parameters of the command that sets it.

XLSX

  • Written as <filters> after pivotTableStyleInfo, as the schema orders them.
  • Label filters are written with the caption* types and stringValue1 and stringValue2. Value filters are written with the value* types and iMeasureFld. Top 10 filters are written with the count, percent and sum types and a top10 element. Each filter carries the AutoFilter conditions that express it.
  • All three kinds are read back. Date filters, and filters that name a missing field or value field, are skipped; a date filter is skipped with a warning. The warning that label and value filters are not supported is removed.

UI

  • In the PivotTable Fields pane, the menu of a row or column field gains Top 10 Items and Bottom 10 Items, and every field's menu gains Clear Filter From "field".
  • The Filters area no longer lists a row or column field that has its own filter. This also affects the existing item selections on axis fields.
  • The pane's strings are not translated, so no i18n entries are needed.

Documentation

ROADMAP.md progress log and docs/target-app-parity.md (PivotTables row, inventory and change log).

Testing

  • New engine tests:
    • label_filters checks each kind of comparison, wildcards, bounds in either order, number captions, the interaction with item selection, reporting and clearing.
    • value_and_top10_filters covers value filters, Top 10 by items, percent and sum, another value field, renamed and removed value fields, Top 1 within each parent with its subtotal, a column field, moving a field between areas, and Clear Filter with a report filter in place.
    • filters_round_trip_and_refusals checks an XLSX round trip of a label filter and a bottom Top 10, the report after a refresh, and twelve refused parameter sets that leave the filters unchanged.
  • Updated engine test: hostile_params_never_panic covers the new commands with two more hostile parameter sets.
  • New XLSX tests:
    • label_value_and_top10_filters_round_trip checks the written XML and that the model compares equal after reading it back.
    • unsupported_and_hostile_pivot_filters checks that a date filter, a filter on a field that is not on an axis, and value filters with a missing value field or no number are skipped, and that a Top 10 without its top10 element takes Excel's defaults.
  • CI: fmt and clippy (-D warnings) pass. cargo test --workspace --no-fail-fast passes except for three tests that already fail on main: the French i18n coverage test and two semicolon CSV tests, which open pull requests already address.
  • Other checks: cargo xtask assets, layers, wasm, parity and keytips pass, with no regenerated changes.

Follow-ups

These are not part of this pull request.

  • Date filters (equals, before, after, between, and the relative periods such as this month or last quarter), which depend on today's date.
  • Allow multiple filters per field (a label filter and a value filter together), with multipleFieldFilters in XLSX.
  • Filter menus and dialogs in the sheet's field buttons and the Fields pane: checklist with search, Label Filters, Value Filters and Top 10.
  • Group Selection and Ungroup (manual groups), with discretePr round trip.
  • Time grouping, several date levels from one command, day spans and user-set date bounds.
  • Settings dialogs for Show Values As and Grouping.
  • Calculated items, Field Settings and PivotTable Options, a shared pivot cache, and dragging fields in the Fields pane.

Generative AI use: The code and descriptions in this pull request were written with the assistance of Claude Opus 5.5 (Claude Code). I directed the work, chose its scope, and reviewed and tested the changes.

… base item

pivot.valueSettings accepts every Show Values As calculation: % of,
% of Parent Row Total, % of Parent Column Total, % of Parent Total,
Difference From, % Difference From, % Running Total In, Rank Smallest
to Largest and Index, in addition to the existing % of Grand Total,
% of Column Total, % of Row Total, Running Total In and Rank Largest
to Smallest. The calculations that need one take a base field (a row
or column field) and a base item (an item label, "(previous)" or
"(next)"); Running Total and Rank now follow the base field instead of
always using the first row field. pivot.fields reports the base field
and item.

XLSX: written and read as showDataAs with baseField and baseItem, and
as the x14 pivotShowAs extension for the calculations that
showDataAs cannot express.
pivot.group accepts by: range with start, end and interval (Excel's
Starting at, Ending at and By), defaulting to the field's smallest and
largest numbers in steps of 10. Each range becomes one item captioned
"0-9" for whole-number steps or "0-2.5" otherwise; numbers below the
start or above the end fall into "<start" and ">end" items, sorted
first and last. Bounds must be finite with start no greater than end,
the step positive, and the grouping is limited to 100,000 ranges. by:
none removes the grouping, and a change of grouping clears the field's
item selection. pivot.list and pivot.fields report the range; the
field menu in the PivotTable Fields pane gains Group by Ranges of 10.

XLSX: written as a fieldGroup on the base cache field with rangePr
startNum, endNum and groupInterval and the group items, and read back;
a range without a usable width is ungrouped with a warning.
New commands set a filter rule on a row or column field:

- pivot.labelFilter compares each item's caption: equals, does not
  equal, begins with, ends with, contains (and their negations), greater
  than, less than, between and not between. The text comparisons take
  * and ? wildcards; numbers compare as numbers; case does not matter.
- pivot.valueFilter compares each item's total of a value field (the
  first by default) with a number or a range.
- pivot.top10 keeps the top or bottom items by a value field, counted
  as items, a percent of the total or a sum.
- pivot.clearFilter clears a field's filters, or every field's; a
  report filter stays in its area, showing all items.

Value and Top 10 filters apply within each parent item, and subtotals
and grand totals count only the items shown. As in Excel without
"Allow multiple filters per field", a field has one kind of filter at a
time: a rule replaces the field's item selection and the reverse. A
rule follows its field between rows and columns and is dropped when the
field moves to the filters area; value rules follow a renamed value
field and go with a removed one; regrouping a field clears its label
filter. pivot.list and pivot.fields report each rule.

The PivotTable Fields pane gains Top 10 Items, Bottom 10 Items and
Clear Filter in a field's menu, and no longer lists a row or column
field's filter in the Filters area.

XLSX: written as the pivotTableDefinition <filters> (caption*, value*,
count, percent and sum filter types with their AutoFilter conditions)
and read back; date filters are skipped with a warning.
@neilcawse

Copy link
Copy Markdown
Contributor

I tested this PR at df0446d on 11 Excel-saved fleet reports that hold 26 PivotTables. Nine of the files use label, value or Top N filters.

Warnings: "calculated items and label/value filters are not supported" no longer appears on any of the 9 files.

Refresh compared with Excel's saved cells. I refreshed every PivotTable and compared its cells with the ones Excel saved:

Cells that differ from Excel
main 7,229
this PR 4,809
this PR + #722 (sort by value, captions, item order, per-field subtotals) 774

On the files it affects, this PR makes a large difference: Idling Graph goes from 1,617 to 42, Driver Safety goes from 73/75 to 21/21 (0/0 with #722), and Active Faults Summary goes from 1,465 to 792 (0 with #722). With both PRs, 10 of the 26 PivotTables match Excel exactly. Of the 774 cells that still differ, 773 are items that tie on the sort value and come out in a different order (same rows, different order). The last one is a one-minute rounding difference in a summed duration.

One behaviour difference: Top N with a tie at the cut-off. Excel keeps every item tied with the Nth value. GridCraft cuts at exactly N. In one report, a Top 15 filter on a row field (sorted by Speeding Events) has items 14, 15 and 16 all at 5. Excel shows 16 items, but this PR shows 15, so the PivotTable is one row shorter than Excel's.

Merging. This PR conflicts with current main in ROADMAP.md only. I also merged it with #717, #718, #719, #722, #684, #685 and #686. The only overlap is with #722, in xlsx/pivot.rs, engine/pivot.rs and engine/cmd/pivot.rs. Each side adds code next to the other:

On that merge, the xlsx, engine and model tests pass, and the XLSX round trip shows 0 differences on all 11 files.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants