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
100% normalized
position = "fill" and stat = "percent"
produce the same chart — gsm.viz normalizes the former into the
latter.
Single series, frequency sort, and Top-N
Labels, tooltips, reference lines
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
)