Table of Contents generated with DocToc
- CLAUDE.md – cfbplotR Development Guide
- Package Overview
- Architecture
- Function Families
- The ggpath Re-export Pattern
- Internal Resolvers
- Team Data in
sysdata.rda - Build & Development Commands
- Testing
- Documentation Maintenance
- Commit Convention
- Common Pitfalls
Package Overview
cfbplotR is a GitHub-only R package (not on CRAN) that provides ggplot2 geoms, theme elements, scales, and gt table helpers for visualizing college-football logos, wordmarks, and player headshots. It is built on top of the ggpath package, which handles image fetching, caching, colorizing, and alpha-transparency rendering.
- Version: 0.0.1.9000 (development)
- R Requirement: >= 4.1.0
- License: MIT
- Repository: https://github.com/sportsdataverse/cfbplotR
-
Maintainer: Jared Lee (
13jaredlee@gmail.com) -
Branch:
mainis the default and release branch.
Install the development version:
# pak (recommended):
pak::pkg_install("sportsdataverse/cfbplotR")
# or devtools:
devtools::install_github("sportsdataverse/cfbplotR")Architecture
cfbplotR sits as a thin domain layer on top of ggpath:
-
ID resolution (
R/utils.R):logo_from_school(),wordmark_from_school(), andheadshot_from_id()translate a CFB team name or ESPN player ID into an image URL or path. -
Geoms (
R/geom_cfb_logos.R,R/geom_cfb_wordmarks.R,R/geom_cfb_headshots.R): each geom resolves theteamorplayer_idaesthetic to a path via the resolver helpers, then delegates all rendering toggpath::GeomFromPath$draw_panel(). -
Theme elements (
R/theme-elements.R):element_cfb_logo(),element_cfb_wordmark(), andelement_cfb_headshot()are S3 element objects (classelement_cfb_*). Theirelement_grob.*methods resolve the label to a path/URL and then call the internal helper.cfb_element_to_path_grob(), which constructs a properggpath::element_pathS7 object (viaggpath::element_path(...)) and callsggplot2::element_grob()on it.-
Critical:
ggpath::element_pathis an S7 object – the helper must construct it viaggpath::element_path(alpha=..., colour=..., hjust=..., vjust=..., size=...). Do NOT try tostructure()a plain list and re-class it; S7 property access would break.
-
Critical:
-
Scales (
R/scale_cfb.R):scale_color_cfb(),scale_fill_cfb(),scale_x_cfb(),scale_y_cfb()– map team names to CFB primary/alt colors or logo positions. -
gt helpers (
R/gt_fmt_cfb.R,R/gt_stack_team.R):gt_fmt_cfb_logo(),gt_fmt_cfb_wordmark(),gt_fmt_cfb_headshot(),gt_fmt_cfb_dot_logo(),gt_cfb_cols_label()– format cells in a gt table with logos, wordmarks, or headshots. -
Team utilities (
R/cfb_team_tiers.R):cfb_team_tiers(),cfb_team_factor()– factor utilities for ordering teams in plots. -
Cleaning helpers (
R/cleaning_helpers.R):clean_school_names(),clean_team_abbrs()– standardize user-supplied team name strings to cfbplotR’s internal keys. -
Re-exports (
R/reexports.R): a set of ggpath symbols (geom_from_path,GeomFromPath,element_path,element_raster,geom_mean_lines,geom_median_lines,GeomRefLines) are re-exported so users do not need to attach ggpath directly. -
Team data (
R/sysdata.rda): internal named listslogo_listandwordmark_listmap school names to image URLs. The reference data framelogo_refcarriesschool,type, and URL columns and powersvalid_team_names(). Updated viadata-raw/.
Function Families
| Family | Functions |
|---|---|
| Geoms |
geom_cfb_logos(), geom_cfb_wordmarks(), geom_cfb_headshots()
|
| Theme elements |
element_cfb_logo(), element_cfb_wordmark(), element_cfb_headshot()
|
| Color/fill scales |
scale_color_cfb(), scale_fill_cfb()
|
| Position scales |
scale_x_cfb(), scale_y_cfb()
|
| gt formatters |
gt_fmt_cfb_logo(), gt_fmt_cfb_wordmark(), gt_fmt_cfb_headshot(), gt_fmt_cfb_dot_logo(), gt_cfb_cols_label(), gt_stack_team_column()
|
| Team utilities |
cfb_team_tiers(), cfb_team_factor()
|
| Name cleaners |
clean_school_names(), clean_team_abbrs(), valid_team_names()
|
| Title helpers |
ggtitle_image(), theme_title_image()
|
| ggpath re-exports |
geom_from_path, GeomFromPath, element_path, element_raster, geom_mean_lines, geom_median_lines, GeomRefLines
|
| Cache | .cfbplotR_clear_cache() |
The ggpath Re-export Pattern
R/reexports.R re-exports ggpath symbols using the standard roxygen2 pattern:
#' @importFrom ggpath geom_from_path
#' @export
ggpath::geom_from_pathThis lets package users call geom_from_path() without attaching ggpath. Do not duplicate the implementation – always re-export the canonical ggpath object.
Internal Resolvers
All three resolver helpers live in R/utils.R and are not exported:
-
logo_from_school(team): callsclean_school_names()to normalize, warns on invalid names and falls back to"NCAA", then looks uplogo_list[[t]]. -
wordmark_from_school(team): same pattern, falls back to the NCAA wordmark. -
headshot_from_id(player_id): wrapsheadshot_id_to_url()which buildshttp://a.espncdn.com/i/headshots/college-football/players/full/<id>.png.
These return character vectors of image URLs/paths. ggpath fetches, caches, and renders those paths.
Team Data in sysdata.rda
R/sysdata.rda is produced by the scripts in data-raw/ and baked into the package binary. It contains:
-
logo_list– named character vectorname -> logo URL -
wordmark_list– named character vectorname -> wordmark URL -
logo_ref– data frame withschool,type, and URL columns
Regenerate after updating team data:
source("data-raw/<relevant-script>.R")
devtools::document()Never edit sysdata.rda by hand.
Build & Development Commands
# Load package for interactive development
devtools::load_all()
# Regenerate man/ + NAMESPACE after changing roxygen2 comments
devtools::document()
# Run the full test suite
devtools::test()
# Run a single test file
testthat::test_file("tests/testthat/test-geom-cfb-logos.R")
# Full R CMD check (run before opening a PR)
devtools::check()
# Re-render README.md from README.Rmd
devtools::build_readme()
# Install locally
devtools::install()
# Build pkgdown site locally
pkgdown::build_site()Testing
Test types
-
Snapshot tests (via vdiffr): visual geoms and theme elements are tested with
vdiffr::expect_doppelganger(). Runvdiffr::manage_cases()to review/update snapshots after intentional visual changes. -
Data unit tests: resolver functions, cleaning helpers, and scale/gt functions are tested with standard
expect_*assertions (no rendering required). -
Network guard: any test that hits a live URL (e.g. ESPN headshot CDN) must be wrapped in
skip_if_offline()andskip_on_cran().
Test pattern
test_that("geom_cfb_logos renders without error", {
skip_on_cran()
skip_if_not_installed("vdiffr")
p <- ggplot2::ggplot(
data.frame(x = 1, y = 1, team = "Alabama"),
ggplot2::aes(x = x, y = y, team = team)
) +
geom_cfb_logos(width = 0.1)
vdiffr::expect_doppelganger("geom_cfb_logos-basic", p)
})Column/resolver assertions
test_that("logo_from_school returns character vector", {
result <- cfbplotR:::logo_from_school(c("Alabama", "Auburn"))
expect_type(result, "character")
expect_length(result, 2L)
})Use subset-direction assertions for column checks (upstream data can add columns):
Documentation Maintenance
Roxygen2 / NAMESPACE / man/
Never hand-edit NAMESPACE or anything in man/. Regenerate after any roxygen2 change:
devtools::document()Markdown TOCs (doctoc)
CLAUDE.md, CONTRIBUTING.md, .github/copilot-instructions.md, and .github/pull_request_template.md carry a doctoc-generated TOC inside the standard marker comments. After editing any of those files, regenerate:
Rscript tools/run_doctoc.R --maxlevel 2 \
CLAUDE.md CONTRIBUTING.md \
.github/copilot-instructions.md .github/pull_request_template.mdtools/run_doctoc.R is a no-Node.js R replacement for the npm doctoc CLI. It is idempotent.
README.md
README.md is rendered from README.Rmd. Never hand-edit README.md. After editing README.Rmd:
devtools::build_readme()Commit README.Rmd and the regenerated README.md together.
NEWS.md / _pkgdown.yml
-
NEWS.md: add bullets under the current unreleased version heading. -
_pkgdown.yml: new exported functions whose names matchstarts_with("geom_cfb")/starts_with("element_cfb")/starts_with("scale_")/starts_with("gt_")/starts_with("cfb_")/starts_with("clean_")are picked up by the existing selectors; otherwise add them to thereference:section manually.
Commit Convention
Use Conventional Commits:
feat: add geom_cfb_logos() clip-circle option
fix: correct wordmark fallback for unknown school names
docs: update README with new scale examples
test: add vdiffr snapshot for element_cfb_headshot
refactor: extract resolver helpers to utils.R
chore: regenerate sysdata.rda with 2025 team data
ci: update R CMD check workflow to ubuntu-latest
Prefer scoped subjects when useful (feat(geom):, fix(elements):, docs(readme):, etc.). Use type!: or a BREAKING CHANGE: footer for breaking changes. Keep unrelated work in separate commits.
Never add AI tools (Claude, Copilot, etc.) as commit co-authors.
Common Pitfalls
-
ggpath::element_pathis an S7 object. Always construct it viaggpath::element_path(alpha=..., colour=..., ...). Neverstructure(list(...), class="element_path"). -
logo_listandwordmark_listlive insysdata.rda(not exported). Access them as bare names inside the package; outside tests usecfbplotR:::logo_list. -
valid_team_names()callsclean_school_names()internally – do not call both in sequence or you will double-normalize. -
geom_cfb_*geoms delegate toggpath::GeomFromPath$draw_panel(); do not re-implement rendering logic inside cfbplotR. - Never hand-edit
NAMESPACE,man/, orR/sysdata.rda. - vdiffr snapshots live in
tests/testthat/_snaps/. After intentional visual changes, runvdiffr::manage_cases()to accept the new snapshot; do not delete.svgfiles manually. -
devtools::build_readme()must be re-run after any edit toREADME.Rmd.