Gallery: Basic Examples

Single-feature showcases demonstrating key tabviz capabilities.

1. Nested Hierarchical Groups

Three levels of nesting: Region > Country > Site. Click headers to collapse entire branches.

Code
nested_data <- tibble(
  site = c(
    "Boston General", "Mass Eye & Ear", "Johns Hopkins", "Cleveland Clinic",
    "UCL Hospital", "Imperial College", "Charite Berlin", "LMU Munich",
    "Tokyo University", "Osaka Medical", "Peking Union", "Shanghai Ruijin"
  ),
  region = c(
    rep("americas", 4),
    rep("europe", 4),
    rep("asia_pacific", 4)
  ),
  country = c(
    "usa", "usa", "usa", "usa",
    "uk", "uk", "germany", "germany",
    "japan", "japan", "china", "china"
  ),
  hr = c(0.72, 0.68, 0.75, 0.71, 0.78, 0.82, 0.69, 0.74, 0.65, 0.70, 0.67, 0.72),
  lower = c(0.58, 0.52, 0.61, 0.56, 0.64, 0.68, 0.54, 0.59, 0.50, 0.55, 0.52, 0.57),
  upper = c(0.89, 0.89, 0.92, 0.90, 0.95, 0.99, 0.88, 0.93, 0.85, 0.89, 0.86, 0.91),
  n = c(245, 189, 312, 278, 156, 134, 298, 267, 445, 389, 512, 478)
)

tabviz(
  nested_data,
  label = "site",
  group = c("region", "country"),  # Hierarchical: region > country
  columns = list(
    col_n("n"),
    col_interval("HR (95% CI)", point = "hr", lower = "lower", upper = "upper"),
    viz_forest(point = "hr", lower = "lower", upper = "upper",
               scale = "log", null_value = 1, axis_label = "Hazard Ratio")
  ),
  theme = web_theme_modern(),
  title = "Nested Hierarchical Groups",
  subtitle = "Region > Country > Site (3 levels)",
  caption = "Click any group header to collapse that branch and all children"
)

2. Multiple Effects Per Row

Five different analysis methods shown simultaneously. Vertical stacking reveals sensitivity.

Code
multi_effect_data <- tibble(
  study = c("PIONEER", "SUMMIT", "HORIZON", "APEX", "ZENITH"),
  n = c(2450, 1890, 3200, 1680, 2100),
  # Primary (ITT)
  itt_or = c(0.72, 0.78, 0.65, 0.81, 0.69),
  itt_lo = c(0.58, 0.64, 0.52, 0.66, 0.55),
  itt_hi = c(0.89, 0.95, 0.81, 0.99, 0.87),
  # Multiple Imputation
  mi_or = c(0.74, 0.80, 0.67, 0.83, 0.71),
  mi_lo = c(0.60, 0.66, 0.54, 0.68, 0.57),
  mi_hi = c(0.91, 0.97, 0.83, 1.01, 0.88),
  # Complete Case
  cc_or = c(0.70, 0.75, 0.63, 0.79, 0.67),
  cc_lo = c(0.55, 0.60, 0.49, 0.63, 0.52),
  cc_hi = c(0.89, 0.94, 0.81, 0.99, 0.86),
  # Per-Protocol
  pp_or = c(0.68, 0.73, 0.61, 0.77, 0.65),
  pp_lo = c(0.53, 0.58, 0.47, 0.61, 0.50),
  pp_hi = c(0.87, 0.92, 0.79, 0.97, 0.84),
  # Tipping Point
  tip_or = c(0.78, 0.84, 0.71, 0.87, 0.75),
  tip_lo = c(0.63, 0.69, 0.57, 0.71, 0.60),
  tip_hi = c(0.97, 1.02, 0.88, 1.07, 0.94)
)

