This chapter covers the core features for building forest plots: data mapping, scales, multiple effects, markers, axis control, interactivity, and exporting.

Data Mapping

webforest uses a column-mapping pattern: you specify which columns contain your data, not the values themselves.

Code
# Your data can have any column names
data <- data.frame(
  study = c("Smith 2020", "Jones 2021", "Lee 2022", "Chen 2023"),
  my_hr = c(0.72, 0.85, 0.91, 0.68),
  ci_lo = c(0.55, 0.70, 0.75, 0.52),
  ci_hi = c(0.95, 1.03, 1.10, 0.89)
)

# Just map them to the right arguments
forest_plot(data,
  point = "my_hr",
  lower = "ci_lo",
  upper = "ci_hi",
  label = "study",
  null_value = 1, scale = "log"
)

Required Mappings

Every forest plot needs these three column mappings:

Argument What it is Example
point Point estimate Hazard ratio, odds ratio, mean difference
lower Lower CI bound 95% CI lower
upper Upper CI bound 95% CI upper

The label argument is optional but almost always used to identify each row.

Handling Missing Values (NA)

webforest uses NA values strategically for structured layouts:

  • Header rows: row_type = "header" with NA for effect estimates
  • Spacer rows: row_type = "spacer" for visual separation
  • Missing estimates: Data rows where effect couldn’t be calculated
Code
structured <- data.frame(
  label = c("Primary Outcomes", "  CV Death", "  MI", "", "Secondary"),
  hr = c(NA, 0.82, 0.79, NA, NA),
  lower = c(NA, 0.72, 0.68, NA, NA),
  upper = c(NA, 0.94, 0.92, NA, NA),
  rtype = c("header", "data", "data", "spacer", "header"),
  rbold = c(TRUE, FALSE, FALSE, FALSE, TRUE)
)

forest_plot(structured,
  point = "hr", lower = "lower", upper = "upper", label = "label",
  row_type = "rtype", row_bold = "rbold",
  scale = "log", null_value = 1
)

Scale and Null Value

Log Scale

Use for ratios (odds ratio, hazard ratio, risk ratio) where effects are multiplicative:

Code
forest_plot(data,
  point = "my_hr", lower = "ci_lo", upper = "ci_hi", label = "study",
  scale = "log",
  null_value = 1,  # Ratio of 1 = no effect
  axis_label = "Hazard Ratio (95% CI)"
)
WarningLog Scale Requires Positive Values

All values in point, lower, and upper must be positive. Zero or negative values will cause errors.

Linear Scale

Use for differences (mean difference, risk difference, SMD) where effects are additive:

Code
diff_data <- data.frame(
  comparison = c("Treatment A", "Treatment B", "Treatment C"),
  mean_diff = c(-2.5, 1.3, -0.8),
  lower = c(-4.1, -0.2, -2.1),
  upper = c(-0.9, 2.8, 0.5)
)

forest_plot(diff_data,
  point = "mean_diff", lower = "lower", upper = "upper",
  label = "comparison",
  scale = "linear",
  null_value = 0,  # Difference of 0 = no effect
  axis_label = "Mean Difference (95% CI)"
)

Multiple Effects

Display multiple effect estimates per row—useful for comparing analyses or outcomes.

Code
multi_data <- data.frame(
  study = c("PIONEER", "SUMMIT", "HORIZON"),
  n = c(2450, 1890, 3200),
  # ITT analysis
  itt_or = c(0.72, 0.78, 0.65),
  itt_lo = c(0.58, 0.64, 0.52),
  itt_hi = c(0.89, 0.95, 0.81),
  # Per-Protocol analysis
  pp_or = c(0.68, 0.73, 0.61),
  pp_lo = c(0.53, 0.58, 0.47),
  pp_hi = c(0.87, 0.92, 0.79)
)

forest_plot(multi_data,
  point = "itt_or", lower = "itt_lo", upper = "itt_hi",
  label = "study",
  effects = list(
    web_effect("itt_or", "itt_lo", "itt_hi", label = "ITT", color = "#2563eb"),
    web_effect("pp_or", "pp_lo", "pp_hi", label = "Per-Protocol", color = "#16a34a")
  ),
  columns = list(
    col_n("n"),
    col_interval("Primary OR")
  ),
  scale = "log", null_value = 1,
  title = "Sensitivity Analysis Comparison"
)

