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:

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():

Code
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:

Code
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.

NoteWhen 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.

tabviz(data, ...) |>     # Create plot
  set_row_style(...) |>       # Modify rows
  set_marker_style(...) |>    # Modify markers
  set_theme(...)              # Apply theme
NoteImmutability 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 (~ ...):

# 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 and 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.

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:

Code
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):

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:

Code
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):

plot |> set_column_style(column = "hr",
  bold = "is_sig",
  color = "hr_color",
  bg = "hr_bg"
)
TipStyling 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:

# 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:

Code
# 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:

Code
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")
TipStyle 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:

Code
# 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:

Code
# 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 for complete parameter documentation.