---
title: "The Fluent API"
---
```{r}
#| include: false
library(tabviz)
library(dplyr)
```
Throughout this guide, you've seen styling via direct arguments to `tabviz()`. This chapter introduces the **fluent API**—a pipe-based alternative that lets you build and modify plots step by step using `set_*()` functions.
Both approaches produce identical results. The fluent API shines when:
- Styling depends on computed values
- You're creating multiple variants from one base plot
- You want reusable styling pipelines
- Conditional logic determines styling
## The Two Styling Approaches
tabviz offers two equivalent ways to style your tables. Both produce identical results—choose based on your workflow preferences.
### Approach 1: Direct Arguments in tabviz()
When you know all your styling upfront, pass everything directly to `tabviz()`:
```{r}
#| eval: false
tabviz(data, label = "study",
columns = list(
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
),
row_type = "rtype",
row_bold = "rbold",
marker_color = "effect_color",
theme = web_theme_jama()
)
```
This approach is concise and keeps all configuration in one place.
### Approach 2: Fluent API (Pipe-Based Styling)
When styling depends on computation or you want to build incrementally, use `set_*()` functions:
```{r}
#| eval: false
tabviz(data, label = "study",
columns = list(
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
)
) |>
set_row_style(type = "rtype", bold = "rbold") |>
set_marker_style(color = "effect_color") |>
set_theme("jama")
```
Both produce identical output.
::: {.callout-note}
## When to Use Which
| Use Constructor Arguments When | Use Fluent API When |
|-------------------------------|---------------------|
| Everything is known upfront | Styling depends on computation |
| Simple, static plots | Creating multiple variants from one base |
| Fewer lines of code matter | Conditional logic determines styling |
| | Building reusable styling pipelines |
:::
---
## The Fluent Pattern
The fluent API follows a simple rule: **every `set_*()` function returns a modified plot that can be piped into the next function**.
```r
tabviz(data, ...) |> # Create plot
set_row_style(...) |> # Modify rows
set_marker_style(...) |> # Modify markers
set_theme(...) # Apply theme
```
::: {.callout-note}
## Immutability Guarantee
Each `set_*()` call returns a **new** modified plot, leaving the original unchanged. This makes it safe to create multiple variants without side effects.
:::
---
## Core Styling Functions
All `set_*()` functions accept either **column names** (strings) or **formula expressions** (`~ ...`):
```r
# Using column names (pre-computed)
plot |> set_row_style(bold = "is_significant")
# Using formula expressions (computed on the fly)
plot |> set_row_style(bold = ~ pvalue < 0.05)
# Mix both approaches
plot |> set_marker_style(
color = ~ ifelse(upper < 1, "#16a34a", "#64748b"),
shape = "marker_shape_column"
)
```
See [Row Styling](row-styling.qmd#expression-based-styling) and [Cell Styling](cell-styling.qmd#formula-expressions-for-cell-styling) for detailed formula documentation.
### set_row_style()
Style entire rows based on column values. This is your primary tool for creating visual hierarchy—distinguishing headers from data rows, highlighting summaries, and showing nested structure through indentation.
```r
plot |> set_row_style(
type = "rtype", # "header", "data", "summary", "spacer"
bold = "rbold", # Column name or formula (~ pval < 0.05)
italic = "ritalic", # Column name or formula
color = "rcolor", # Column name or formula (CSS colors)
bg = "rbg", # Column name or formula (background colors)
badge = "rbadge", # Column name or formula (badge text)
icon = "ricon", # Column name or formula (emoji/unicode)
indent = "rindent" # Column name or formula (indent levels: 0, 1, 2, ...)
)
```
**Example**:
```{r}
styled_data <- data.frame(
study = c("Primary", " CV Death", " MI", "Secondary", " Stroke"),
hr = c(NA, 0.82, 0.79, NA, 0.88),
lower = c(NA, 0.72, 0.68, NA, 0.74),
upper = c(NA, 0.94, 0.92, NA, 1.05),
rtype = c("header", "data", "data", "header", "data"),
rbold = c(TRUE, FALSE, FALSE, TRUE, FALSE),
rindent = c(0, 1, 1, 0, 1)
)
forest_plot(styled_data,
point = "hr", lower = "lower", upper = "upper", label = "study",
scale = "log", null_value = 1
) |>
set_row_style(type = "rtype", bold = "rbold", indent = "rindent") |>
set_theme("modern")
```
### set_marker_style()
Style interval markers (the points and lines representing effect estimates):
```r
plot |> set_marker_style(
color = "marker_color", # Column with CSS colors
shape = "marker_shape", # "square", "circle", "diamond", "triangle"
opacity = "marker_alpha", # Column with 0-1 values
size = "marker_size" # Column with size multipliers
)
```
**Example**:
```{r}
marker_data <- data.frame(
study = c("Trial A", "Trial B", "Trial C", "Trial D"),
hr = c(0.72, 0.95, 0.68, 1.15),
lower = c(0.55, 0.78, 0.52, 0.90),
upper = c(0.94, 1.15, 0.89, 1.47),
direction = c("benefit", "neutral", "benefit", "harm")
) |>
mutate(
marker_color = case_when(
upper < 1 ~ "#16a34a", # Green for benefit
lower > 1 ~ "#dc2626", # Red for harm
TRUE ~ "#64748b" # Gray for neutral
),
marker_shape = ifelse(direction == "benefit", "circle", "square")
)
forest_plot(marker_data,
point = "hr", lower = "lower", upper = "upper", label = "study",
scale = "log", null_value = 1
) |>
set_marker_style(color = "marker_color", shape = "marker_shape") |>
set_theme("modern")
```
### set_column_style()
Style individual column cells (must be applied before rendering):
```r
plot |> set_column_style(column = "hr",
bold = "is_sig",
color = "hr_color",
bg = "hr_bg"
)
```
::: {.callout-tip}
## Styling Priority
When both row and column styles are set for the same cell:
1. Column styles take precedence
2. Row styles apply to cells without column overrides
This lets you set row-wide defaults with column-specific highlights.
:::
### set_theme() and Theme Modifiers
Apply or modify themes:
```r
# Apply by name
plot |> set_theme("jama")
# Apply by object
plot |> set_theme(web_theme_lancet())
# Modify theme properties inline
plot |> set_colors(primary = "#1a365d")
plot |> set_spacing(row_height = 24)
plot |> set_axis(gridlines = TRUE)
```
All theme modifiers work on both plot widgets and theme objects.
---
## Creating Multiple Variants
A powerful pattern is creating multiple plot versions from a common base:
```{r}
# Base plot
base_plot <- forest_plot(marker_data,
point = "hr", lower = "lower", upper = "upper", label = "study",
scale = "log", null_value = 1
)
# Variant 1: Color by significance
plot_colored <- base_plot |>
set_marker_style(color = "marker_color") |>
set_theme("modern")
# Variant 2: Shape by design
plot_shaped <- base_plot |>
set_marker_style(shape = "marker_shape") |>
set_theme("modern")
# Show one
plot_colored
```
Since each `set_*()` call returns a new object, `base_plot` remains unchanged and can be reused for each variant.
---
## Conditional Styling with dplyr
A powerful pattern is computing style columns with dplyr before plotting:
```{r}
analysis_data <- data.frame(
study = c("EMPA-REG", "CANVAS", "DECLARE", "DAPA-HF"),
hr = c(0.62, 0.86, 0.93, 0.74),
lower = c(0.49, 0.75, 0.84, 0.65),
upper = c(0.77, 0.97, 1.03, 0.85),
pval = c(0.0001, 0.02, 0.17, 0.0001)
)
styled_analysis <- analysis_data |>
mutate(
# Significance styling
is_sig = pval < 0.05,
sig_color = ifelse(is_sig, "#16a34a", "#94a3b8"),
# Effect direction
effect_dir = case_when(
upper < 1 ~ "benefit",
lower > 1 ~ "harm",
TRUE ~ "neutral"
)
)
forest_plot(styled_analysis,
point = "hr", lower = "lower", upper = "upper", label = "study",
columns = list(col_pvalue("pval")),
scale = "log", null_value = 1
) |>
set_marker_style(color = "sig_color") |>
set_row_style(bold = "is_sig") |>
set_theme("modern")
```
::: {.callout-tip}
## Style Column Naming Convention
Use consistent suffixes for style columns:
- `_bold` for bold flags
- `_color` for text colors
- `_bg` for backgrounds
- `_badge` for badge text
This makes it clear which columns are data vs styling.
:::
---
## Reusable Styling Pipelines
Create functions that encapsulate your organization's styling standards:
```{r}
#| eval: false
# Define a standard styling pipeline
apply_clinical_style <- function(plot) {
plot |>
set_marker_style(color = "sig_color") |>
set_row_style(bold = "is_sig") |>
set_theme("jama")
}
# Use it consistently across analyses
forest_plot(trial_a, ...) |> apply_clinical_style()
forest_plot(trial_b, ...) |> apply_clinical_style()
forest_plot(pooled, ...) |> apply_clinical_style()
```
---
## Theme Customization Example
Create a customized theme and apply it:
```{r}
# Create a customized theme
custom_theme <- web_theme_lancet() |>
set_axis(gridlines = TRUE) |>
set_spacing(row_height = 32)
# Apply to plot
forest_plot(analysis_data[1:3, ],
point = "hr", lower = "lower", upper = "upper", label = "study",
scale = "log", null_value = 1
) |>
set_theme(custom_theme)
```
---
## Function Reference
| Function | Purpose |
|----------|---------|
| `set_row_style()` | Row-level styling (bold, italic, color, type, indent) |
| `set_marker_style()` | Interval marker styling (color, shape, opacity, size) |
| `set_column_style()` | Per-cell column styling |
| `set_theme()` | Apply theme by name or object |
| `set_colors()` | Modify color palette |
| `set_typography()` | Modify fonts |
| `set_spacing()` | Modify spacing |
| `set_shapes()` | Modify shapes |
| `set_axis()` | Modify axis settings |
| `set_layout()` | Modify layout |
| `set_group_headers()` | Modify group header hierarchy |
| `set_effect_colors()` | Set multi-effect color palette |
| `set_marker_shapes()` | Set multi-effect shape palette |
See [Styling Reference](../reference/styling.qmd) for complete parameter documentation.