Modern SPC Visualization for Healthcare Quality Improvement
BFHcharts is an R package for creating beautiful, publication-ready Statistical Process Control (SPC) charts tailored for healthcare settings. Built on ggplot2 and qicharts2, it provides a consistent visual style inspired by BBC's data journalism approach.
- 🎨 Beautiful themes - Hospital branding with configurable multi-organizational support
- 📊 SPC chart types - Run charts, I-charts, P-charts, U-charts, and more
- 🔧 Flexible API - Simple one-function interface returning composable ggplot2 objects
- 📖 Well documented - Comprehensive vignettes and examples
- ✅ Production ready - Test-driven development with extensive coverage
# Install pak if you don't have it
install.packages("pak")
# For most users: Install stable release from r-universe (fastest)
pak::pkg_install("BFHcharts", repos = "https://johanreventlow.r-universe.dev")
# For developers: Install latest development version from GitHub
pak::pkg_install("johanreventlow/BFHcharts")r-universe vs GitHub:
- r-universe: Pre-built binaries, ingen compilation, baseret på releases (anbefalet)
- GitHub: Seneste kode, kræver build tools, langsom (til udvikling)
# From r-universe
install.packages("BFHcharts", repos = "https://johanreventlow.r-universe.dev")
# From GitHub (requires devtools)
devtools::install_github("johanreventlow/BFHcharts")BFHcharts depends on BFHtheme (>= 0.5.0) for theming and color palettes.
BFHtheme lives in the Remotes: field (not on CRAN), so it installs
automatically when you use pak::pkg_install() or
remotes::install_github() -- but not with the bare install.packages()
form. If you see a startup message
BFHcharts requires BFHtheme >= 0.5.0, install it explicitly:
remotes::install_github("johanreventlow/BFHtheme@v0.5.0")Some hospital networks (Posit Connect / RStudio Workbench behind a
corporate firewall, air-gapped clinical infrastructure) block public
GitHub access and cannot reach r-universe.dev or github.com
directly. BFHcharts can still be deployed in these environments via
one of three patterns:
1. Internal Posit Package Manager (recommended)
If your organisation hosts a Posit Package Manager instance, mirror
the johanreventlow.r-universe.dev repository or publish BFHcharts +
BFHtheme + BFHllm as internal source packages. Configure
options(repos = ...) to point at the internal endpoint, then:
install.packages("BFHcharts")2. Local tarball install
For one-off deployments or restricted-network environments without an internal mirror, download release tarballs from the GitHub Releases page on a connected workstation and transfer them:
# On a connected machine: download release assets
# https://github.com/johanreventlow/BFHcharts/releases
# https://github.com/johanreventlow/BFHtheme/releases
# On the deployment target: install in dependency order
install.packages("BFHtheme_0.5.0.tar.gz", repos = NULL, type = "source")
install.packages("BFHcharts_0.15.0.tar.gz", repos = NULL, type = "source")CRAN-mirrored runtime dependencies (ggplot2, qicharts2, dplyr,
scales, lubridate, purrr, stringr, tibble, yaml,
commonmark, xml2, systemfonts, rlang, svglite, marquee,
grid, lemon) must be available from a CRAN mirror reachable by
the target environment.
3. Posit Connect manifest deployment
For Shiny apps deploying via rsconnect::writeManifest() to Posit
Connect (Cloud or self-hosted), the manifest pinpoints package
versions including Remotes: references. Connect resolves these via
its configured upstream repositories. If Connect cannot resolve
johanreventlow/BFHtheme directly, configure an internal Package
Manager and pin the manifest's Remotes: field to the internal URL.
See #270 in biSPCharts for an example Posit Connect Cloud deployment configuration.
BFHcharts PDF export uses the Mari font for hospital branding when available.
Mari font is installed automatically on hospital computers. No action needed - PDFs will display full hospital branding.
The package uses font fallback: Mari → Roboto → Arial → Helvetica → sans-serif.
PDFs will be fully functional and readable, but without Region Hovedstaden specific branding. This is by design - Mari font is copyrighted and cannot be redistributed with the package.
Healthcare organizations that need consistent proprietary branding (custom fonts, hospital logos) across their BFHcharts deployments should distribute those assets via a private companion R package rather than bundling them in their consumer application or hardcoding paths.
Security warning:
inject_assetsis full code execution. The supplied function runs with the same privileges as the calling R session, with full file-system and network access. It must not come from user input (Shiny inputs, REST API parameters, configuration files of unknown provenance). Never forward user-supplied values toinject_assets— doing so creates a remote code execution (RCE) vector. Only pass functions from version-controlled application code or a controlled companion package. See?bfh_export_pdf(Security section) for acceptable/unacceptable sources.
Pattern:
- Create a private R package (e.g.
MyOrgAssets) hosting fonts and images ininst/assets/ - Export a single function
inject_my_assets(template_dir)that copies bundled assets into the staged Typst template directory - In your consumer application (e.g. a Shiny dashboard), depend on the companion package and pass its inject function to BFHcharts:
BFHcharts::bfh_export_pdf(
result, "report.pdf",
inject_assets = MyOrgAssets::inject_my_assets # safe: from companion package
)This keeps proprietary assets out of public BFHcharts and out of your consumer app's git history, while supporting full branding in production deployments (including Posit Connect Cloud, RStudio Connect, and Docker).
For the BFH/Region Hovedstaden reference deployment, the BFHchartsAssets private companion package (separate repository, hospital-internal access) implements this pattern. See its repository documentation for setup details.
This section documents exactly what the public BFHcharts package bundles, what requires
a companion package, and how to verify your setup.
- Typst template:
inst/templates/typst/bfh-template/bfh-template.typis bundled and used by default for allbfh_export_pdf()calls. - Font fallback chain: The template specifies
("Mari", "Roboto", "Arial", "Helvetica", "sans-serif"). If Mari is absent, Typst falls through to the next available font automatically. Roboto, Arial, and Helvetica are widely available on Ubuntu, macOS, and Windows. - No proprietary assets in package: Mari fonts and hospital logos are gitignored and
never committed to the public repository. A clean
pak::pkg_install()from GitHub produces a package that renders PDFs with system-available fonts. - Auto-detect staged fonts:
bfh_compile_typst()automatically detects afonts/subdirectory placed byinject_assetscallbacks and passes it as--font-pathto the Typst compiler — no extra configuration needed.
- Mari font files (proprietary, Region Hovedstaden):
BFHchartsAssets::inject_bfh_assetscopies Mari.otf/.ttffiles into the staged template directory before compile. - Hospital logo (
images/Hospital_Maerke_RGB_A1_str.png): supplied by the companion package alongside fonts.
Without a companion package, PDFs render correctly using system fonts but without the hospital logo and Mari branding.
# Check which fonts Typst will find on your system
systemfonts::system_fonts()[grepl("Mari|Roboto", systemfonts::system_fonts()$family), "family"]
# Smoke-render a PDF to verify the full pipeline works
result <- bfh_qic(
data.frame(x = 1:20, y = runif(20, 0.05, 0.15), n = rep(100, 20)),
x = "x", y = "y", n = "n", chart_type = "p"
)
bfh_export_pdf(result, tempfile(fileext = ".pdf"))
message("PDF rendered successfully")The images/ directory containing the hospital logo is currently untracked in the public
repository. A git archive HEAD tarball will produce a package where the default template
references an absent image. Rendering will succeed only when companion assets are injected
via inject_assets. A future release will add a conditional image reference or placeholder
asset to close this gap (see inst/adr/ADR-001-pdf-asset-policy.md).
library(BFHcharts)
# Example data: Monthly hospital-acquired infections
data <- data.frame(
month = seq(as.Date("2024-01-01"), by = "month", length.out = 24),
infections = rpois(24, lambda = 15),
surgeries = rpois(24, lambda = 100)
)
# Example 1: Simple run chart
bfh_qic(
data = data,
x = month,
y = infections,
chart_type = "run",
y_axis_unit = "count",
chart_title = "Monthly Hospital-Acquired Infections"
)
# Example 2: P-chart with target line
bfh_qic(
data = data,
x = month,
y = infections,
n = surgeries,
chart_type = "p",
y_axis_unit = "percent",
chart_title = "Infection Rate per 100 Surgeries",
target_value = 0.02,
target_text = "↓ Target: 2%"
)
# Example 3: I-chart with intervention (phase split)
bfh_qic(
data = data,
x = month,
y = infections,
chart_type = "i",
y_axis_unit = "count",
chart_title = "Infections Before/After Intervention",
part = c(12), # Intervention after 12 months
freeze = 12 # Freeze baseline at month 12
)BFHcharts integrates with the BFHtheme package for consistent hospital branding:
library(BFHtheme)
# Example 1: Use default BFHtheme
plot <- bfh_qic(
data = data,
x = month,
y = infections,
chart_type = "run",
y_axis_unit = "count",
chart_title = "Hospital Quality Improvement Chart"
)
# Example 2: Add hospital logo
plot <- plot |> BFHtheme::add_bfh_logo()
# Example 3: Apply alternative BFHtheme variants
plot <- bfh_qic(
data = data,
x = month,
y = infections,
chart_type = "run",
y_axis_unit = "count",
chart_title = "Dark Theme Chart"
) + BFHtheme::theme_bfh_dark()
# Example 4: Use BFHtheme color palettes
plot <- bfh_qic(
data = data,
x = month,
y = infections,
chart_type = "run",
y_axis_unit = "count",
chart_title = "Custom Colors"
) +
BFHtheme::scale_color_bfh_continuous()Note: Customization of hospital colors is handled by the BFHtheme package. Refer to BFHtheme documentation for advanced theming options.
When generating PDFs for multiple departments or indicators in a loop,
use bfh_create_export_session() to copy the Typst template assets once
and share them across all exports. This eliminates the recursive
directory copy that otherwise happens on every bfh_export_pdf() call.
library(BFHcharts)
# Create a session — template assets copied once
session <- bfh_create_export_session()
on.exit(close(session)) # Cleanup when done
departments <- c("ICU", "Medicine", "Surgery")
for (dept in departments) {
result <- bfh_qic(dept_data[[dept]], x = month, y = value,
chart_type = "i", chart_title = paste("Quality —", dept))
bfh_export_pdf(result,
output = paste0(dept, "_report.pdf"),
metadata = list(department = dept),
batch_session = session)
}
# close(session) called automatically via on.exit()Notes:
batch_sessioncannot be combined withtemplate_pathorinject_assets.- Pass
inject_assetsandfont_pathtobfh_create_export_session()instead. - Sessions are single-threaded; do not share across parallel workers.
Chart labels, analysis text, and details output are available in Danish ("da", default) and English ("en").
# English output
result <- bfh_qic(data, x = month, y = value, chart_type = "p",
language = "en")
bfh_generate_analysis(result, language = "en")
bfh_generate_details(result, language = "en")Default is language = "da" — existing code without the parameter is unaffected.
See TRANSLATORS.md for instructions on adding a new language.
- Facettering (
facets,nrow,ncol,scales) er endnu ikke understøttet i BFHcharts; multi-panel plots kræver manuel opbygning indtil issue #1 løses.
- Roxygen reference topics, e.g.
?bfh_qicorhelp(package = "BFHcharts") - Architecture notes in
docs/ - Vignettes are planned; links will be added once the articles ship
GPL-3 © Johan Reventlow
- Inspired by BBC's bbplot design philosophy
- Built on qicharts2 for SPC calculations
- Developed for Bispebjerg og Frederiksberg Hospital quality improvement work