---
title: "Formulas, tables and the rest"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Formulas, tables and the rest}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
library(writexl)
```

Everything a cell can hold besides a plain value, and the worksheet features
that act on ranges.

## What a cell can hold

`xl_cell_general()` is the general cell: any combination of a value, a formula,
a hyperlink, a format and a comment. The narrower constructors --- `xl_formula()`,
`xl_hyperlink()`, `xl_comment()` --- return the same kind of object.

### Formulas

```{r}
df <- data.frame(x = 1:3, y = c(10, 20, 30))
df$total <- xl_formula(sprintf("=A%d*B%d", 2:4, 2:4))
path <- write_xlsx(df, tempfile(fileext = ".xlsx"))
```

writexl writes the formula, not its result: Excel computes the value when the
file is opened. Give `result =` if a reader that does not calculate needs
something to show.

**Array formulas** cover a range and are entered once:

```{r}
xl_cell_general(formula = "=SUM(A2:A4*B2:B4)", array = TRUE)
xl_cell_general(formula = "=UNIQUE(A2:A10)", dynamic = TRUE)
```

`array = TRUE` writes the legacy CSE form and `dynamic = TRUE` the modern
spilling form. A multi-cell array range is declared with `array_range =` and
must start at the cell holding the formula, and must not overlap cells the
sheet writes itself.

### Hyperlinks

```{r}
xl_hyperlink("https://example.com", "Example")
```

The second argument is the text the cell shows; without it the URL is
shown. External URLs, `mailto:` addresses, other files and internal
`#Sheet1!A1` references all work. Styling comes from the workbook's
`hyperlink_format`, and `xl_properties(hyperlink_format = NULL)` writes them
unstyled.

### Comments

```{r}
xl_cell_general(value = 42, comment = xl_comment("Checked", author = "QA"))
```

A comment carries its own box: size, position, colour, and whether it is
visible when the file opens.

### Rich strings

One cell whose text is split into differently formatted runs:

```{r}
xl_rich_string("Plain ", xl_rich_run("bold", xl_font(bold = TRUE)),
               " and ", xl_rich_run("red", xl_font(color = "red")))
```

A run carries a font and nothing else, because that is all Excel renders on
one. A rich string cannot share a cell with a formula or a hyperlink, both of
which would discard the runs.

### Mixed columns

A column may hold different kinds of cell at different rows --- a number, a
formula, a blank, a hyperlink --- by giving `xl_cell_general()` lists:

```{r}
xl_cell_general(value = list(1, NA, "text"),
                format = list(NULL, xl_fill(background = "yellow"), NULL))
```

## Data validation

`xl_validation()` restricts what may be typed into a range, and carries the
messages Excel shows:

```{r}
xl_sheet(data.frame(size = c("S", "M", "L")),
         validation = xl_validation(list(cols = "size"), type = "list",
                                    list = c("S", "M", "L"),
                                    input_title = "Pick a size"))
```

Dropdown lists, numeric, date, time and text-length bounds, and custom formulas
are all available, with the criteria spelled the same way as elsewhere
(`"=="`, `"between"`, ...).

## Autofilters

```{r}
xl_sheet(data.frame(fruit = c("apple", "pear"), qty = c(5, 12)),
         filter = xl_filter("fruit", "==", "apple"))
```

Excel stores filter criteria and hidden rows independently and does **not**
apply a filter when a file is opened, so criteria alone would give a sheet that
looks filtered while showing every row. writexl therefore evaluates the filter
and hides the rows it excludes.

That makes it responsible for reproducing Excel's matching rules, which were
measured rather than inferred. An exact value or a `list` matches the text a
cell *displays*, so it matches the number `10` and the string `"10"` alike;
every other criteria compares by type. Text matching is case-insensitive, `*`
and `?` are wildcards, and blank covers an empty cell as well as an empty
string.

`xl_filter_keep()` exposes the rule on its own, without writing anything:

```{r}
df <- data.frame(fruit = c("apple", "pear", "plum"), qty = c(5, 12, 7))
xl_filter_keep(df, xl_filter("qty", ">", 6))
```

## Worksheet tables

A table is a named, styled range Excel treats as a unit: banded rows, a filter
dropdown, structured references, an optional total row.

```{r}
xl_sheet(data.frame(item = c("a", "b"), qty = c(3, 4)),
         table = xl_table(name = "Stock", total_row = TRUE,
                          columns = xl_table_column("qty", total = "sum")))
```

Column headers always come from the data frame's names. `worksheet_add_table()`
would otherwise write its own generic captions over them, and Excel treats a
mismatch between the table part and the header cells as a corrupt file.

Table names are made unique across the workbook. A table turns off the
row-streaming mode described in [Worksheets and workbooks](c-worksheets-workbooks.html), and
cannot sit on the same range as the sheet's own autofilter.

## Merged cells

```{r}
xl_sheet(data.frame(a = 1:2, b = 3:4),
         merge = xl_merge("A1:B1", "Heading",
                          format = xl_align(horizontal = "center")))
```

A merged range holds one value, so `xl_merge()` carries its own text. Merging
over cells the data frame filled keeps only the merged text, exactly as merging
does in Excel.

## Elsewhere

* Styling any of this: [Formatting cells](b-formatting.html)
* Sheet layout, printing, workbook properties: [Worksheets and
  workbooks](c-worksheets-workbooks.html)
* Charts, chartsheets, pictures: [Charts and images](d-charts-images.html)
