Skip to content

Conditional formatting: data bars keep Automatic vs Lowest/Highest bounds and their lengths - #1304

Closed
iluebbe wants to merge 5 commits into
storytold:mainfrom
iluebbe:fix/cf-data-bar-bounds
Closed

iluebbe wants to merge 5 commits into
storytold:mainfrom
iluebbe:fix/cf-data-bar-bounds

Conversation

@iluebbe

@iluebbe iluebbe commented Oct 14, 2026

Copy link
Copy Markdown

Fixes #1158 and #1159. Data bars told Excel's Automatic bounds and Lowest/Highest Value apart nowhere, and drew every bar from 0 with a fixed 10 % head start.

Builds on #717 (Excel 2010 conditional formats, @neilcawse). That PR moves the x14 data bar reader and writer this one extends, so this branch starts from its head (6f0f747) and contains its four commits until it merges. Please merge #717 first; I'll rebase this one onto main then.

Problem

Fix

  • Model (features.rs):
    • CfValueKind::AutoMin and AutoMax, documented as data-bar only.
    • CfRule::DataBar gets min_length and max_length (percent of the cell). The serde defaults are 10 and 90, a file's defaults.
  • Evaluation (cf.rs):
    • Automatic minimum: 0, or the lowest value when that is negative, as before. Every other minimum is used as it is.
    • The bar is minLength + frac × (maxLength − minLength) of the cell, with frac = (value − min) / (max − min) clamped to 0…1.
  • XLSX read (sheet_read.rs):
    • autoMin/autoMax become AutoMin/AutoMax.
    • The x14 rule a main-section bar links to now gives its bounds and lengths as well as its fill. The main section's minLength/maxLength are read too.
    • Only borders, the axis and negative bar colours still give the "partly skipped" warning.
  • XLSX write (sheet_write.rs):
    • Automatic is written as min/max in the main section and autoMin/autoMax in the x14 rule, as Excel does.
    • A bar gets its x14 rule when it needs one: solid fill, an Automatic bound, or lengths other than 10–90. Lengths go in both sections.
    • XLSX: read, evaluate and write Excel 2010 conditional formats #717's solid_bar_* helpers become bar_x14_*.
  • Command: home.conditionalFormat data bars default to Automatic and 0–100 %, as Excel's Data Bars gallery makes them. min/max also take autoMin/autoMax. The ribbon gallery and the sample workbooks get Automatic bars.

Measured in Excel for Microsoft 365

A file written by this branch, opened in its own Excel instance. Bounds, fill and lengths come from COM (Databar.MinPoint.Type, BarFillType, PercentMin/PercentMax). Bar length is a fraction of the full bar, from a capture of Excel's window (solid bars).

Rule (GridCraft command) Excel reads Excel draws
{"type": "dataBar"} over 10…20 Automatic Min/Max (6/7), 0–100 10 → 0.50, 15 → 0.76, 20 → 1.0
min/max over 10…20 Lowest/Highest Value (1/2), 0–100 10 → 0, 11 → 0.09, 15 → 0.51, 20 → 1.0
Number 3 – Number 8 over 0…10 Number (0/0), 0–100 3 → 0, 4 → 0.21, 6 → 0.60, 8 → 1.0
Gradient, min/max, 10–90 (no x14 rule) Lowest/Highest Value, gradient, 10–90 (2007 bar, as before)

These are the values data_bar_lengths_as_in_excel expects, with #1158's table for Percent, Percentile and Formula.

Bar lengths and the axis. The lengths behave as above only without an axis. Measured over 0…10, Lowest to Highest, solid:

minLength–maxLength No axis (axisPosition="none") Automatic axis (the default)
0–50 not measured 0 → 0, 10 → 0.51
50–100 0 → 0.51, 10 → 1.0 0 → 0.26, 10 → 0.76
20–100 0 → 0.21, 10 → 1.0 0 → 0.09, 10 → 0.91
40–60 0 → 0.40, 10 → 0.60 0 → 0.21, 10 → 0.40

With Excel's automatic axis, a nonzero shortest bar is halved: minLength/2 + frac × (maxLength − minLength). A bar without an x14 rule (Excel 2007, other writers; 10–90) is drawn like the no-axis column.

GridCraft draws no axis, which is #781's work, so it draws every bar like the no-axis column. That matches Excel for:

  • every bar Excel's dialogs can make, since they are always 0–100;
  • every bar this command makes;
  • bars without an x14 rule.

Only a bar with a nonzero minLength from VBA or another writer, on Excel's automatic axis, still differs. For that reason home.conditionalFormat takes no length parameters.

