Skip to contents

Every chart shape gsm.vizr covers, in the combinations report packages actually use. The bars() shapes below are asserted in the package’s Playwright suite, so that matrix and the tests stay in step.

dfSites <- data.frame(
  site = rep(c("S-001", "S-002", "S-003"), times = c(5, 3, 2)),
  flag = rep(c("2", "1", "2", "1", "0"), times = 2)
)
dfCounts <- data.frame(site = sprintf("S-%02d", 1:8), n = c(9, 3, 7, 1, 5, 2, 8, 4))
dfFlags <- data.frame(
  invid = factor(rep(c("INV-3", "INV-1", "INV-2"), each = 2),
    levels = c("INV-3", "INV-1", "INV-2")
  ),
  flag = factor(rep(c("2", "1"), times = 3), levels = c("2", "1")),
  n = c(5, 2, 4, 1, 2, 2)
)

Generic bar charts

Vertical stack (count)

The default: one row per observation, gsm.viz does the counting.

bars(dfSites, bars_spec(x = "site", fill = "flag"))

Horizontal stack, pre-aggregated, named colors, captions

bars(dfFlags, bars_spec(
  x = "invid", y = "n", fill = "flag",
  orientation = "horizontal", stat = "identity",
  scales = list(fill = list(colors = c(`2` = "#c91b1d", `1` = "#fdb84c"))),
  labels = list(captions = c("Footnote line 1.", "Footnote line 2."))
))

Position: dodge

bars(dfSites, bars_spec(x = "site", fill = "flag", position = "dodge"))

100% normalized

position = "fill" and stat = "percent" produce the same chart — gsm.viz normalizes the former into the latter.

bars(dfSites, bars_spec(x = "site", fill = "flag", position = "fill"))

Single series, frequency sort, and Top-N

bars(dfCounts, bars_spec(x = "site", y = "n", stat = "identity"))
bars(dfCounts, bars_spec(
  x = "site", y = "n", stat = "identity",
  scales = list(x = list(sort = "total", sortDir = "desc"))
))
bars(dfCounts, bars_spec(x = "site", y = "n", stat = "identity", nCategories = 3))

Labels, tooltips, reference lines

bars(dfFlags, bars_spec(
  x = "invid", y = "n", fill = "flag", stat = "identity",
  annotations = list(labels = list(
    segment = list(display = TRUE),
    total = list(display = TRUE)
  ))
))
bars(dfCounts, bars_spec(
  x = "site", y = "n", stat = "identity",
  annotations = list(referenceLines = list(
    list(value = 5, label = "Threshold", color = "#c91b1d")
  ))
))

Zoom, dense legend, multi-select

bars(dfCounts, bars_spec(
  x = "site", y = "n", stat = "identity",
  zoom = list(enabled = TRUE)
))
bars(dfSites, bars_spec(
  x = "site", fill = "flag",
  selection = list(enabled = TRUE, multiple = TRUE)
))

Facet small multiples

dfFacet <- data.frame(
  site = rep(c("S-001", "S-002"), 4),
  flag = rep(c("2", "1"), each = 4),
  country = rep(c("USA", "CAN"), each = 4)
)
facet_bars(
  dfFacet, bars_spec(x = "site", fill = "flag"),
  facet_spec("country", order = c("USA", "CAN"), nCol = 2)
)

Downstream replicas

The shapes the migrating report packages actually render.

Participant-level counts, normalized, with footnotes

set.seed(158)
dfSubj <- data.frame(
  invid = factor(sample(sprintf("INV-%d", 1:6), 60, replace = TRUE)),
  category = factor(
    sample(c("Eligible", "Excluded", "Pending"), 60, replace = TRUE),
    levels = c("Eligible", "Excluded", "Pending")
  )
)
bars(dfSubj, bars_spec(
  x = "invid", fill = "category", orientation = "horizontal",
  position = "fill",
  theme = list(dynamicSizing = TRUE),
  labels = list(captions = c(
    "Counts of participants by investigator.",
    "Source: synthetic gallery data."
  ))
))

Pre-aggregated eligibility stack, with titles

The dominant real shape: already summarized, horizontal, plain stack, with a chart title and both axis titles.

