Meta-analyses, forest plots, and clinical trial visualizations.

JAMA-Style Forest Plot

Classic medical journal style with clean typography:

Code
data(glp1_trials)

tabviz(
  glp1_trials,
  label = "study",
  group = "group",
  columns = list(
    col_text("drug", "Drug"),
    col_n("n"),
    col_events("events", "n", "Events"),
    viz_forest(point = "hr", lower = "lower", upper = "upper",
               scale = "log", null_value = 1,
               axis_label = "Hazard Ratio (95% CI)"),
    col_interval("HR (95% CI)", point = "hr", lower = "lower", upper = "upper"),
    col_pvalue("pvalue", "P")
  ),
  row_type = "row_type",
  row_bold = "row_bold",
  theme = web_theme_jama(),
  title = "GLP-1 Agonist Cardiovascular Outcomes",
  subtitle = "Major adverse cardiovascular events (MACE)"
)
TipTheme Variations

The same data structure works with any theme. Try web_theme_lancet(), web_theme_nature(), or web_theme_cochrane() for different journal styles. See Feature Showcases for a visual comparison of all 9 built-in themes.


Subgroup Analysis

Forest plot with subgroup headers and indentation:

Code
subgroup_data <- tibble(
  label = c(
    "Overall Population", "",
    "Age",
    "  < 65 years", "  >= 65 years", "",
    "Sex",
    "  Male", "  Female", "",
    "Prior CV Disease",
    "  Yes", "  No"
  ),
  hr = c(0.78, NA, NA, 0.72, 0.85, NA, NA, 0.76, 0.82, NA, NA, 0.74, 0.81),
  lower = c(0.68, NA, NA, 0.58, 0.72, NA, NA, 0.64, 0.68, NA, NA, 0.61, 0.66),
  upper = c(0.89, NA, NA, 0.89, 1.00, NA, NA, 0.90, 0.99, NA, NA, 0.90, 0.99),
  n = c(15234, NA, NA, 7821, 7413, NA, NA, 9140, 6094, NA, NA, 8542, 6692),
  rtype = c("summary", "spacer", "header", "data", "data", "spacer",
            "header", "data", "data", "spacer", "header", "data", "data"),
  rbold = c(TRUE, FALSE, TRUE, FALSE, FALSE, FALSE,
            TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE)
)

tabviz(subgroup_data,
  label = "label",
  columns = list(
    col_n("n"),
    viz_forest(point = "hr", lower = "lower", upper = "upper",
               scale = "log", null_value = 1,
               axis_label = "Hazard Ratio"),
    col_interval("HR (95% CI)", point = "hr", lower = "lower", upper = "upper")
  ),
  row_type = "rtype",
  row_bold = "rbold",
  theme = web_theme_jama(),
  title = "Subgroup Analysis"
)

Multi-Arm Dose-Response

Compare three treatment arms across multiple endpoints using col_group to organize results:

Code
dose_response <- data.frame(
  endpoint = c("Overall Survival", "Progression-Free Survival",
               "Objective Response", "Quality of Life"),
  low_hr  = c(0.88, 0.82, 0.85, 0.92),
  low_lo  = c(0.74, 0.68, 0.70, 0.78),
  low_hi  = c(1.05, 0.99, 1.03, 1.08),
  mid_hr  = c(0.76, 0.71, 0.74, 0.83),
  mid_lo  = c(0.62, 0.58, 0.60, 0.69),
  mid_hi  = c(0.93, 0.87, 0.91, 1.00),
  high_hr = c(0.65, 0.60, 0.62, 0.75),
  high_lo = c(0.51, 0.46, 0.48, 0.61),
  high_hi = c(0.83, 0.78, 0.80, 0.92)
)

tabviz(dose_response, label = "endpoint",
  columns = list(
    viz_forest(
      effects = list(
        effect_forest("low_hr", "low_lo", "low_hi", label = "Low Dose"),
        effect_forest("mid_hr", "mid_lo", "mid_hi", label = "Standard"),
        effect_forest("high_hr", "high_lo", "high_hi", label = "High Dose")
      ),
      scale = "log", null_value = 1,
      axis_label = "Hazard Ratio"
    ),
    col_group("Low Dose",
      col_interval("HR (95% CI)", point = "low_hr", lower = "low_lo", upper = "low_hi")
    ),
    col_group("Standard",
      col_interval("HR (95% CI)", point = "mid_hr", lower = "mid_lo", upper = "mid_hi")
    ),
    col_group("High Dose",
      col_interval("HR (95% CI)", point = "high_hr", lower = "high_lo", upper = "high_hi")
    )
  ),
  theme = web_theme_cochrane(),
  title = "Dose-Response Analysis",
  subtitle = "Three-arm randomized trial across key endpoints"
)

Annotated Forest Plot

Combine reference lines with annotations, direction-coded marker colors, and significance-based styling in a single comprehensive example:

Code
annotated_data <- glp1_trials |>
  filter(row_type == "data") |>
  mutate(
    sig_color = case_when(
      upper < 1 ~ "#16a34a",
      lower > 1 ~ "#dc2626",
      TRUE ~ "#64748b"
    ),
    sig_shape = case_when(
      upper < 1 ~ "circle",
      lower > 1 ~ "diamond",
      TRUE ~ "square"
    )
  )

tabviz(annotated_data,
  label = "study",
  columns = list(
    col_text("drug", "Drug"),
    col_n("n", "N"),
    col_events("events", "n", "Events"),
    viz_forest(point = "hr", lower = "lower", upper = "upper",
               scale = "log", null_value = 1,
               axis_gridlines = TRUE,
               annotations = list(
                 refline(1, label = "No effect", style = "solid"),
                 refline(0.85, label = "Pooled estimate", style = "dashed", color = "#2563eb")
               )),
    col_interval("HR (95% CI)", point = "hr", lower = "lower", upper = "upper"),
    col_pvalue("pvalue", "P", stars = TRUE)
  ),
  marker_color = "sig_color",
  marker_shape = "sig_shape",
  row_bold = ~ pvalue < 0.05,
  theme = web_theme_nature(),
  title = "Cardiovascular Outcomes with Annotations",
  subtitle = "Direction-coded markers: green = significant benefit, gray = non-significant"
)