Code
# Add semantic styling columns to your data
data <- data |>
mutate(
is_primary = endpoint == "Primary",
is_secondary = grepl("Secondary", endpoint)
)
tabviz(data, ...,
row_emphasis = "is_primary",
row_muted = "is_secondary"
)This guide covers per-cell styling, which allows you to apply different formatting to individual cells based on data values.
tabviz applies styles in order of specificity:
row_bold, row_color) - apply to entire rowscol_*(bold = ...)) - override for specific cellsMore specific styles override less specific ones. This lets you set row-wide defaults and override individual cells.
tabviz offers two levels of styling:
| Level | Parameters | Scope | Use When |
|---|---|---|---|
| Row | row_bold, row_color, row_indent, row_badge |
Entire row | Headers, summaries, highlighting rows |
| Cell | bold, color, bg, badge, icon on columns |
Specific cells | Conditional formatting, significance |
Cell styling takes precedence over row styling when both are specified.
For quick conditional formatting, use semantic styling classes that apply theme-appropriate styles:
| Parameter | Effect | Use For |
|---|---|---|
row_emphasis |
Bold text, darker color | Key results, primary endpoints |
row_muted |
Lighter color, reduced prominence | Secondary results, supporting data |
row_accent |
Theme accent color | Highlighted findings, special rows |
These semantic styles use theme colors automatically, so they adapt when switching themes.
Every col_*() function accepts these styling parameters:
| Parameter | Type | Description |
|---|---|---|
bold |
Column name or formula | Bold text when TRUE |
italic |
Column name or formula | Italic text when TRUE |
color |
Column name or formula | Text color |
bg |
Column name or formula | Background color |
badge |
Column name or formula | Badge label |
icon |
Column name or formula | Emoji/unicode icon |
emphasis |
Column name or formula | Theme-aware emphasis (bold + foreground) |
muted |
Column name or formula | Theme-aware muted styling |
accent |
Column name or formula | Theme-aware accent styling |
Each parameter accepts either:
~ ...): Evaluated on the fly to compute stylingInstead of pre-computing style columns, you can use formulas that reference your data. For cell-level styling, use .x to refer to the column’s own values:
# Data without any pre-computed style columns
pval_demo <- data.frame(
study = c("Trial A", "Trial B", "Trial C", "Trial D"),
hr = c(0.72, 0.85, 0.91, 0.68),
lower = c(0.55, 0.70, 0.75, 0.52),
upper = c(0.95, 1.03, 1.10, 0.89),
pval = c(0.001, 0.1, 0.3, 0.02)
)
tabviz(pval_demo,
label = "study",
columns = list(
col_pvalue("pval",
# .x refers to the pval column values
bold = ~ .x < 0.05,
color = ~ ifelse(.x < 0.05, "#16a34a", "#71717a")
),
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
)
).x PronounWhen using formulas in col_*() functions:
.x refers to the current column’s values| Approach | Best For |
|---|---|
Formulas (~ .x < 0.05) |
Simple conditions, self-referential logic, one-off styling |
Column names ("is_sig") |
Complex logic computed separately, reused across plots |
# Data with styling columns
data <- data.frame(
study = c("ACCORD", "ADVANCE", "SPRINT", "ONTARGET"),
hr = c(0.65, 1.15, 0.82, 0.98),
lower = c(0.50, 0.95, 0.68, 0.85),
upper = c(0.85, 1.40, 0.99, 1.14),
pval = c(0.002, 0.18, 0.04, 0.62),
# Styling columns (computed from data)
sig_bold = c(TRUE, FALSE, TRUE, FALSE), # Bold if p < 0.05
dir_color = c("#22c55e", "#ef4444", "#22c55e", "#71717a") # Green/red/gray
)
tabviz(data,
label = "study",
columns = list(
col_numeric("hr", "HR", color = "dir_color", bold = "sig_bold"),
col_pvalue("pval", bold = "sig_bold"),
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
)
)styled <- data.frame(
study = c("Trial A", "Trial B", "Trial C", "Trial D"),
hr = c(0.72, 1.25, 0.88, 1.05),
lower = c(0.58, 1.02, 0.74, 0.92),
upper = c(0.89, 1.53, 1.05, 1.20)
) |>
mutate(
# Determine direction and significance
effect_color = case_when(
upper < 1 ~ "#16a34a", # Significant benefit (green)
lower > 1 ~ "#dc2626", # Significant harm (red)
TRUE ~ "#71717a" # Non-significant (gray)
)
)
tabviz(styled,
label = "study",
columns = list(
col_interval(point = "hr", lower = "lower", upper = "upper",
color = "effect_color"),
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
)
)trials <- data.frame(
study = c("EMPA-REG", "CANVAS", "DECLARE", "CREDENCE", "DAPA-HF"),
hr = c(0.62, 0.86, 0.93, 0.70, 0.74),
lower = c(0.49, 0.75, 0.84, 0.59, 0.65),
upper = c(0.77, 0.97, 1.03, 0.82, 0.85),
pval = c(0.0001, 0.02, 0.17, 0.0001, 0.0001)
) |>
mutate(
is_sig = pval < 0.05,
sig_bg = if_else(is_sig, "#fef3c7", NA_character_), # Yellow highlight
sig_badge = if_else(pval < 0.001, "***", if_else(pval < 0.01, "**", if_else(pval < 0.05, "*", NA_character_)))
)
tabviz(trials,
label = "study",
columns = list(
col_numeric("hr", "HR", bold = "is_sig", bg = "sig_bg"),
col_pvalue("pval", badge = "sig_badge"),
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
)
)outcomes <- data.frame(
endpoint = c("Primary", "CV Death", "HF Hosp", "All-cause Death"),
hr = c(0.74, 0.82, 0.65, 0.88),
lower = c(0.66, 0.72, 0.55, 0.76),
upper = c(0.83, 0.94, 0.77, 1.02)
) |>
mutate(
status = case_when(
upper < 1 ~ "benefit",
lower > 1 ~ "harm",
TRUE ~ "neutral"
),
icon = case_when(
status == "benefit" ~ "\u2705", # Green check
status == "harm" ~ "\u274C", # Red X
TRUE ~ "\u2796" # Dash
),
text_color = case_when(
status == "benefit" ~ "#16a34a",
status == "harm" ~ "#dc2626",
TRUE ~ "#525252"
)
)
tabviz(outcomes,
label = "endpoint",
columns = list(
col_text("icon", " ", width = 30),
col_numeric("hr", "HR", color = "text_color"),
col_interval(point = "hr", lower = "lower", upper = "upper",
color = "text_color"),
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
)
)Row styling applies to the entire row, while cell styling allows overrides for specific columns:
combined <- data.frame(
label = c("Primary Endpoints", " CV death or HF hosp", " CV death",
"Secondary Endpoints", " All-cause mortality"),
hr = c(NA, 0.74, 0.82, NA, 0.88),
lower = c(NA, 0.66, 0.72, NA, 0.76),
upper = c(NA, 0.83, 0.94, NA, 1.02),
# Row styling
rtype = c("header", "data", "data", "header", "data"),
rbold = c(TRUE, TRUE, FALSE, TRUE, FALSE),
rcolor = c("#2563eb", NA, NA, "#2563eb", NA),
rbadge = c(NA, "Primary", NA, NA, NA),
# Cell styling (overrides for specific cells)
hr_color = c(NA, "#16a34a", "#16a34a", NA, "#71717a")
)
tabviz(combined,
label = "label",
row_type = "rtype", row_bold = "rbold", row_color = "rcolor", row_badge = "rbadge",
columns = list(
col_numeric("hr", "HR", color = "hr_color"),
col_interval(point = "hr", lower = "lower", upper = "upper"),
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
)
)For fluent-style workflows, use set_column_style() after creating a WebSpec:
# Create spec then add styling
tabviz(data,
label = "study",
columns = list(
col_numeric("hr", "HR"),
col_interval(point = "hr", lower = "lower", upper = "upper"),
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
),
.spec_only = TRUE
) |>
set_column_style("hr", bold = "is_significant", color = "direction_color") |>
set_column_style("_interval", badge = "sig_stars") |>
tabviz()A typical workflow computes style columns with dplyr before plotting:
meta_results <- data.frame(
study = c("SHIFT", "PARADIGM-HF", "DAPA-HF", "EMPEROR-Reduced"),
drug_class = c("Ivabradine", "ARNI", "SGLT2i", "SGLT2i"),
hr = c(0.82, 0.80, 0.74, 0.75),
lower = c(0.75, 0.73, 0.65, 0.65),
upper = c(0.90, 0.87, 0.85, 0.86),
pval = c(0.0001, 0.0001, 0.0001, 0.0001)
)
styled_results <- meta_results |>
mutate(
# Significance styling
is_sig = pval < 0.05,
sig_stars = case_when(
pval < 0.001 ~ "***",
pval < 0.01 ~ "**",
pval < 0.05 ~ "*",
TRUE ~ NA_character_
),
# Effect direction
effect_color = case_when(
upper < 1 ~ "#16a34a",
lower > 1 ~ "#dc2626",
TRUE ~ "#71717a"
),
# Drug class styling
class_color = case_when(
drug_class == "SGLT2i" ~ "#2563eb",
drug_class == "ARNI" ~ "#7c3aed",
TRUE ~ "#525252"
)
)
tabviz(styled_results,
label = "study",
columns = list(
col_text("drug_class", "Class", color = "class_color"),
col_numeric("hr", "HR", bold = "is_sig", color = "effect_color"),
col_interval(point = "hr", lower = "lower", upper = "upper",
color = "effect_color"),
col_pvalue("pval", badge = "sig_stars"),
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
)
)Keep your data organized by using consistent naming:
| Style Type | Suggested Suffix | Example |
|---|---|---|
| Bold | _bold |
hr_bold, is_bold |
| Color | _color |
effect_color, pval_color |
| Background | _bg |
sig_bg, row_bg |
| Badge | _badge |
sig_badge, status_badge |
| Icon | _icon |
status_icon |
This makes it clear which columns are data vs styling, and helps when debugging formatting issues.
---
title: "Cell Styling"
---
```{r}
#| include: false
library(tabviz)
library(dplyr)
```
This guide covers per-cell styling, which allows you to apply different formatting to individual cells based on data values.
::: {.callout-tip}
## The Styling Hierarchy
tabviz applies styles in order of specificity:
1. **Theme defaults** (base colors, fonts)
2. **Row styles** (`row_bold`, `row_color`) - apply to entire rows
3. **Cell styles** (`col_*(bold = ...)`) - override for specific cells
More specific styles override less specific ones. This lets you set row-wide defaults and override individual cells.
:::
## Row vs Cell Styling
tabviz offers two levels of styling:
| Level | Parameters | Scope | Use When |
|-------|------------|-------|----------|
| **Row** | `row_bold`, `row_color`, `row_indent`, `row_badge` | Entire row | Headers, summaries, highlighting rows |
| **Cell** | `bold`, `color`, `bg`, `badge`, `icon` on columns | Specific cells | Conditional formatting, significance |
Cell styling takes precedence over row styling when both are specified.
## Semantic Row Styling
For quick conditional formatting, use semantic styling classes that apply theme-appropriate styles:
| Parameter | Effect | Use For |
|-----------|--------|---------|
| `row_emphasis` | Bold text, darker color | Key results, primary endpoints |
| `row_muted` | Lighter color, reduced prominence | Secondary results, supporting data |
| `row_accent` | Theme accent color | Highlighted findings, special rows |
```{r}
#| eval: false
# Add semantic styling columns to your data
data <- data |>
mutate(
is_primary = endpoint == "Primary",
is_secondary = grepl("Secondary", endpoint)
)
tabviz(data, ...,
row_emphasis = "is_primary",
row_muted = "is_secondary"
)
```
These semantic styles use theme colors automatically, so they adapt when switching themes.
## Cell Style Parameters
Every `col_*()` function accepts these styling parameters:
| Parameter | Type | Description |
|-----------|------|-------------|
| `bold` | Column name or formula | Bold text when TRUE |
| `italic` | Column name or formula | Italic text when TRUE |
| `color` | Column name or formula | Text color |
| `bg` | Column name or formula | Background color |
| `badge` | Column name or formula | Badge label |
| `icon` | Column name or formula | Emoji/unicode icon |
| `emphasis` | Column name or formula | Theme-aware emphasis (bold + foreground) |
| `muted` | Column name or formula | Theme-aware muted styling |
| `accent` | Column name or formula | Theme-aware accent styling |
Each parameter accepts either:
- A **column name** (string): The values in that column control styling per row
- A **formula expression** (`~ ...`): Evaluated on the fly to compute styling
## Formula Expressions for Cell Styling
Instead of pre-computing style columns, you can use formulas that reference your data. For cell-level styling, use `.x` to refer to the column's own values:
```{r}
# Data without any pre-computed style columns
pval_demo <- data.frame(
study = c("Trial A", "Trial B", "Trial C", "Trial D"),
hr = c(0.72, 0.85, 0.91, 0.68),
lower = c(0.55, 0.70, 0.75, 0.52),
upper = c(0.95, 1.03, 1.10, 0.89),
pval = c(0.001, 0.1, 0.3, 0.02)
)
tabviz(pval_demo,
label = "study",
columns = list(
col_pvalue("pval",
# .x refers to the pval column values
bold = ~ .x < 0.05,
color = ~ ifelse(.x < 0.05, "#16a34a", "#71717a")
),
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
)
)
```
### The `.x` Pronoun
When using formulas in `col_*()` functions:
- **`.x`** refers to the current column's values
- You can also reference other columns by name
```{r}
#| eval: false
# .x is the column's own values
col_pvalue("pval", bold = ~ .x < 0.05)
# You can also reference other columns
col_numeric("hr", color = ~ ifelse(pval < 0.05, "green", "gray"))
```
### When to Use Formulas vs Columns
| Approach | Best For |
|----------|----------|
| **Formulas** (`~ .x < 0.05`) | Simple conditions, self-referential logic, one-off styling |
| **Column names** (`"is_sig"`) | Complex logic computed separately, reused across plots |
::: {.callout-tip}
## Formula Advantage
Formulas keep styling logic close to where it's used, making your code more readable:
```r
# Without formulas - need to pre-compute
data <- data |> mutate(pval_bold = pval < 0.05)
col_pvalue("pval", bold = "pval_bold")
# With formulas - logic inline
col_pvalue("pval", bold = ~ .x < 0.05)
```
:::
## Basic Example
```{r}
# Data with styling columns
data <- data.frame(
study = c("ACCORD", "ADVANCE", "SPRINT", "ONTARGET"),
hr = c(0.65, 1.15, 0.82, 0.98),
lower = c(0.50, 0.95, 0.68, 0.85),
upper = c(0.85, 1.40, 0.99, 1.14),
pval = c(0.002, 0.18, 0.04, 0.62),
# Styling columns (computed from data)
sig_bold = c(TRUE, FALSE, TRUE, FALSE), # Bold if p < 0.05
dir_color = c("#22c55e", "#ef4444", "#22c55e", "#71717a") # Green/red/gray
)
tabviz(data,
label = "study",
columns = list(
col_numeric("hr", "HR", color = "dir_color", bold = "sig_bold"),
col_pvalue("pval", bold = "sig_bold"),
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
)
)
```
## Conditional Formatting Patterns
### Color by Effect Direction
```{r}
styled <- data.frame(
study = c("Trial A", "Trial B", "Trial C", "Trial D"),
hr = c(0.72, 1.25, 0.88, 1.05),
lower = c(0.58, 1.02, 0.74, 0.92),
upper = c(0.89, 1.53, 1.05, 1.20)
) |>
mutate(
# Determine direction and significance
effect_color = case_when(
upper < 1 ~ "#16a34a", # Significant benefit (green)
lower > 1 ~ "#dc2626", # Significant harm (red)
TRUE ~ "#71717a" # Non-significant (gray)
)
)
tabviz(styled,
label = "study",
columns = list(
col_interval(point = "hr", lower = "lower", upper = "upper",
color = "effect_color"),
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
)
)
```
### Highlight Significant Results
```{r}
trials <- data.frame(
study = c("EMPA-REG", "CANVAS", "DECLARE", "CREDENCE", "DAPA-HF"),
hr = c(0.62, 0.86, 0.93, 0.70, 0.74),
lower = c(0.49, 0.75, 0.84, 0.59, 0.65),
upper = c(0.77, 0.97, 1.03, 0.82, 0.85),
pval = c(0.0001, 0.02, 0.17, 0.0001, 0.0001)
) |>
mutate(
is_sig = pval < 0.05,
sig_bg = if_else(is_sig, "#fef3c7", NA_character_), # Yellow highlight
sig_badge = if_else(pval < 0.001, "***", if_else(pval < 0.01, "**", if_else(pval < 0.05, "*", NA_character_)))
)
tabviz(trials,
label = "study",
columns = list(
col_numeric("hr", "HR", bold = "is_sig", bg = "sig_bg"),
col_pvalue("pval", badge = "sig_badge"),
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
)
)
```
### Traffic Light Styling
```{r}
outcomes <- data.frame(
endpoint = c("Primary", "CV Death", "HF Hosp", "All-cause Death"),
hr = c(0.74, 0.82, 0.65, 0.88),
lower = c(0.66, 0.72, 0.55, 0.76),
upper = c(0.83, 0.94, 0.77, 1.02)
) |>
mutate(
status = case_when(
upper < 1 ~ "benefit",
lower > 1 ~ "harm",
TRUE ~ "neutral"
),
icon = case_when(
status == "benefit" ~ "\u2705", # Green check
status == "harm" ~ "\u274C", # Red X
TRUE ~ "\u2796" # Dash
),
text_color = case_when(
status == "benefit" ~ "#16a34a",
status == "harm" ~ "#dc2626",
TRUE ~ "#525252"
)
)
tabviz(outcomes,
label = "endpoint",
columns = list(
col_text("icon", " ", width = 30),
col_numeric("hr", "HR", color = "text_color"),
col_interval(point = "hr", lower = "lower", upper = "upper",
color = "text_color"),
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
)
)
```
## Combining Row and Cell Styling
Row styling applies to the entire row, while cell styling allows overrides for specific columns:
```{r}
combined <- data.frame(
label = c("Primary Endpoints", " CV death or HF hosp", " CV death",
"Secondary Endpoints", " All-cause mortality"),
hr = c(NA, 0.74, 0.82, NA, 0.88),
lower = c(NA, 0.66, 0.72, NA, 0.76),
upper = c(NA, 0.83, 0.94, NA, 1.02),
# Row styling
rtype = c("header", "data", "data", "header", "data"),
rbold = c(TRUE, TRUE, FALSE, TRUE, FALSE),
rcolor = c("#2563eb", NA, NA, "#2563eb", NA),
rbadge = c(NA, "Primary", NA, NA, NA),
# Cell styling (overrides for specific cells)
hr_color = c(NA, "#16a34a", "#16a34a", NA, "#71717a")
)
tabviz(combined,
label = "label",
row_type = "rtype", row_bold = "rbold", row_color = "rcolor", row_badge = "rbadge",
columns = list(
col_numeric("hr", "HR", color = "hr_color"),
col_interval(point = "hr", lower = "lower", upper = "upper"),
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
)
)
```
## Using set_column_style()
For fluent-style workflows, use `set_column_style()` after creating a WebSpec:
```{r}
#| eval: false
# Create spec then add styling
tabviz(data,
label = "study",
columns = list(
col_numeric("hr", "HR"),
col_interval(point = "hr", lower = "lower", upper = "upper"),
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
),
.spec_only = TRUE
) |>
set_column_style("hr", bold = "is_significant", color = "direction_color") |>
set_column_style("_interval", badge = "sig_stars") |>
tabviz()
```
## dplyr Workflow for Style Columns
A typical workflow computes style columns with dplyr before plotting:
```{r}
meta_results <- data.frame(
study = c("SHIFT", "PARADIGM-HF", "DAPA-HF", "EMPEROR-Reduced"),
drug_class = c("Ivabradine", "ARNI", "SGLT2i", "SGLT2i"),
hr = c(0.82, 0.80, 0.74, 0.75),
lower = c(0.75, 0.73, 0.65, 0.65),
upper = c(0.90, 0.87, 0.85, 0.86),
pval = c(0.0001, 0.0001, 0.0001, 0.0001)
)
styled_results <- meta_results |>
mutate(
# Significance styling
is_sig = pval < 0.05,
sig_stars = case_when(
pval < 0.001 ~ "***",
pval < 0.01 ~ "**",
pval < 0.05 ~ "*",
TRUE ~ NA_character_
),
# Effect direction
effect_color = case_when(
upper < 1 ~ "#16a34a",
lower > 1 ~ "#dc2626",
TRUE ~ "#71717a"
),
# Drug class styling
class_color = case_when(
drug_class == "SGLT2i" ~ "#2563eb",
drug_class == "ARNI" ~ "#7c3aed",
TRUE ~ "#525252"
)
)
tabviz(styled_results,
label = "study",
columns = list(
col_text("drug_class", "Class", color = "class_color"),
col_numeric("hr", "HR", bold = "is_sig", color = "effect_color"),
col_interval(point = "hr", lower = "lower", upper = "upper",
color = "effect_color"),
col_pvalue("pval", badge = "sig_stars"),
viz_forest(point = "hr", lower = "lower", upper = "upper",
scale = "log", null_value = 1)
)
)
```
## Style Column Naming Conventions
Keep your data organized by using consistent naming:
| Style Type | Suggested Suffix | Example |
|------------|-----------------|---------|
| Bold | `_bold` | `hr_bold`, `is_bold` |
| Color | `_color` | `effect_color`, `pval_color` |
| Background | `_bg` | `sig_bg`, `row_bg` |
| Badge | `_badge` | `sig_badge`, `status_badge` |
| Icon | `_icon` | `status_icon` |
This makes it clear which columns are data vs styling, and helps when debugging formatting issues.