tabviz(
  multi_effect_data,
  label = "study",
  columns = list(
    col_n("n"),
    col_interval("Primary OR", point = "itt_or", lower = "itt_lo", upper = "itt_hi"),
    viz_forest(
      effects = list(
        effect_forest("itt_or", "itt_lo", "itt_hi", label = "ITT (Primary)", color = "#2563eb"),
        effect_forest("mi_or", "mi_lo", "mi_hi", label = "Multiple Imputation", color = "#7c3aed"),
        effect_forest("cc_or", "cc_lo", "cc_hi", label = "Complete Case", color = "#059669"),
        effect_forest("pp_or", "pp_lo", "pp_hi", label = "Per-Protocol", color = "#d97706"),
        effect_forest("tip_or", "tip_lo", "tip_hi", label = "Tipping Point", color = "#dc2626")
      ),
      scale = "log", null_value = 1, axis_label = "Odds Ratio (95% CI)"
    )
  ),
  theme = web_theme_modern() |> set_spacing(row_height = 40),
  title = "Multiple Effects Per Row",
  subtitle = "5 sensitivity analyses displayed simultaneously",
  footnote = "Blue=ITT, Purple=MI, Green=CC, Orange=PP, Red=Tipping"
)

3. Table-Only Mode

No forest plot. Pure interactive table with rich column types.

Code
table_data <- tibble(
  metric = c("Revenue", "Gross Profit", "EBITDA", "Net Income", "Free Cash Flow",
             "Customer Count", "Churn Rate", "NPS Score", "CAC", "LTV"),
  category = c(rep("Financial", 5), rep("Operational", 5)),
  q1 = c(142, 98, 45, 28, 22, 12500, 2.8, 72, 185, 920),
  q2 = c(156, 108, 52, 32, 28, 13200, 2.5, 74, 178, 945),
  q3 = c(168, 118, 58, 38, 35, 14100, 2.3, 76, 172, 980),
  q4 = c(185, 132, 68, 45, 42, 15200, 2.1, 78, 165, 1020),
  yoy_pct = c(18.5, 22.1, 28.4, 35.2, 42.8, 21.6, -25.0, 8.3, -10.8, 10.9),
  trend = list(
    c(128, 135, 142, 156, 168, 185), c(85, 90, 98, 108, 118, 132),
    c(38, 42, 45, 52, 58, 68), c(22, 25, 28, 32, 38, 45),
    c(18, 20, 22, 28, 35, 42), c(10200, 11000, 12500, 13200, 14100, 15200),
    c(3.5, 3.2, 2.8, 2.5, 2.3, 2.1), c(68, 70, 72, 74, 76, 78),
    c(210, 198, 185, 178, 172, 165), c(850, 880, 920, 945, 980, 1020)
  )
)

tabviz(
  table_data,
  label = "metric", group = "category",
  columns = list(
    col_numeric("q4", "Q4 Actual"),
    col_bar("yoy_pct", "YoY %"),
    col_sparkline("trend", "6Q Trend")
  ),
  theme = web_theme_modern(),
  title = "Table-Only Mode",
  subtitle = "No forest plot - pure data table with bars and sparklines",
  caption = "Using tabviz() for table-only display"
)

4. Custom Theme Building

Building a branded theme from scratch with the fluent API.

Code
# Build a "Terminal" theme step by step
terminal_theme <- web_theme_default() |>
  set_colors(
    background = "#0c0c0c",
    foreground = "#00ff00",
    primary = "#00ff00",
    secondary = "#008800",
    muted = "#005500",
    border = "#003300",
    interval_positive = "#00ff00",
    interval_negative = "#ff0000",
    interval_line = "#00cc00",
    summary_fill = "#00ff00",
    summary_border = "#00aa00"
  ) |>
  set_typography(
    font_family = "'Courier New', monospace",
    font_size_base = "0.85rem"
  ) |>
  set_spacing(row_height = 28, header_height = 32) |>
  set_shapes(point_size = 6, line_width = 1.5, border_radius = 0) |>
  set_axis(gridlines = TRUE, gridline_style = "dotted")

theme_demo_data <- tibble(
  process = c("AUTH_SERVICE", "API_GATEWAY", "DB_PRIMARY", "CACHE_LAYER", "MSG_QUEUE"),
  latency_ms = c(12, 45, 8, 3, 28),
  latency_se = c(2, 8, 1.5, 0.5, 5),
  uptime = c(99.99, 99.95, 99.999, 99.99, 99.97),
  rps = c(12500, 8900, 45000, 125000, 3200)
) |>
  mutate(lower = latency_ms - 1.96 * latency_se, upper = latency_ms + 1.96 * latency_se)

