Skip to content

PivotTables: Group Selection, time grouping, calculated items, timelines and filter menus (builds on #474, #498, #1189) - #1207

Open
Herr-Enklo wants to merge 16 commits into
storytold:mainfrom
Herr-Enklo:feat/pivot-items-timeline
Open

Herr-Enklo wants to merge 16 commits into
storytold:mainfrom
Herr-Enklo:feat/pivot-items-timeline

Conversation

@Herr-Enklo

Copy link
Copy Markdown

Stacked on #1189 (PivotTable Analyze/Design tabs, GETPIVOTDATA), because these features fill that PR's disabled ribbon placeholders. Until #1189 lands, this diff includes its two commits. The commits after the Merge feat/getpivotdata commit are the ones to review here.

Builds on #474 and #498 by @prxnvt:

Their commits stay in the branch, brought up to date with main. #498's open review point is fixed: Top N keeps items tied at the cut-off.

Grouping (PivotTable Analyze › Group)

  • Group Selection (Auswahl gruppieren) makes a grouped field Region2 with Group1. Ungroup (Alt+Shift+Left) removes it.
  • The Grouping dialog (Feld gruppieren) covers:
    • numbers: Starting at / Ending at / By
    • dates and times: Seconds, Minutes, Hours, Days, Months, Quarters, Years, several at once, plus Number of days
  • Values outside the bounds collect in <1/1/2026 / >6/30/2026 items.
  • Excel's restrictions apply, with Excel's messages.
  • XLSX fieldGroup, rangePr, groupItems and discretePr are read and written. Date groups keep their chronological order in Excel.

Calculated items

  • PivotTable Analyze › Calculations › Fields, Items & Sets ▾ › Calculated Item…, Solve Order…, List Formulas. The new commands are pivot.calculatedItem, pivot.solveOrder, pivot.listFormulas, plus the query pivot.fieldItems.
  • Totals include calculated items, as in Excel. Solve order decides cells where two calculated items meet.
  • Excel's restrictions apply: no averages, no duplicate values, not in the filter area, no grouping, no cell references.
  • XLSX calculatedItems are loaded and saved. The reader no longer warns "calculated items … are not supported".

Timelines

  • Insert › Filters › Timeline, and PivotTable Analyze › Filter › Insert Timeline, with Excel's Insert Timelines dialog listing the date fields.
  • A timeline is a sheet object next to slicers. It filters its PivotTables by whole periods: Years, Quarters, Months or Days.
  • A contextual Timeline tab offers caption, Report Connections, 12 styles, size, and Show (Header, Selection Label, Scrollbar, Time Level).
  • XLSX: the timeline and timeline-cache parts, the x15 extensions, the drawing frame and the dateBetween PivotTable filter are read and written.

Filter menus

  • The report's field buttons and the field list open Sort, Label Filters ▸, Value Filters ▸ (with Top 10…) and the item checklist, as in Excel.
  • New dialogs: Label Filter, Value Filter, Top 10, Grouping, Calculated Item, Solve Order, Insert Timelines, Report Connections.
  • Value Field Settings offers all 15 Show Values As options.
  • German labels follow German Excel: Auswahl gruppieren, Gruppierung aufheben, Feld gruppieren, Berechnetes Element…, Lösungsreihenfolge…, Formeln auflisten, Zeitachse, Beschriftungsfilter, Wertefilter, MONATE, Alle Zeiträume.

Checked in Excel 365

All checks ran black-box on synthetic files that were never saved.

  • Grouped fields show the same items and totals before and after a refresh in Excel.
  • Files with calculated items or timelines open without repair. Excel then drops the calculated items (with f="1" it refuses the file, without it it loads none; 10 variants tried). It also doesn't apply a timeline's saved period on refresh (11 variants tried). GridCraft round-trips both correctly; gaps.md §8 documents this.

Not included

Tests

  • Engine tests for every new command: grouping, time levels, calculated items and restrictions, solve order, timelines and report connections.
  • XLSX round-trip and hostile-input tests.
  • egui_kittest UI tests in pivot_timeline.rs.
  • The hostile-params fuzz covers every new command.
  • cargo xtask ci: all 8 steps pass (1658 tests).

🤖 Generated with Claude Code

prxnvt and others added 15 commits October 11, 2026 22:10
… 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.
…op 10 filters)

Brings storytold#474 and storytold#498 up to date with main (413 commits). Conflicts resolved
keeping both sides:
- engine/pivot.rs: main's column budget check (`data_cols_exceed_sheet`,
  refusing a report that can't fit before its column groups are allocated)
  now runs in the record loop after storytold#498's shared `add` closure; the data
  loop takes main's values-on-rows value index and storytold#474's Show Values As.
