Table of Contents generated with DocToc
Thanks for your interest in improving cfbplotR, the SportsDataverse ggplot2 extension for
college-football logos, wordmarks, and player headshots!
By participating you agree to abide by the Code of Conduct.
- Report a bug -- open an issue using the Bug Report template.
- Request a feature -- open an issue using the Feature Request template.
- Improve docs -- typo fixes, clearer examples, and updated screenshots are always welcome.
- Add or fix a geom/element/scale/gt helper -- see the conventions below.
- Update team data -- logo and wordmark URLs go stale; PRs that refresh
data-raw/are valuable.
-
Fork and clone the repository.
-
Install development dependencies (pak recommended):
# recommended: install.packages("pak") pak::local_install_dev_deps() # or with devtools: install.packages("devtools") devtools::install_dev_deps()
-
Load the package for interactive development:
devtools::load_all()
devtools::load_all() # load the package
devtools::document() # regenerate man/ + NAMESPACE after changing roxygen2
devtools::test() # run the full test suite
devtools::check() # full R CMD check -- run before opening a PR
devtools::build_readme() # re-render README.md from README.Rmd
pkgdown::build_site() # preview the pkgdown site locallycfbplotR follows the conventions documented in CLAUDE.md. The essentials:
cfbplotR is a thin domain layer on top of ggpath. ggpath owns image fetching, caching, colorizing, and rendering. cfbplotR:
- Resolves a CFB team name or ESPN player ID to an image URL/path (via
logo_from_school(),wordmark_from_school(),headshot_from_id()inR/utils.R). - Delegates rendering to ggpath (
ggpath::GeomFromPath$draw_panel()for geoms;ggplot2::element_grob(ggpath::element_path(...))for theme elements).
Never re-implement ggpath rendering inside cfbplotR.
ggpath::element_path is an S7 object. The internal helper .cfb_element_to_path_grob()
must construct it via ggpath::element_path(alpha=..., colour=..., hjust=..., vjust=..., size=...).
Do NOT structure() a plain list and re-class it -- S7 property access will break.
| Family | Pattern | Example |
|---|---|---|
| Geoms | geom_cfb_* |
geom_cfb_logos() |
| Theme elements | element_cfb_* |
element_cfb_logo() |
| Color/fill scales | scale_color_cfb, scale_fill_cfb |
|
| Position scales | scale_x_cfb, scale_y_cfb |
|
| gt formatters | gt_fmt_cfb_*, gt_cfb_* |
gt_fmt_cfb_logo() |
| Team utilities | cfb_* |
cfb_team_tiers() |
| Name cleaners | clean_* |
clean_school_names() |
Use the three resolver helpers -- do not inline URL construction:
logo_from_school(team)-- team name -> logo URL (falls back to NCAA logo)wordmark_from_school(team)-- team name -> wordmark URL (falls back to NCAA wordmark)headshot_from_id(player_id)-- ESPN player ID -> ESPN CDN headshot URL
Add new ggpath re-exports to R/reexports.R using the standard roxygen2 pattern:
#' @importFrom ggpath new_symbol
#' @export
ggpath::new_symbolR/sysdata.rda is generated from data-raw/. After updating team data, re-source the
relevant script and run devtools::document(). Never edit sysdata.rda by hand.
Use cli::cli_warn() for user-facing warnings, cli::cli_abort() for errors, and
cli::cli_inform() for informational messages. Do not use bare warning() or message().
- Snapshot tests: visual geoms and theme elements use
vdiffr::expect_doppelganger(). After intentional visual changes, runvdiffr::manage_cases()to review and accept new snapshots. Do not delete.svgfiles manually. - Data/resolver tests: cleaning helpers and resolver functions use plain
expect_*. - Network tests: guard with
skip_if_offline()andskip_on_cran().
test_that("geom_cfb_logos renders a known team", {
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-alabama", p)
})Use subset-direction assertions so that upstream data additions never break the suite:
expect_in(sort(required_cols), sort(colnames(df))) # expected ⊆ actualNever write expect_equal(sort(colnames(df)), sort(required_cols)) -- that fails if the
upstream data gains a new column.
Name it tests/testthat/test-<function-slug>.R. Gate network tests as described above.
-
Never hand-edit
NAMESPACEor anything inman/-- regenerate withdevtools::document(). -
Re-render
README.mdwithdevtools::build_readme()and commitREADME.Rmd+README.mdtogether. Never hand-editREADME.md. -
Add new exported functions to the
reference:section of_pkgdown.ymlif they are not already covered by the existingstarts_with()selectors. -
For user-visible changes, update
NEWS.md. -
Regenerate doctoc TOCs after editing this file,
CLAUDE.md,.github/copilot-instructions.md, or.github/pull_request_template.md:Rscript tools/run_doctoc.R --maxlevel 2 \ CONTRIBUTING.md CLAUDE.md \ .github/copilot-instructions.md .github/pull_request_template.md
- Use Conventional Commits:
feat(geom):,fix(elements):,docs:,test:,refactor:,chore:,ci:. Mark breaking changes withtype!:or aBREAKING CHANGE:footer. - Keep unrelated changes in separate commits.
- Do not add AI tools (Claude, Copilot, etc.) as commit co-authors.
- Open your PR against
main, fill in the PR template, and confirmdevtools::check()anddevtools::test()pass before requesting review.
Thanks again for contributing!