tabviz(
  theme_demo_data,
  label = "process",
  columns = list(
    col_numeric("uptime", "Uptime %"),
    col_numeric("rps", "RPS"),
    col_interval("Latency ms (95% CI)", point = "latency_ms", lower = "lower", upper = "upper"),
    viz_forest(point = "latency_ms", lower = "lower", upper = "upper",
               null_value = 20, axis_label = "Response Latency (ms)")
  ),
  theme = terminal_theme,
  title = "Custom Theme: Terminal",
  subtitle = "Built with set_colors(), set_typography(), set_spacing(), set_shapes()",
  caption = "Monospace font, green-on-black, zero border radius"
)

5. Sparklines & Bars

Column visualizations for trends and magnitudes.

Code
viz_data <- tibble(
  fund = c("Growth Fund A", "Value Fund B", "Index Fund C", "Bond Fund D", "REIT Fund E"),
  return_1y = c(24.5, 12.8, 18.2, 4.5, 8.9),
  return_se = c(4.2, 2.8, 3.1, 1.2, 2.5),
  aum_b = c(45.2, 28.5, 125.8, 52.1, 18.9),
  expense = c(0.85, 0.45, 0.03, 0.15, 0.65),
  monthly_returns = list(
    c(-2, 4, 3, -1, 5, 2, 4, -3, 6, 3, 2, 5),
    c(1, 2, 1, 0, 2, 1, 1, 2, 1, 0, 1, 2),
    c(1, 3, 2, 0, 3, 1, 2, -1, 4, 2, 1, 3),
    c(0.5, 0.3, 0.4, 0.3, 0.4, 0.3, 0.4, 0.3, 0.4, 0.3, 0.4, 0.3),
    c(1, -1, 2, 0, 1, 2, -1, 3, 0, 1, 1, 2)
  ),
  flow_trend = list(
    c(2.1, 2.5, 3.2, 4.1, 4.8, 5.2),
    c(1.8, 1.6, 1.4, 1.2, 1.1, 0.9),
    c(8.5, 9.2, 10.1, 11.5, 12.8, 14.2),
    c(3.2, 3.4, 3.5, 3.6, 3.7, 3.8),
    c(1.2, 1.4, 1.5, 1.3, 1.6, 1.8)
  )
) |>
  mutate(lower = return_1y - 1.96 * return_se, upper = return_1y + 1.96 * return_se)

tabviz(
  viz_data,
  label = "fund",
  columns = list(
    col_group("Fund Info",
      col_bar("aum_b", "AUM ($B)"),
      col_numeric("expense", "Expense %")
    ),
    col_group("Trends",
      col_sparkline("monthly_returns", "12M Returns"),
      col_sparkline("flow_trend", "6M Flows")
    ),
    col_interval("1Y Return % (95% CI)", point = "return_1y", lower = "lower", upper = "upper"),
    viz_forest(point = "return_1y", lower = "lower", upper = "upper",
               null_value = 0, axis_label = "1-Year Return (%)")
  ),
  theme = web_theme_modern(),
  title = "Sparklines & Bars",
  subtitle = "col_group() organizes related columns under shared headers",
  caption = "Fund Info group on left, Trends group on right"
)

6. Row Styling

Headers, summaries, badges, indentation, and custom colors.

Code
styled_data <- tibble(
  label = c(
    "Primary Endpoint",
    "  Composite MACE", "  CV Death", "  MI", "  Stroke",
    "",
    "Secondary Endpoints",
    "  All-cause mortality", "  HF hospitalization",
    "",
    "Overall Summary"
  ),
  hr = c(NA, 0.82, 0.88, 0.79, 0.76, NA, NA, 0.91, 0.72, NA, 0.80),
  lower = c(NA, 0.74, 0.76, 0.68, 0.62, NA, NA, 0.79, 0.61, NA, 0.73),
  upper = c(NA, 0.91, 1.02, 0.92, 0.93, NA, NA, 1.05, 0.85, NA, 0.88),
  events = c(NA, 856, 312, 298, 246, NA, NA, 445, 412, NA, 1268),
  rtype = c("header", rep("data", 4), "spacer", "header", "data", "data", "spacer", "summary"),
  rbold = c(TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE),
  rindent = c(0, 1, 2, 2, 2, 0, 0, 1, 1, 0, 0),
  rcolor = c("#2563eb", NA, NA, NA, NA, NA, "#2563eb", NA, NA, NA, "#16a34a"),
  rbadge = c(NA, "Primary", NA, NA, NA, NA, NA, NA, "Key", NA, "Pooled")
)