- xlsx/pivot.rs: storytold#498's filter writer and reader next to main's
  `axis_fields_xml`; the read warning keeps only calculated items (values on
  rows and label/value filters are both supported now).
- ui-egui/pivot_pane.rs: main's translated "Show as …" labels for all 15
  Show Values As calculations.
- Docs and the ROADMAP progress log keep both sides' entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of storytold#498 (neilcawse): on a real report a Top 15 filter whose items
14, 15 and 16 all had the same value showed 15 rows where Excel shows 16.
Excel keeps every item tied with the last item a Top N (items) filter keeps;
GridCraft now does the same, for Top and Bottom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…elines

Engine and model for the rest of Excel's PivotTable Analyze depth:

- Grouping (PivotTable Analyze › Group). `pivot.group` takes several date
  and time levels at once (seconds, minutes, hours, days, months, quarters,
  years; one field entry per level, coarsest first), Starting at / Ending at
  for dates (values outside fall into `<date` and `>date` items) and Number
  of days (N-day spans from the first date, the last one cut at the end).
  Without parameters it opens the Grouping dialog for the field at the
  active cell. `pivot.groupSelection` gathers two or more selected items into
  `Group1`, `Group2`… of a new grouped field (`Region2`) placed just outside
  its base field; `pivot.ungroup` removes groups (the grouped field goes with
  its last one) or a date/number grouping. Captions follow Excel: `12 AM`,
  `:05`, `1/15/2026 - 1/21/2026`.
- Calculated items (Calculations › Fields, Items & Sets). A formula over the
  items of one field (`=East+West`, `=Region[East]*1.1`, `'North East'`)
  runs on each item's summary for every combination of the other fields'
  items; the results count in subtotals and grand totals, as in Excel.
  Where items of two fields meet, the later one in solve order sets the
  value (`pivot.solveOrder`). `pivot.listFormulas` writes Excel's formula
  list sheet. Excel's restrictions are enforced both ways: no calculated
  items with date/number grouping, Average/StdDev/Var or a field twice in
  Values, and a field with calculated items stays in Rows or Columns.
- Timelines (Insert › Filters › Timeline, PivotTable Analyze › Insert
  Timeline, and the Timeline tab's caption, Report Connections, styles, size
  and Show options). A timeline keeps its selection as whole periods of its
  level; connected PivotTables keep only records whose date falls in it.
- The report now records which field and item each label cell shows
  (`Output::items`), so commands can act on the selected items.

Behaviour was checked black-box against Excel 365 (captions, totals that
include calculated items, restrictions, timeline defaults).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ribbon now shows Excel's two PivotTable tabs while the active cell is in a
PivotTable report, and goes back to Home when it leaves. Every group follows
Excel 365 in order and control type: Analyze has PivotTable (name, Options),
Active Field, Group, Filter, Data, Actions, Calculations, Tools and Show;
Design has Layout (Subtotals, Grand Totals, Report Layout, Blank Rows),
PivotTable Style Options and the PivotTable Styles gallery. Existing pivot
commands are wired where Excel has them; controls for features GridCraft
doesn't have yet (slicers and timelines for PivotTables, calculated items,
grouping dialogs, OLAP tools, blank rows...) stay visible, disabled, with the
reason on hover. Each group is its own function in ribbon/pivot_tabs.rs, so a
feature can be wired in where its control already sits.

New pieces behind the controls:
- pivot.clearAll and pivot.clearFilters (Actions > Clear): Clear All empties
  the PivotTable but keeps it, as Excel does, instead of deleting it.
- pivot.showFieldList (UI): the Field List toggle hides the Fields pane.
- Value Field Settings and Change PivotTable Data Source dialogs.
- Key tips JT and JY. They are contextual: the matcher switches to a
  contextual tab only while it shows, so Table Design's JT letters keep
  working; xtask keytips accepts contextual tab targets and generates tests
  for the hidden case.
- German labels in German Excel's wording (PivotTable-Analyse, Entwurf, ...),
  checked against the installed German Excel's own labels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on and calculated items

- Date groupings write and read hours, minutes and seconds (`groupBy`),
  Starting at / Ending at set by hand (`autoStart="0"`, `autoEnd="0"` with
  the dates) and Number of days (`groupInterval`, with `1/5/2024 - 2/3/2024`
  group items cut at the end).
- Group Selection fields are cache fields over their base field
  (`databaseField="0"`, `fieldGroup base` with `discretePr` mapping each base
  shared item to a group item); the base field names them with
  `<fieldGroup par>`. Reading turns such a field back into the model's groups.
- Calculated items now load instead of being dropped with a warning: each is
  a shared item of its field after the data items, and its formula is a
  `calculatedItem` of the cache with a `pivotArea` pointing at that item.
