---
title: "Cookbook"
subtitle: "Common tasks and patterns"
---
```{r}
#| include: false
library(webforest)
library(dplyr)
```
Quick recipes for common forest plot tasks. Each recipe is self-contained and can be adapted to your data.
## Add a Summary Row
Display an overall effect estimate as a diamond:
```{r}
meta_data <- data.frame(
study = c("Smith 2020", "Jones 2021", "Lee 2022", "Overall"),
hr = c(0.72, 0.85, 0.91, 0.82),
lower = c(0.55, 0.70, 0.75, 0.70),
upper = c(0.95, 1.03, 1.10, 0.96),
rtype = c("data", "data", "data", "summary"),
rbold = c(FALSE, FALSE, FALSE, TRUE)
)
forest_plot(meta_data,
point = "hr", lower = "lower", upper = "upper",
label = "study", null_value = 1, scale = "log",
row_type = "rtype", row_bold = "rbold",
theme = web_theme_modern()
)
```
## Customize Colors for JAMA
Adjust the JAMA theme for your journal's requirements:
```{r}
jama_custom <- web_theme_jama() |>
set_colors(
primary = "#000000", # Black for main elements
interval = "#000000", # Black markers
interval_line = "#333333" # Dark gray CI lines
) |>
set_typography(
font_family = "Arial, sans-serif" # Common journal font
)
forest_plot(meta_data[1:3, ],
point = "hr", lower = "lower", upper = "upper",
label = "study", null_value = 1, scale = "log",
theme = jama_custom
)
```
## Show Multiple Effects Per Row
Display ITT, Per-Protocol, and As-Treated analyses on the same row:
```{r}
multi_data <- data.frame(
study = c("Trial A", "Trial B", "Trial C"),
itt_or = c(0.79, 0.85, 0.72),
itt_lower = c(0.66, 0.71, 0.58),
itt_upper = c(0.95, 1.02, 0.89),
pp_or = c(0.75, 0.82, 0.68),
pp_lower = c(0.62, 0.68, 0.54),
pp_upper = c(0.91, 0.99, 0.86)
)
forest_plot(multi_data,
point = "itt_or", lower = "itt_lower", upper = "itt_upper",
label = "study", null_value = 1, scale = "log",
effects = list(
web_effect("itt_or", "itt_lower", "itt_upper",
label = "ITT", color = "#2563eb"),
web_effect("pp_or", "pp_lower", "pp_upper",
label = "Per-Protocol", color = "#16a34a")
),
theme = web_theme_modern(),
footnote = "Blue = ITT, Green = Per-Protocol"
)
```
## Export High-Resolution PNG
For journal submission at 300 DPI:
```r
spec <- web_spec(data,
point = "hr", lower = "lower", upper = "upper",
label = "study", null_value = 1, scale = "log",
theme = web_theme_jama()
)
# 800px * 3 = 2400px for ~8 inch width at 300 DPI
save_plot(spec, "figure1.png", width = 800, scale = 3)
```
## Format P-values
P-values are displayed with Unicode superscript notation for very small values (e.g., `1.2×10⁻⁵`). This keeps the display compact and readable:
```{r}
pval_data <- data.frame(
study = c("Alpha", "Beta", "Gamma", "Delta", "Epsilon"),
or = c(0.72, 0.95, 0.68, 1.02, 0.55),
lower = c(0.55, 0.78, 0.52, 0.84, 0.42),
upper = c(0.94, 1.16, 0.89, 1.24, 0.72),
pval = c(0.000012, 0.42, 0.0008, 0.78, 0.00000035)
)
forest_plot(pval_data,
point = "or", lower = "lower", upper = "upper",
label = "study", null_value = 1,
columns = list(
col_pvalue("pval", "P-value")
),
theme = web_theme_modern()
)
```
## Customize P-value Precision
Control the number of significant figures and when to switch to exponential notation:
```{r}
forest_plot(pval_data,
point = "or", lower = "lower", upper = "upper",
label = "study", null_value = 1,
columns = list(
col_pvalue("pval", "P-value",
digits = 3, # 3 significant figures
exp_threshold = 0.01 # Use exponential below 0.01
)
),
theme = web_theme_modern()
)
```
## Add Significance Stars
Enable stars for quick visual identification (`*` p<0.05, `**` p<0.01, `***` p<0.001):
```{r}
forest_plot(pval_data,
point = "or", lower = "lower", upper = "upper",
label = "study", null_value = 1,
columns = list(
col_pvalue("pval", "P-value", stars = TRUE)
),
theme = web_theme_modern()
)
```
## Create Nested Subgroups
Two-level hierarchy with region and country:
```{r}
nested_data <- data.frame(
study = c("Site A", "Site B", "Site C", "Site D", "Site E", "Site F"),
country = c("USA", "USA", "USA", "UK", "UK", "Germany"),
or = c(0.82, 0.91, 0.78, 0.85, 0.88, 0.79),
lower = c(0.68, 0.75, 0.62, 0.70, 0.72, 0.64),
upper = c(0.99, 1.10, 0.98, 1.03, 1.07, 0.98)
)
forest_plot(nested_data,
point = "or", lower = "lower", upper = "upper",
label = "study", group = "country",
null_value = 1, scale = "log",
theme = web_theme_modern()
)
```
## Add Weight Bars
Show study weights as horizontal bars:
```{r}
weighted_data <- data.frame(
study = c("Large RCT", "Medium RCT", "Small RCT"),
or = c(0.85, 0.78, 0.92),
lower = c(0.75, 0.62, 0.71),
upper = c(0.96, 0.98, 1.19),
weight = c(55, 30, 15)
)
forest_plot(weighted_data,
point = "or", lower = "lower", upper = "upper",
label = "study", null_value = 1,
weight = "weight", # Scale marker sizes by weight
columns = list(
col_bar("weight", "Weight %", width = 100)
),
theme = web_theme_modern()
)
```
## Format as Mean Difference
Use linear scale for continuous outcomes:
```{r}
md_data <- data.frame(
study = c("Trial 1", "Trial 2", "Trial 3"),
md = c(-2.5, -1.8, -3.2),
lower = c(-4.1, -3.5, -5.0),
upper = c(-0.9, -0.1, -1.4)
)
forest_plot(md_data,
point = "md", lower = "lower", upper = "upper",
label = "study",
null_value = 0, # 0 for differences
scale = "linear",
axis_label = "Mean Difference (95% CI)",
theme = web_theme_modern()
)
```
## Create Headers and Sections
Organize a complex meta-analysis with headers:
```{r}
sectioned_data <- data.frame(
study = c(
"Primary Outcomes", " Mortality", " CV Events", " Primary Subtotal",
"Secondary Outcomes", " Quality of Life", " Hospitalizations"
),
hr = c(NA, 0.78, 0.85, 0.82, NA, 0.92, 0.88),
lower = c(NA, 0.65, 0.72, 0.73, NA, 0.80, 0.76),
upper = c(NA, 0.94, 1.00, 0.92, NA, 1.06, 1.02),
rtype = c("header", "data", "data", "summary",
"header", "data", "data"),
rbold = c(TRUE, FALSE, FALSE, TRUE, TRUE, FALSE, FALSE),
rindent = c(0, 1, 1, 1, 0, 1, 1)
)
forest_plot(sectioned_data,
point = "hr", lower = "lower", upper = "upper",
label = "study", null_value = 1, scale = "log",
columns = list(col_interval("HR (95% CI)")),
row_type = "rtype", row_bold = "rbold", row_indent = "rindent",
theme = web_theme_modern()
)
```
## Highlight Specific Studies
Use badges and colors to draw attention:
```{r}
highlight_data <- data.frame(
study = c("Landmark Trial", "Study B", "Study C", "Study D"),
or = c(0.72, 0.85, 0.91, 0.88),
lower = c(0.60, 0.70, 0.75, 0.72),
upper = c(0.86, 1.03, 1.10, 1.07),
is_bold = c(TRUE, FALSE, FALSE, FALSE),
badge = c("Key", NA, NA, NA),
color = c("#2563eb", NA, NA, NA)
)
forest_plot(highlight_data,
point = "or", lower = "lower", upper = "upper",
label = "study", null_value = 1,
row_bold = "is_bold", row_badge = "badge", row_color = "color",
theme = web_theme_modern()
)
```
## Color Markers by Significance
Highlight significant results with green, non-significant with gray:
```{r}
sig_data <- data.frame(
study = c("Trial A", "Trial B", "Trial C", "Trial D"),
or = c(0.72, 0.95, 0.68, 1.05),
lower = c(0.55, 0.78, 0.52, 0.86),
upper = c(0.94, 1.15, 0.89, 1.28),
pval = c(0.008, 0.42, 0.002, 0.65)
)
sig_data$marker_color <- ifelse(sig_data$pval < 0.05, "#16a34a", "#94a3b8")
forest_plot(sig_data,
point = "or", lower = "lower", upper = "upper",
label = "study", null_value = 1, scale = "log",
marker_color = "marker_color",
columns = list(col_pvalue("pval", "P")),
theme = web_theme_modern(),
footnote = "Green = significant (p<0.05), Gray = non-significant"
)
```
## Different Shapes by Study Design
Use circles for RCTs and squares for observational studies:
```{r}
design_data <- data.frame(
study = c("RCT-001", "RCT-002", "OBS-001", "OBS-002"),
design = c("RCT", "RCT", "Observational", "Observational"),
or = c(0.72, 0.78, 0.85, 0.82),
lower = c(0.55, 0.62, 0.70, 0.68),
upper = c(0.94, 0.98, 1.03, 0.99)
)
design_data$marker_shape <- ifelse(design_data$design == "RCT", "circle", "square")
forest_plot(design_data,
point = "or", lower = "lower", upper = "upper",
label = "study", group = "design",
null_value = 1, scale = "log",
marker_shape = "marker_shape",
theme = web_theme_modern(),
footnote = "Circles = RCTs, Squares = Observational studies"
)
```
## Add Hover Tooltips
Show additional metadata when hovering over interval markers:
```{r}
tooltip_data <- data.frame(
study = c("EMPA-REG 2015", "CANVAS 2017", "DECLARE 2019"),
hr = c(0.62, 0.86, 0.93),
lower = c(0.49, 0.75, 0.84),
upper = c(0.77, 0.97, 1.03),
n = c(7020, 10142, 17160),
events = c(490, 585, 882),
pvalue = c(0.0001, 0.02, 0.17)
)
forest_plot(tooltip_data,
point = "hr", lower = "lower", upper = "upper",
label = "study", null_value = 1, scale = "log",
interaction = web_interaction(
tooltip_fields = c("n", "events", "pvalue")
),
theme = web_theme_modern(),
footnote = "Hover over markers to see tooltip"
)
```
Tooltips are opt-in and display the row label, estimate with CI, and any additional fields you specify.
## Use Dark Theme for Presentations
```{r}
forest_plot(meta_data[1:3, ],
point = "hr", lower = "lower", upper = "upper",
label = "study", null_value = 1, scale = "log",
theme = web_theme_dark(),
title = "Treatment Effect",
subtitle = "For presentation slides"
)
```
## Split by Subgroups
Create separate navigable plots for each subgroup value:
```{r}
set.seed(42)
subgroup_data <- data.frame(
study = paste0("Study ", 1:20),
region = sample(c("Americas", "Europe", "Asia"), 20, replace = TRUE),
or = exp(rnorm(20, log(0.8), 0.25)),
lower = NA, upper = NA
)
subgroup_data$lower <- subgroup_data$or * exp(-1.96 * 0.2)
subgroup_data$upper <- subgroup_data$or * exp(1.96 * 0.2)
forest_plot(subgroup_data,
point = "or", lower = "lower", upper = "upper",
label = "study",
split_by = "region", # Creates sidebar navigation
scale = "log", null_value = 1,
theme = web_theme_modern()
)
```
For hierarchical navigation (e.g., Sex > Age Group):
```r
forest_plot(data,
point = "or", lower = "lower", upper = "upper",
label = "study",
split_by = c("sex", "age_group"), # Nested tree
shared_axis = TRUE, # Same scale across all subgroups
scale = "log", null_value = 1
)
```
## Export Split Forest to Directory
Save all subgroup plots as separate files:
```r
split_result <- data |>
web_spec(point = "or", lower = "lower", upper = "upper", label = "study") |>
split_forest(by = c("sex", "age_group"))
# Creates: output/Male/Male_Young.svg, output/Male/Male_Old.svg, etc.
save_split_forest(split_result, "output", format = "svg")
```
## Apply Styling with the Fluent API
Use `set_*()` functions to modify plots after creation:
```{r}
step_data <- data.frame(
study = c("Trial A", "Trial B", "Trial C", "Overall"),
hr = c(0.72, 0.85, 0.91, 0.82),
lower = c(0.55, 0.70, 0.75, 0.68),
upper = c(0.95, 1.03, 1.10, 0.99),
is_summary = c(FALSE, FALSE, FALSE, TRUE),
sig_color = c("#16a34a", "#94a3b8", "#94a3b8", "#2563eb"),
study_shape = c("circle", "circle", "circle", "diamond")
)
forest_plot(step_data,
point = "hr", lower = "lower", upper = "upper",
label = "study", scale = "log", null_value = 1,
theme = web_theme_modern()
) |>
set_marker_style(color = "sig_color", shape = "study_shape") |>
set_row_style(bold = "is_summary")
```
## Create Plot Variants
Apply different styling to the same base plot:
```{r}
#| eval: false
base_plot <- forest_plot(data,
point = "hr", lower = "lower", upper = "upper",
label = "study", scale = "log", null_value = 1
)
# Variant 1: Colored by significance
base_plot |> set_marker_style(color = "sig_color")
# Variant 2: Shaped by study type
base_plot |> set_marker_style(shape = "study_shape")
# Variant 3: Both
base_plot |> set_marker_style(color = "sig_color", shape = "study_shape")
```