tabviz(
  styled_data,
  label = "label",
  columns = list(
    col_numeric("events", "Events"),
    col_interval("HR (95% CI)", point = "hr", lower = "lower", upper = "upper"),
    viz_forest(point = "hr", lower = "lower", upper = "upper",
               scale = "log", null_value = 1, axis_label = "Hazard Ratio")
  ),
  row_type = "rtype", row_bold = "rbold", row_indent = "rindent",
  row_color = "rcolor", row_badge = "rbadge",
  theme = web_theme_modern(),
  title = "Row Styling Features",
  subtitle = "row_type, row_bold, row_indent, row_color, row_badge",
  caption = "Headers in blue, summary in green, with badges and indentation"
)

7. Marker Styling with Formulas

Customize marker shapes, colors, and opacity per row using formula expressions.

Code
marker_data <- tibble(
  study = c("RCT-001", "RCT-002", "RCT-003", "OBS-001", "OBS-002", "OBS-003"),
  design = c("RCT", "RCT", "RCT", "Observational", "Observational", "Observational"),
  hr = c(0.72, 0.68, 0.75, 0.82, 0.78, 0.85),
  lower = c(0.58, 0.52, 0.61, 0.68, 0.64, 0.71),
  upper = c(0.89, 0.89, 0.92, 0.99, 0.95, 1.02),
  pvalue = c(0.002, 0.001, 0.008, 0.045, 0.022, 0.068),
  n = c(1250, 980, 1420, 2100, 1890, 1650)
)

tabviz(
  marker_data,
  label = "study",
  group = "design",
  columns = list(
    col_n("n"),
    col_pvalue("pvalue", "P"),
    col_interval("HR (95% CI)", point = "hr", lower = "lower", upper = "upper"),
    viz_forest(point = "hr", lower = "lower", upper = "upper",
               scale = "log", null_value = 1, axis_label = "Hazard Ratio")
  ),
  # 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),
  theme = web_theme_modern(),
  title = "Marker Styling with Formulas",
  subtitle = "Shape by study design, color by significance, opacity by p-value",
  caption = "Using formula expressions: ~ ifelse(design == \"RCT\", \"circle\", \"square\")"
)

8. Cell-Level Styling with Formulas

Use .x to style cells based on their own values—no pre-computed columns needed.

Code
cell_style_data <- tibble(
  study = c("ACCORD", "ADVANCE", "SPRINT", "ONTARGET", "TRANSCEND"),
  hr = c(0.65, 1.15, 0.82, 0.98, 0.92),
  lower = c(0.50, 0.95, 0.68, 0.85, 0.78),
  upper = c(0.85, 1.40, 0.99, 1.14, 1.09),
  pval = c(0.002, 0.18, 0.04, 0.62, 0.24),
  change = c(-18.5, 8.2, -12.4, -1.2, -5.8)
)

tabviz(
  cell_style_data,
  label = "study",
  columns = list(
    # .x refers to the column's own values
    col_numeric("hr", "HR",
      bold = ~ .x < 1,
      color = ~ ifelse(.x < 1, "#16a34a", "#dc2626")
    ),
    col_pvalue("pval",
      bold = ~ .x < 0.05,
      color = ~ ifelse(.x < 0.05, "#16a34a", "#71717a")
    ),
    col_numeric("change", "% Change",
      color = ~ case_when(
        .x < -10 ~ "#16a34a",
        .x > 5 ~ "#dc2626",
        TRUE ~ "#71717a"
      )
    ),
    col_interval("HR (95% CI)", point = "hr", lower = "lower", upper = "upper"),
    viz_forest(point = "hr", lower = "lower", upper = "upper",
               scale = "log", null_value = 1, axis_label = "Hazard Ratio")
  ),
  theme = web_theme_modern(),
  title = "Cell-Level Formula Styling",
  subtitle = "Using .x to style cells based on their own values",
  caption = ".x < 0.05 applies per-cell: bold = ~ .x < 0.05"
)