Testing

  • data_bar_lengths_as_in_excel (engine): the command's default bar and Number, Percent, Percentile, Formula and Lowest/Highest bounds, against the measured values (tolerance 0.02).

  • data_bar_lengths_from_a_file (engine): lengths 10–90, 50–100, 20–100 and 40–60, against the no-axis measurements.

  • data_bar_bounds_and_lengths (xlsx), from Excel-shaped XML:

    • an Automatic gallery bar with border, axis and negative colours (→ AutoMin/AutoMax, 0–100, one "partly skipped" warning);
    • a solid Lowest/Highest bar;
    • a bar without an x14 rule (→ 10–90);
    • a main-section bar with lengths 20–80.

    Writing it back gives three x14 rules with the expected bounds and lengths, no autoMin in the main section, and the same rules on reading, without warnings.

  • XLSX: read, evaluate and write Excel 2010 conditional formats #717's x14_conditional_formats_are_read: its x14 bar (Automatic, 0–100, solid) now reads with those bounds and lengths. It no longer warns, since the model holds all of its settings.

  • full_roundtrip: its data bar carries lengths 5–95.

  • cargo xtask ci: fmt, clippy, test, assets, layers and wasm pass.

    • parity and keytips report their generated files out of date on this Windows checkout (core.autocrlf).
    • Regenerating them changes only line endings: git diff is empty, as on main.
    • After rebasing onto XLSX: read, evaluate and write Excel 2010 conditional formats #717's latest commit, gridcraft-xlsx (176 tests) and the engine's cf:: tests pass again.

Conflicts:

The merges are mechanical.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SAgnkqJiy3v24Hp25dQLRj

neilcawse and others added 5 commits October 13, 2026 13:18
Excel 2010+ keeps conditional format rules whose formulas refer to
other sheets only in the worksheet's x14:conditionalFormattings
extension, with xm:f formulas and an inline x14:dxf in place of a
dxfId. GridCraft skipped the whole extension with a warning.

Read: x14 cellIs (every operator, one or two formulas), expression,
blanks, errors, duplicate/unique, top 10, above average and time
period rules become ordinary rules, with the inline differential
format (font, fill, border, number format) and priorities that
interleave with the main section's. A data bar's linked extension
(the x14:id of a main-section rule) adds no second rule. Other x14
kinds (data bar settings, x14 icon sets) are skipped with a warning
naming the kind.

Evaluate: cross-sheet references in rule formulas already resolve;
an engine test pins cellIs and expression rules against cells on
another sheet.

Write: a cell-value or formula rule referring to another sheet is
written in the x14 extension ({78C0D931-6437-407d-A8EE-F0AAD7539E65})
with an inline x14:dxf and a unique id; other rules stay in the main
section. The extension shares one extLst with the sparklines (CF
first). Inline number formats get ids styles.xml declares.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Colour scale, data bar and icon set rules are always written in the main
section, even when their cfvo formulas refer to other sheets; the parity
docs now say so. (This commit first dropped the
reader's own x14:id linking; after the rebase onto main, main's data bar
linking from storytold#550 is the one path, so only the docs part remains.)
…kinds

The file that motivated the x14 conditional format work
(watchdog-report.xlsx) was saved by LibreOffice 25.2, not Excel, so
nothing observed in Excel backs the earlier commit's statement that
Excel 2010+ keeps cross-sheet rules only in the extension. MS-XLSX
2.6.27 (CT_CfRule) allows single-sheet references in xm:f but says
nothing about where Excel places such rules; the comments and docs now
say the placement follows the LibreOffice-saved file and Excel's own
is unverified.

MS-XLSX 2.6.27 forbids sheet-range-reference (3-D spans) in xm:f, so a
rule with a 3-D span now stays in the main section.

The parity docs and gaps list name the x14 kinds read (cellIs,
expression, blanks, errors, duplicate/unique, top10, aboveAverage,
timePeriod) and those skipped with a warning and lost on save (data
bars, icon sets, colour scales, containsText, notContainsText,
beginsWith, endsWith).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dxf_xml, which writes both styles.xml dxfs and inline x14:dxf elements, wrote font, number format, fill, alignment and border but never protection, although read_dxf reads it. A rule whose format unlocked or hid cells lost that on save. It now writes protection after border, the order CT_Dxf requires.
…ounds and their lengths (fixes storytold#1158, storytold#1159)

Excel writes Automatic and Lowest/Highest Value both as min/max in the main section; only the
x14 rule tells them apart (autoMin/autoMax), and it holds the bar lengths. GridCraft read both
as Min/Max, evaluated Min as Automatic, wrote its bars without x14 bounds (Excel reads them as
Lowest/Highest Value, 10-90 %), and drew every bar as frac * 0.9 + 0.1 from 0.

- Model: CfValueKind::AutoMin/AutoMax; DataBar min_length/max_length (10/90 by default).
- Evaluation: only Automatic pulls a positive minimum to 0; the bar is
  minLength + frac * (maxLength - minLength), as Excel draws it without an axis.
- XLSX: autoMin/autoMax and the lengths are read from the linked x14 rule (and the main
  section's lengths); written as min/max plus an x14 rule with autoMin/autoMax whenever a bar
  needs one (solid, Automatic, or lengths other than 10-90).
- home.conditionalFormat: data bars default to Automatic, 0-100 %, as Excel's gallery.

Measured in Excel for Microsoft 365: a file this branch writes opens with Automatic,
Lowest/Highest and Number bounds and the lengths as written, and the bar lengths match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAgnkqJiy3v24Hp25dQLRj
@iluebbe

iluebbe commented Oct 15, 2026

Copy link
Copy Markdown
Author

Closing this pull request. #1158 and #1159 describe the problem; their new comments add the bar-length measurements (with and without Excel's axis) and the fix approach. A fix fits on top of #717.

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.

Conditional formatting: data bars with a fixed minimum (number, percent, percentile, formula, lowest value) are drawn from 0

2 participants