- A `fieldGroup` that only names a grouped field (`par` without `rangePr` or
  `discretePr`) no longer reads as an unsupported grouping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ilters

A timeline is saved the way Excel 2013+ stores one: a timeline part per
sheet (`x15:timeline`: caption, level, selection level, scroll position,
style and the Show options) listed in the sheet's `x15:timelineRefs`, a
timeline cache per timeline (`timelineCacheDefinition`: the date field, its
PivotTable by sheet id and name, the selected period and the data bounds)
listed in the workbook's `x15:timelineCacheRefs` with its `NativeTimeline_`
defined name, a `tsle:timeslicer` drawing frame with a fallback shape, and a
`dateBetween` filter in the PivotTable that the cache's state names. The
PivotTable's cache gets the `x14:pivotCacheDefinition` id the timeline cache
refers to, and the timeline's field is written as dates.

Reading puts it back together after every sheet is read: frames give the
anchors, caches the field, selection and PivotTables (mapped by sheet id and
name to the model's ids); the PivotTable's timeline filter and the cache's
defined name are not read as a filter rule or a name.

A timeline cache can only list PivotTables on one pivot cache, and GridCraft
writes one pivot cache per PivotTable, so a timeline filtering several
PivotTables is saved filtering the first of them (GridCraft's JSON keeps
them all).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GETPIVOTDATA(data_field, pivot_table, [field1, item1], ...) reads a value
shown in a PivotTable report, with Excel's semantics, checked against Excel
365 on synthetic PivotTables:
- data_field is a value field's caption or its source field, case ignored;
  pivot_table is any cell or range in the report (#REF! otherwise).
- Pairs name row, column or single-item report filter fields and one of
  their shown items: numbers and dates (serials or DATE) match by value,
  text matches an item's label. Fields left out stand for their total, or
  for the one item the named items leave (Plums sells only in the West).
- Hidden, filtered and collapsed items, totals turned off and ambiguous
  pairs give #REF!; an empty value cell in a cross tab is 0; Show Values As
  results come back as shown.

Layering: the engine, which lays out reports, now records with each report
what every line and data column stands for (model PivotReport, a runtime
cache on the PivotTable that comparisons ignore). calc evaluates
GETPIVOTDATA from that index alone and depends on the report area, so
refreshes (full recalcs) and edits inside the report reach it. Reports of
PivotTables read from files are indexed on open when the recomputed report
matches the cells on the sheet. GETPIVOTDATA has no _xlfn prefix in files.

Generate GetPivotData (PivotTable Analyze > PivotTable > Options, and File
> Options > Formulas > Use GetPivotData functions for PivotTable
references) is an application setting, on by default: clicking, dragging
back to or arrowing onto a PivotTable value while typing a formula inserts
GETPIVOTDATA("Sum of Sales",$A$3,"Region","East") instead of the
reference, with the sheet when the PivotTable is elsewhere and DATE for
date items.

The PivotTable Analyze tab now uses the index too: Active Field shows the
active cell's field, Field Settings opens Value Field Settings, Expand and
Collapse Field and Ungroup act on it, and Select > Values selects the
value cells.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s and the timeline UI

- Report field buttons: the Row Labels / Column Labels cells (each field's
  header in Outline and Tabular form, and each report filter) get Excel's
  dropdown button. Its menu has Select field (Compact form), Sort A to Z /
  Z to A, Clear Filter From, Label Filters ▸ (the 14 comparisons), Value
  Filters ▸ (8 comparisons and Top 10…) and the searchable item checklist.
  The Fields pane's field menus get the same two submenus.
- Dialogs: Label Filter, Value Filter, Top 10 Filter, Grouping (Starting at,
  Ending at, By; for dates the seven levels and Number of days), Insert
  Calculated Item (Name, Formula, Fields, Items, Insert Item, Add/Modify,
  Delete), Calculated Item Solve Order, Insert Timelines and a timeline's
  Report Connections. They run the engine commands; errors keep them open.
- Timelines are drawn on the sheet (caption, clear button, selection label,
  time level menu, period tiles under their year, scroll bar) in our own
  palette for the twelve styles. A click selects a period, Shift+click
  extends the selection, the caption row selects or drags the object, Delete
  removes it. Insert › Filters › Timeline sits after Slicer, and a selected
  timeline shows the Timeline tab: caption and Report Connections, Timeline
  Styles, Arrange (disabled), Size, and Show (Header, Selection Label,
  Scrollbar, Time Level).
- German labels for all of it (Zeitachse, Beschriftungsfilter, Wertefilter,
  Top 10…, Gruppierung, Berechnetes Element einfügen…) and for the Fields
  pane; key tip NST for Insert › Timeline.
- `pivot.fieldItems` (query) lists a field's items with whether its item
  selection shows them, for the checklist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gn tabs, GETPIVOTDATA

Wires this branch's commands into the tabs' placeholders:
- Group: Group Selection (`pivot.groupSelection`), Ungroup (`pivot.ungroup`,
  enabled on date/number-grouped and Group Selection fields) and Group Field
  (`pivot.group`, the Grouping dialog).
- Filter: Insert Timeline (`pivot.insertTimeline`); Insert Slicer and Filter
  Connections stay disabled.
- Calculations › Fields, Items, & Sets: Calculated Item…, Solve Order… and
  List Formulas next to Calculated Field….
- Value Field Settings offers all 15 Show Values As calculations (storytold#474) with
  a base field and base item; the Active Field's show-as key covers them.

Merge resolutions: the PivotTable model and the report output keep both
sides' fields (Group Selection fields, calculated items and the item cells
here; the GETPIVOTDATA report index there); the GETPIVOTDATA index names a
Group Selection field by its own name. Duplicate German labels keep the
tabs' wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Checked in Excel 365: a field grouped by 30-day spans showed its items in
text order (`11.11.2026 - 10.12.2026` first) because the pivot field carried
`sortType="ascending"`, which Excel applies to group captions as text. Its
group items are already in date order, so ascending date groups are now
written without `sortType`, as Excel writes them, and a date-grouped field
without one reads back as ascending. (storytold#1067 found and fixed the same for days.)

A timeline's date field is only written as dates when nothing else needs its
shared items (an ungrouped Rows or Columns field keeps them).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gaps.md item 8, the target-app, file-format and UI parity docs, the beta
gates and the ROADMAP progress log cover Group Selection, time grouping,
calculated items, timelines and the label/value filter menus, and record
what Excel 365 does with the files: grouping round-trips, while saved
calculated items and timeline selections are not picked up yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uage

The timeline drew its time level (MONTHS) and the empty-selection caption (All Periods) in
English even with a German interface, although the level menu was already translated. Both
now go through the language catalog: MONATE, Alle Zeiträume.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@neilcawse

Copy link
Copy Markdown
Contributor

I tested this PR at 895b235 on the 11 Excel-saved fleet reports I used for #498: 26 PivotTables, of which 11 (in 8 files) have label, value or Top N filters. None use calculated items, so this covers the filter part only.

Filters behave as in Excel.

main (84acf62) #498 (df0446d) this PR
PivotTable warnings on open 9 1 0
Filtered PivotTables showing Excel's items after refresh 2 / 11 10 / 11 11 / 11
<filter> elements kept on save (12 in the files) 0 10 10

The difference from #498 is the tie at the Top N cut-off. Speeding Violations has a Top 15 with items tied at 15th place. Excel shows 19 rows; #498 shows 18, and which tied item it keeps changes from run to run (2 of 10 runs kept the other one). This PR shows all 19 every time. With 72b2a77 the item lists of all 11 filtered PivotTables match Excel's. The cells that still differ come from value sorting, captions and layout, which #722 covers. They are the same on main, #498 and this PR.

One loss on save, without a warning. ELD-Unverified-Logs has a label filter on a field that isn't in any area:

<filter fld="14" type="captionEqual" evalOrder="-1" id="3" stringValue1="1">

It appears in pivotTable2.xml and pivotTable3.xml, and field 14 has no axis. After gridcraft-cli run --in ELD-Unverified-Logs.xlsx --out out.xlsx, neither part has a <filter>, and the only warning printed is about an embedded object. The refreshed report is unaffected, since the field isn't shown. But a user who drags the field back into Rows in Excel after a GridCraft save would no longer get the filter. The same happens on #498. Keeping such filters, or warning that they were dropped, would close this.

A minor difference: top10 val="15" filterVal="15" is saved as top10 top="1" percent="0" val="15". The cached filterVal is dropped, but the filter means the same.

No crashes, and each file opened and saved in 2 s or less. gridcraft-cli check shows the same round-trip results as main (only the chart style and colour parts, which main also drops) and no calculation mismatches.

@prxnvt

prxnvt commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Thank you @Herr-Enklo. I will be more thorough in subsequent implementations!

Retain supported label, value and Top 10 rules for fields outside Rows and
Columns during XLSX load/save, without creating report-filter rows or buttons.
Reactivate saved rules when the field returns to an axis, and remove them
cleanly when filters or their referenced value fields are cleared.

Add synthetic field-14 import, repeated round-trip, engine and UI regression
tests. Update the affected PivotTable support documentation.

Validation: cargo xtask ci passed all 8 gates; 1665 tests passed, with one
existing timing-sensitive test ignored.

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.

3 participants