web_effect() Arguments

Argument Description
point Column name for point estimates
lower Column name for lower bounds
upper Column name for upper bounds
label Display label for legend
color Override color for this effect
shape Override shape: "square", "circle", "diamond", "triangle"

Marker Customization

Customize marker appearance based on data values using formula expressions:

Code
marker_data <- data.frame(
  study = c("RCT-001", "RCT-002", "OBS-001", "OBS-002"),
  design = c("RCT", "RCT", "Observational", "Observational"),
  hr = c(0.72, 0.68, 0.82, 0.78),
  lower = c(0.58, 0.52, 0.68, 0.64),
  upper = c(0.89, 0.89, 0.99, 0.95),
  pvalue = c(0.002, 0.001, 0.045, 0.022)
)

forest_plot(marker_data,
  point = "hr", lower = "lower", upper = "upper",
  label = "study",
  # Formula expressions - no pre-computed columns needed
  marker_shape = ~ ifelse(design == "RCT", "circle", "square"),
  marker_color = ~ ifelse(pvalue < 0.05, "#16a34a", "#94a3b8"),
  marker_opacity = ~ ifelse(pvalue < 0.01, 1, 0.7),
  scale = "log", null_value = 1,
  title = "Marker Styling with Formulas"
)

Marker Arguments

Argument Value Type Effect
marker_color string CSS color for marker fill
marker_shape string Shape: "square", "circle", "diamond", "triangle"
marker_opacity numeric Transparency (0–1)
marker_size numeric Size multiplier (1 = default)

All marker arguments accept either a column name or a formula expression (~ ...).

TipAdvanced: Pipe-Based Workflows

Marker styling can also be done with set_marker_style(). See The Fluent API.


Axis Control

Axis Range

By default, the axis range is auto-calculated. Override with axis_range:

Code
forest_plot(data,
  point = "my_hr", lower = "ci_lo", upper = "ci_hi", label = "study",
  scale = "log", null_value = 1,
  axis_range = c(0.3, 2.0)
)

Custom Tick Values

Specify exact tick positions with axis_ticks:

Code
forest_plot(data,
  point = "my_hr", lower = "ci_lo", upper = "ci_hi", label = "study",
  scale = "log", null_value = 1,
  axis_range = c(0.3, 2.0),
  axis_ticks = c(0.5, 0.7, 1.0, 1.5, 2.0)
)

Gridlines

Add vertical gridlines at tick positions:

Code
forest_plot(data,
  point = "my_hr", lower = "ci_lo", upper = "ci_hi", label = "study",
  scale = "log", null_value = 1,
  axis_gridlines = TRUE
)

Axis Summary

Parameter Description Example
axis_range Min/max axis values c(0.3, 2.0)
axis_ticks Custom tick positions c(0.5, 1, 2)
axis_gridlines Show vertical gridlines TRUE
axis_label X-axis label "Odds Ratio"

Reference Lines

The null_value parameter draws a dashed vertical reference line. Add custom reference lines with forest_refline():

Code
forest_plot(data,
  point = "my_hr", lower = "ci_lo", upper = "ci_hi", label = "study",
  scale = "log", null_value = 1,
  annotations = list(
    forest_refline(0.8, label = "Threshold", style = "dashed", color = "#e11d48")
  ),
  title = "With Custom Reference Line"
)

forest_refline() Arguments

Argument Description
value X-axis position
label Optional label text
style "solid", "dashed", "dotted"
color Line color

Titles and Labels

Code
forest_plot(data,
  point = "my_hr", lower = "ci_lo", upper = "ci_hi", label = "study",
  scale = "log", null_value = 1,
  title = "Treatment Effect on Primary Outcome",
  subtitle = "Randomized controlled trials, 2020-2024",
  caption = "HR < 1 favors treatment",
  footnote = "Random effects meta-analysis",
  axis_label = "Hazard Ratio (95% CI)"
)
Argument Position
title Top
subtitle Below title
axis_label Below axis
caption Bottom left
footnote Bottom right

Interactivity

NoteInteractive by Default

All webforest plots include these interactions out of the box:

  • Hover highlighting on rows
  • Click to select rows
  • Column sorting via header clicks
  • Column resizing via drag
  • Group collapsing via chevron clicks
  • Download button on hover (top-right)