dfElig <- data.frame(
  invid = factor(rep(sprintf("INV-%d", 1:4), each = 3),
    levels = sprintf("INV-%d", 4:1)
  ),
  criteria = factor(rep(c("Ineligible", "No Eligibility Risk", "Neither"), 4),
    levels = c("Ineligible", "No Eligibility Risk", "Neither")
  ),
  n = c(3, 8, 1, 5, 6, 2, 1, 9, 3, 4, 4, 4)
)
bars(dfElig, bars_spec(
  x = "invid", y = "n", fill = "criteria", stat = "identity",
  orientation = "horizontal",
  scales = list(
    x = list(label = "Investigator"),
    y = list(label = "Participants"),
    fill = list(colors = c(
      Ineligible = "#FF5859", `No Eligibility Risk` = "#00BFC4",
      Neither = "#7CAE00"
    ))
  ),
  labels = list(
    title = "Eligibility criteria by investigator",
    captions = "Participants screened between 2026-01-01 and 2026-06-30."
  )
))

Endpoint status with a two-line tooltip

dfPfs <- data.frame(
  site = rep(c("S-001", "S-002", "S-003"), each = 2),
  status = factor(rep(c("Event", "Censored"), times = 3),
    levels = c("Event", "Censored")
  ),
  n = c(12, 30, 7, 25, 15, 18),
  pct = c(28.6, 71.4, 21.9, 78.1, 45.5, 54.5)
)
bars(dfPfs, bars_spec(
  x = "site", y = "n", fill = "status", stat = "identity",
  scales = list(fill = list(colors = c(Event = "#c91b1d", Censored = "#4e79a7"))),
  annotations = list(labels = list(segment = list(display = TRUE))),
  tooltip = list(formatter = js_hook(
    "function (value, context, details) {
       var d = (details && details.datum) || {};
       return [d.status + ': ' + d.n, 'Rate: ' + d.pct + '%'];
     }"
  ))
))

Sizing at report scale

24 investigators, growing at 25px each above the 500px floor — the sizing curve a report package would otherwise compute itself.

set.seed(24)
dfMany <- data.frame(
  invid = factor(rep(sprintf("INV-%02d", 1:24), each = 2)),
  flag = factor(rep(c("2", "1"), 24), levels = c("2", "1")),
  n = sample(1:9, 48, replace = TRUE)
)
bars(dfMany, bars_spec(
  x = "invid", y = "n", fill = "flag", stat = "identity",
  orientation = "horizontal",
  theme = list(dynamicSizing = TRUE, pxPerCategory = 25)
))

Empty data

Renders without error, though without a placeholder message.

bars(
  data.frame(site = character(), flag = character()),
  bars_spec(x = "site", fill = "flag")
)

Legacy KRI widgets

Relocated from gsm.kri, these wrappers drive gsm.viz’s earlier KRI renderers — barChart, scatterPlot, timeSeries, groupOverview — from the same vendored bundle the charts above use. They take the gsm reporting data frames directly rather than a bars_spec(), and build their configuration internally through MakeChartConfig(). They exist so KRI reports keep working across the move; new charts should reach for bars().

dfKri <- gsm.core::reportingResults %>%
  dplyr::filter(MetricID == "Analysis_kri0001")
dfKriLatest <- dfKri %>% dplyr::filter(SnapshotDate == max(SnapshotDate))
lKriMetric <- gsm.core::reportingMetrics %>%
  dplyr::filter(MetricID == "Analysis_kri0001") %>%
  as.list()
dfKriBounds <- gsm.core::reportingBounds %>%
  dplyr::filter(
    MetricID == "Analysis_kri0001" & SnapshotDate == max(SnapshotDate)
  )

Bar chart

Group-level results for one metric at the latest snapshot, with the outcome dropdown and site highlight control.

Widget_BarChart(
  dfResults = dfKriLatest,
  dfGroups = gsm.core::reportingGroups,
  lMetric = lKriMetric,
  vThreshold = lKriMetric$Threshold
)

Scatter plot

Denominator against numerator, with the metric’s bounds behind the points.

Widget_ScatterPlot(
  dfResults = dfKriLatest,
  dfGroups = gsm.core::reportingGroups,
  lMetric = lKriMetric,
  dfBounds = dfKriBounds
)

Time series

The same metric across every snapshot rather than only the latest.

Widget_TimeSeries(
  dfResults = dfKri,
  dfGroups = gsm.core::reportingGroups,
  lMetric = lKriMetric,
  vThreshold = lKriMetric$Threshold
)

Group overview

A table rather than a chart, and the one widget that spans every metric at once. The default subset keeps the groups carrying at least one red flag.

Widget_GroupOverview(
  dfResults = dplyr::filter(
    gsm.core::reportingResults,
    SnapshotDate == max(SnapshotDate)
  ),
  dfMetrics = gsm.core::reportingMetrics,
  dfGroups = gsm.core::reportingGroups
)