9. Annotations & Reference Lines

Custom reference lines with labels at specific values.

Code
annotation_data <- tibble(
  study = c("LOW-DOSE", "MID-DOSE", "HIGH-DOSE", "COMBO-A", "COMBO-B"),
  or = c(0.92, 0.75, 0.58, 0.62, 0.48),
  lower = c(0.78, 0.62, 0.45, 0.50, 0.38),
  upper = c(1.08, 0.91, 0.75, 0.77, 0.61),
  dose_mg = c(50, 100, 200, 150, 250)
)

tabviz(
  annotation_data,
  label = "study",
  columns = list(
    col_numeric("dose_mg", "Dose (mg)"),
    col_interval("OR (95% CI)", point = "or", lower = "lower", upper = "upper"),
    viz_forest(point = "or", lower = "lower", upper = "upper",
               scale = "log", null_value = 1, axis_label = "Odds Ratio",
               annotations = list(
                 refline(0.80, label = "Clinically meaningful", style = "dashed", color = "#16a34a"),
                 refline(0.50, label = "Target effect", style = "solid", color = "#dc2626")
               ))
  ),
  theme = web_theme_modern(),
  title = "Annotations & Reference Lines",
  subtitle = "refline() adds labeled vertical lines",
  caption = "Green dashed = clinically meaningful threshold, Red solid = target"
)

10. Axis Control & CI Clipping

The ci_clip_factor controls how far CIs can extend beyond estimate bounds. For log scale, it’s a direct ratio: ci_clip_factor = 3 means CIs can extend up to 3× beyond.

Code
# Use the effect_sizes stress test dataset - contains studies with widely varying CI widths
data(effect_sizes)

tabviz(
  effect_sizes,
  label = "study",
  columns = list(
    col_text("phase", "Phase"),
    col_n("n"),
    col_interval("HR (95% CI)", point = "hr", lower = "lower", upper = "upper"),
    viz_forest(point = "hr", lower = "lower", upper = "upper",
               scale = "log", null_value = 1, axis_label = "Hazard Ratio (log scale)")
  ),
  theme = web_theme_modern() |>
    set_axis(gridlines = TRUE, gridline_style = "dotted"),  # default ci_clip_factor = 3
  title = "Axis Control & CI Clipping",
  subtitle = "Default ci_clip_factor = 3 allows CIs up to 3× beyond estimates",
  caption = "Arrows indicate CIs clipped beyond axis limits"
)

11. Climate Science Visualization

Non-clinical example showing temperature anomalies—tabviz works for any interval data.

Code
data(climate_temps)

tabviz(
  climate_temps,
  label = "region",
  group = "category",
  columns = list(
    col_text("period", "Period"),
    col_text("certainty", "Confidence"),
    col_interval("Anomaly °C (95% CI)", point = "anomaly", lower = "lower", upper = "upper"),
    viz_forest(point = "anomaly", lower = "lower", upper = "upper",
               null_value = 0, axis_label = "Temperature Anomaly (°C vs. pre-industrial)")
  ),
  marker_shape = ~ ifelse(certainty == "High", "circle", "square"),
  marker_color = ~ ifelse(anomaly > 1.5, "#dc2626", ifelse(anomaly > 1.0, "#f59e0b", "#3b82f6")),
  theme = web_theme_modern() |>
    set_colors(
      interval_positive = "#dc2626",  # Red for warming
      interval_negative = "#3b82f6"   # Blue for cooling
    ) |>
    set_axis(gridlines = TRUE),
  title = "Regional Temperature Anomalies",
  subtitle = "Grouped by geographic category, shaped by confidence level",
  caption = "Red = significant warming, Blue = cooling or below baseline"
)

See Also