Skip to content

Commit 4527bfe

Browse files
billdenneyclaude
andauthored
Phase 7: conditional formatting (#129)
* Add conditional formatting: the simple cell rules xl_cond_cell() covers the rules that pair a condition with a format -- comparisons, text matches, time periods, above/below average, top/bottom N, duplicates, uniques, blanks, errors and arbitrary formulas -- reaching worksheet_conditional_format_range(). xl_sheet(conditional =) takes one or a list of them through the Phase 0 overlay stage. The 34 criteria are partitioned by rule type and libxlsxwriter checks none of the pairing, so a text criteria on a cell rule produces a file Excel accepts and then silently ignores. .CONDITIONAL_CRITERIA_FOR maps each type to what it accepts, and a mismatch is an error naming both the criteria and the type and listing what would be valid there. Where the two are redundant the type is inferred from the criteria instead. The format is an ordinary xl_format, so it goes through the existing registry unchanged -- but conditional formats are emitted as differential formats. That was worth verifying rather than assuming: a rule's fill lands in <dxfs> in styles.xml and the rule references it by dxfId, which a test now pins. The API consistency gate added in the previous review caught this before it shipped: xl_cond_cell() had criteria before type while xl_validation() has type before criteria, and the two share six arguments. Reordered to match, since in both functions the type is what determines which criteria are legal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Add colour scales, data bars and icon sets xl_cond_scale() writes two- and three-colour scales, xl_cond_bar() in-cell data bars with control over the negative and border colours, axis and direction, and xl_cond_icons() one of Excel's seventeen built-in icon sets. Each is a separate constructor because the clusters share almost no fields; all four flow through one overlay kind. Corrects the conditional type table. Three of libxlsxwriter's type constants -- 2_COLOR_SCALE, 3_COLOR_SCALE and DATA_BAR -- carry no TYPE_ in their names, so an earlier reading of the header missed them and put icon_sets at 14 rather than 17. That is the index of a two-colour scale, so every icon set would have silently written a scale instead. The commit before this one only used indices 0 to 13 and was unaffected. The corrected mapping is pinned by enumerating all seventeen styles against the names Excel stores, which is the test that would have caught the original error: 5_quarters must give 5Quarters, 4_ratings must give 4Rating, and so on. A further test asserts the map still has seventeen entries, so a style added by a libxlsxwriter update fails here rather than being quietly unreachable. Icon sets embed nothing: Excel draws them itself, and a test asserts no xl/media part appears. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent addb67a commit 4527bfe

26 files changed

Lines changed: 1126 additions & 4 deletions

NAMESPACE

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ S3method(format,xl_rich_string)
88
S3method(length,xl_cell_general)
99
S3method(print,xl_cell_general)
1010
S3method(print,xl_comment)
11+
S3method(print,xl_conditional)
1112
S3method(print,xl_format)
1213
S3method(print,xl_merge)
1314
S3method(print,xl_page_setup)
@@ -30,6 +31,10 @@ export(xl_cell_general)
3031
export(xl_col_spec)
3132
export(xl_color)
3233
export(xl_comment)
34+
export(xl_cond_bar)
35+
export(xl_cond_cell)
36+
export(xl_cond_icons)
37+
export(xl_cond_scale)
3338
export(xl_fill)
3439
export(xl_font)
3540
export(xl_format)

NEWS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@
4040
lists, numeric, date, time and text-length bounds, and custom formulas, with
4141
the input and error messages Excel shows (#43).
4242

43+
* **Conditional formatting** via `xl_sheet(conditional = ...)`, in four
44+
flavours: `xl_cond_cell()` for a rule paired with a format, `xl_cond_scale()`
45+
for two- and three-colour scales, `xl_cond_bar()` for data bars and
46+
`xl_cond_icons()` for Excel's built-in icon sets.
47+
4348
* **Merged cells** via `xl_sheet(merge = xl_merge(...))`. A merged range holds
4449
one value, so `xl_merge()` carries its own text; merging over cells the data
4550
frame filled keeps only the merged text, as it does in Excel.

R/xl_conditional.R

Lines changed: 437 additions & 0 deletions
Large diffs are not rendered by default.

R/xl_sheet.R

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,9 @@ xl_row_spec <- function(rows, height = NA, hidden = NA, level = NA,
155155
#' @param validation One [xl_validation()], or a list of them, restricting what
156156
#' may be typed into a range --- a dropdown, a numeric or date bound, a text
157157
#' length limit or a custom formula.
158+
#' @param conditional One conditional format ([xl_cond_cell()],
159+
#' [xl_cond_scale()], [xl_cond_bar()], [xl_cond_icons()]), or a list of them,
160+
#' formatting cells according to their contents.
158161
#' @return An `xl_sheet` object.
159162
#' @family writexl
160163
#' @seealso [xl_col_spec], [xl_row_spec], [write_xlsx]
@@ -174,7 +177,7 @@ xl_sheet <- function(data, cols = NULL, rows = NULL, freeze = NULL,
174177
autofilter = FALSE, protect = FALSE,
175178
comment_author = NA, show_comments = FALSE,
176179
page = NULL, view = NULL, merge = NULL,
177-
validation = NULL) {
180+
validation = NULL, conditional = NULL) {
178181
if (!is.data.frame(data))
179182
stop("`data` must be a data frame", call. = FALSE)
180183
if (!is.logical(auto_colwidth) || length(auto_colwidth) != 1L || is.na(auto_colwidth))
@@ -204,7 +207,8 @@ xl_sheet <- function(data, cols = NULL, rows = NULL, freeze = NULL,
204207
page = page,
205208
view = view,
206209
merge = merge,
207-
validation = validation
210+
validation = validation,
211+
conditional = conditional
208212
),
209213
class = "xl_sheet"
210214
)
@@ -472,7 +476,8 @@ print.xl_sheet <- function(x, ...) {
472476
}
473477

474478
merges <- .resolve_merges(el, df, reg, header_offset, props)
475-
overlay <- c(overlay, .resolve_validations(el, df, header_offset))
479+
overlay <- c(overlay, .resolve_validations(el, df, header_offset),
480+
.resolve_conditionals(el, df, reg, header_offset, props))
476481

477482
col_format_id <- vapply(col_fmt, function(f) .register_format(reg, f), integer(1))
478483

man/is_xl_comment.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/is_xl_format.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/xl_cell_general.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/xl_color.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/xl_colrow_spec.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/xl_comment.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)