flowchart TB
A["Theme defaults"] --> B["Row styling"]
B --> C["Cell styling"]
C --> D["Marker styling"]
D --> E["Final appearance"]
style A fill:#e5e7eb
style B fill:#93c5fd
style C fill:#6ee7b7
style D fill:#fbbf24
style E fill:#4f46e5,color:#fff
Styling Cascade
tabviz applies styles in layers, from general to specific. Understanding this cascade helps you achieve precise control over your visualization’s appearance.
The Cascade
Later styles override earlier ones. A cell style beats a row style, which beats a theme default.
Layer 1: Theme Defaults
Themes establish the baseline appearance for all elements:
Code
# Theme sets default colors, fonts, spacing
tabviz(data,
columns = list(viz_forest(point = "hr", lower = "lo", upper = "hi",
scale = "log", null_value = 1)),
theme = web_theme_jama() # Sets primary color, font, spacing, etc.
)Theme defaults include: - Colors: Primary, accent, text, background - Typography: Font family, sizes, weights - Spacing: Row height, padding, margins - Shapes: Point size, line width
Modifying Theme Defaults
Code
web_theme_jama() |>
set_colors(primary = "#2563eb") |>
set_typography(font_size_base = "0.9rem") |>
set_spacing(row_height = 28)Layer 2: Row Styling
Row styling applies to entire rows, overriding theme defaults:
Code
meta_data <- data.frame(
study = c("Primary Endpoint", " Subgroup A", " Subgroup B", "Summary"),
hr = c(NA, 0.72, 0.85, 0.78),
lo = c(NA, 0.55, 0.70, 0.65),
hi = c(NA, 0.95, 1.03, 0.93),
rtype = c("header", "data", "data", "summary"),
rbold = c(TRUE, FALSE, FALSE, TRUE)
)
tabviz(meta_data,
label = "study",
columns = list(viz_forest(point = "hr", lower = "lo", upper = "hi",
scale = "log", null_value = 1)),
row_type = "rtype",
row_bold = "rbold"
)Row styling options:
| Argument | Effect |
|---|---|
row_type |
Controls row behavior (header, data, summary, spacer) |
row_bold |
Bold text |
row_italic |
Italic text |
row_color |
Text color |
row_bg |
Background color |
row_indent |
Indentation level |
row_emphasis |
Theme-aware emphasis |
row_muted |
Theme-aware de-emphasis |
row_accent |
Theme accent color |
Layer 3: Cell Styling
Cell styling targets specific columns, overriding row styles:
Code
tabviz(data,
label = "study",
columns = list(
col_pvalue("pval",
bold = ~ .x < 0.05, # Bold if p < 0.05
color = ~ ifelse(.x < 0.05, "#16a34a", "#64748b")
),
col_numeric("hr",
color = ~ ifelse(.x < 1, "#16a34a", "#dc2626")
),
viz_forest(point = "hr", lower = "lo", upper = "hi",
scale = "log", null_value = 1)
)
)Cell styling uses .x to reference the column’s own values:
# In cell context, .x = the column's value for that row
col_numeric("hr", color = ~ ifelse(.x < 1, "green", "red"))
# Equivalent to:
# for each row: if (row$hr < 1) "green" else "red"set_column_style()
Apply styling to any column after creation:
Code
tabviz(data, label = "study", columns = list(...)) |>
set_column_style("hr",
bold = ~ .x < 1,
color = ~ case_when(
.x < 0.8 ~ "#16a34a",
.x < 1.0 ~ "#86efac",
TRUE ~ "#64748b"
)
)Layer 4: Marker Styling
Marker styling controls the forest plot points and lines, overriding all other layers:
Code
library(dplyr)
marker_data <- data.frame(
study = c("Trial A", "Trial B", "Trial C", "Trial D"),
hr = c(0.72, 0.95, 0.68, 1.15),
lo = c(0.55, 0.78, 0.52, 0.90),
hi = c(0.94, 1.15, 0.89, 1.47),
design = c("RCT", "RCT", "Observational", "Observational")
) |>
mutate(
marker_color = case_when(
hi < 1 ~ "#16a34a",
lo > 1 ~ "#dc2626",
TRUE ~ "#64748b"
),
marker_shape = ifelse(design == "RCT", "circle", "square")
)
tabviz(marker_data,
label = "study",
columns = list(viz_forest(point = "hr", lower = "lo", upper = "hi",
scale = "log", null_value = 1)),
marker_color = "marker_color",
marker_shape = "marker_shape"
)Marker styling options:
| Argument | Effect |
|---|---|
marker_color |
Fill color |
marker_shape |
Shape (circle, square, diamond, triangle) |
marker_opacity |
Transparency (0-1) |
marker_size |
Size multiplier |
Cascade Examples
Example 1: Theme + Row Override
Code
tabviz(data,
label = "study",
columns = list(viz_forest(point = "hr", lower = "lo", upper = "hi",
scale = "log", null_value = 1)),
theme = web_theme_jama(), # Sets default text color
row_color = ~ ifelse(pval < 0.05, "#16a34a", NULL) # Overrides for significant rows
)- Theme sets default text color (black)
- Row styling overrides to green for significant rows
- Non-significant rows keep theme default
Example 2: Row + Cell Override
Code
tabviz(data,
label = "study",
columns = list(
col_numeric("hr", color = ~ ifelse(.x < 1, "green", "red")) # Cell override
),
row_color = "#333333" # Row default
)- Row styling sets all text to dark gray
- Cell styling overrides HR column to green/red based on value
- Other columns keep the row style
Example 3: Full Cascade
Code
tabviz(data,
label = "study",
columns = list(
col_pvalue("pval", bold = ~ .x < 0.05),
viz_forest(point = "hr", lower = "lo", upper = "hi",
scale = "log", null_value = 1)
),
theme = web_theme_jama(), # Layer 1: Theme
row_bold = "is_summary", # Layer 2: Row
# col_pvalue bold is # Layer 3: Cell
marker_color = ~ ifelse(sig, "green", "gray") # Layer 4: Marker
) |>
set_colors(primary = "#2563eb") # Modifies Layer 1Debugging Styles
When styles don’t appear as expected:
- Check layer order: Is a later layer overriding?
- Check NA values: NA styling values fall through to the next layer
- Inspect formula results: Print
data |> mutate(result = ~ your_formula)to see evaluated values - Simplify: Remove styling layers one at a time to find conflicts
Code
# Debug a formula expression
library(dplyr)
data |>
mutate(
debug_bold = pval < 0.05, # See what row_bold = ~ pval < 0.05 produces
debug_color = ifelse(upper < 1, "green", "gray")
) |>
select(study, pval, upper, debug_bold, debug_color)