Hover Tooltips

Tooltips are opt-in. Specify fields to show when hovering over interval markers:

Code
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",
  scale = "log", null_value = 1,
  interaction = web_interaction(
    tooltip_fields = c("n", "events", "pvalue")
  ),
  footnote = "Hover over markers to see tooltip"
)

Interaction Controls

web_interaction(
  tooltip_fields = NULL,    # Column names for tooltip
  enable_sort = TRUE,       # Click headers to sort
  enable_collapse = TRUE,   # Click group headers to collapse
  enable_select = TRUE,     # Click rows to select
  enable_hover = TRUE,      # Highlight on hover
  enable_resize = TRUE,     # Drag column borders
  enable_export = TRUE,     # Download button
  enable_themes = "default" # Theme selection menu
)

Interaction Presets

Preset Description
web_interaction() Full interactivity (default)
web_interaction_minimal() Hover only—no sorting/selection
web_interaction_publication() Fully static (for print)
Code
# Publication-ready static output
forest_plot(data, ...,
  interaction = web_interaction_publication()
)

Zoom & Sizing

Argument Options Description
zoom 0.5 to 2.0 Initial zoom level (default 1.0)
auto_fit TRUE/FALSE Shrink to fit container if too large (default TRUE)
max_width pixels or NULL Maximum container width
max_height pixels or NULL Maximum container height (enables scrolling)

Exporting Plots

webforest supports exporting to SVG, PNG, and PDF.

Using save_plot()

# Create a plot
p <- forest_plot(data,
  point = "hr", lower = "lower", upper = "upper", label = "study",
  scale = "log", null_value = 1
)

# Save as SVG (vector)
save_plot(p, "forest_plot.svg")

# Save as PNG (raster)
save_plot(p, "forest_plot.png", width = 1200)

# Save as PDF
save_plot(p, "forest_plot.pdf", width = 800)

Output Formats

Format Extension Use Case Dependencies
SVG .svg Vector graphics, web, editing None
PNG .png Documents, slides rsvg package
PDF .pdf Print, publications rsvg package

Options

Argument Description Default
width Canvas width in pixels 800
height Auto-calculated if NULL NULL
scale PNG resolution multiplier 2

Publication-Quality Figures

# High-resolution PNG at 300 DPI equivalent
# 800px × 3 = 2400px for ~8 inch width at 300 DPI
save_plot(p, "figure1.png", width = 800, scale = 3)

Browser Export

Interactive plots include a download button (visible on hover in top-right):

  1. Hover over plot to reveal button
  2. Click to open format menu
  3. Choose SVG or PNG

Data Validation Tips

Before plotting, validate your data:

Code
validate_forest_data <- function(data, point, lower, upper, scale = "linear") {
  p <- data[[point]]
  l <- data[[lower]]
  u <- data[[upper]]
  valid <- !is.na(p)

  issues <- character()

  if (scale == "log" && any(p[valid] <= 0 | l[valid] <= 0 | u[valid] <= 0)) {
    issues <- c(issues, "Log scale requires all positive values")
  }

  if (any(l[valid] > p[valid] | p[valid] > u[valid])) {
    issues <- c(issues, "CI bounds should satisfy: lower <= point <= upper")
  }

  if (length(issues) == 0) {
    message("Data looks valid!")
  } else {
    warning(paste(issues, collapse = "\n"))
  }
}

Working with Meta-Analysis Results

From metafor

Code
library(metafor)

# Run meta-analysis
res <- rma(yi = log_or, sei = se, data = studies, method = "REML")

# Convert to webforest format
forest_data <- studies |>
  mutate(
    or = exp(log_or),
    lower = exp(log_or - 1.96 * se),
    upper = exp(log_or + 1.96 * se)
  ) |>
  bind_rows(
    tibble(
      study = "Pooled Estimate",
      or = exp(res$b),
      lower = exp(res$ci.lb),
      upper = exp(res$ci.ub),
      rtype = "summary",
      rbold = TRUE
    )
  )

forest_plot(forest_data,
  point = "or", lower = "lower", upper = "upper",
  label = "study",
  row_type = "rtype", row_bold = "rbold",
  scale = "log", null_value = 1
)

See Also