├── R ├── sysdata.rda ├── colors_race.R ├── position_nudge_repel.R ├── colors_highlight.R ├── colors_continuous.R ├── axis_handling.R ├── colors_discrete.R ├── RcppExports.R ├── cmapplot.R ├── default_aes.R ├── data.R └── utilities.R ├── LICENSE ├── data ├── peer_grp.rda ├── economy_basic.rda ├── grp_over_time.rda ├── cluster_jobchange.rda ├── percentile_wages.rda ├── transit_ridership.rda ├── vehicle_ownership.rda ├── traded_emp_by_race.rda └── pop_and_laborforce_by_age.rda ├── man ├── figures │ ├── logo.png │ ├── margins.pptx │ ├── margins1.png │ ├── margins2.png │ ├── logo_notext.png │ ├── README-theme-1.png │ └── README-finalize-1.png ├── peer_grp.Rd ├── percentile_wages.Rd ├── transit_ridership.Rd ├── grp_over_time.Rd ├── cluster_jobchange.Rd ├── pop_and_laborforce_by_age.Rd ├── vehicle_ownership.Rd ├── economy_basic.Rd ├── traded_emp_by_race.Rd ├── cmapplot.Rd ├── cmap_fill_continuous.Rd ├── cmap_fill_discrete.Rd ├── customproto.Rd ├── cmap_fill_race.Rd ├── update_recessions.Rd ├── position_nudge_repel.Rd ├── dot-lwd.Rd ├── abbr_years.Rd ├── viz_palette.Rd ├── cmap_default_aes.Rd ├── cmap_fill_highlight.Rd ├── get_cmapplot_globals.Rd ├── theme_cmap.Rd ├── geom_pandemics.Rd ├── geom_text_lastonly.Rd ├── finalize_plot.Rd ├── geom_recessions.Rd └── geom_text_lastonly_repel.Rd ├── pkgdown ├── favicon │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-60x60.png │ └── apple-touch-icon-76x76.png └── _pkgdown.yml ├── .gitignore ├── .Rbuildignore ├── cmapplot.Rproj ├── LICENSE.md ├── DESCRIPTION ├── NAMESPACE ├── vignettes ├── installation.Rmd ├── colors.Rmd ├── plots.Rmd └── finalize.Rmd ├── .github └── workflows │ ├── check-standard.yaml │ └── pkgdown.yaml ├── README.Rmd ├── README.md └── NEWS.md /R/sysdata.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/R/sysdata.rda -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2020 2 | COPYRIGHT HOLDER: Chicago Metropolitan Agency for Planning 3 | -------------------------------------------------------------------------------- /data/peer_grp.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/data/peer_grp.rda -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/man/figures/logo.png -------------------------------------------------------------------------------- /data/economy_basic.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/data/economy_basic.rda -------------------------------------------------------------------------------- /data/grp_over_time.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/data/grp_over_time.rda -------------------------------------------------------------------------------- /man/figures/margins.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/man/figures/margins.pptx -------------------------------------------------------------------------------- /man/figures/margins1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/man/figures/margins1.png -------------------------------------------------------------------------------- /man/figures/margins2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/man/figures/margins2.png -------------------------------------------------------------------------------- /data/cluster_jobchange.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/data/cluster_jobchange.rda -------------------------------------------------------------------------------- /data/percentile_wages.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/data/percentile_wages.rda -------------------------------------------------------------------------------- /data/transit_ridership.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/data/transit_ridership.rda -------------------------------------------------------------------------------- /data/vehicle_ownership.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/data/vehicle_ownership.rda -------------------------------------------------------------------------------- /data/traded_emp_by_race.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/data/traded_emp_by_race.rda -------------------------------------------------------------------------------- /man/figures/logo_notext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/man/figures/logo_notext.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /man/figures/README-theme-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/man/figures/README-theme-1.png -------------------------------------------------------------------------------- /data/pop_and_laborforce_by_age.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/data/pop_and_laborforce_by_age.rda -------------------------------------------------------------------------------- /man/figures/README-finalize-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/man/figures/README-finalize-1.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CMAP-REPOS/cmapplot/HEAD/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .DS_Store 5 | inst/doc 6 | docs 7 | doc 8 | Meta 9 | Rplots.pdf 10 | vignettes/*.png 11 | /doc/ 12 | /Meta/ 13 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^cmapplot\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | ^README\.Rmd$ 5 | ^\.github$ 6 | ^_pkgdown\.yml$ 7 | ^docs$ 8 | ^pkgdown$ 9 | ^doc$ 10 | ^Meta$ 11 | [.]pptx$ 12 | ^Rplots.pdf$ 13 | -------------------------------------------------------------------------------- /cmapplot.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | ProjectId: 4fa506ff-d1f7-44a2-9c57-2d8d7beb1fa7 3 | 4 | RestoreWorkspace: No 5 | SaveWorkspace: No 6 | AlwaysSaveHistory: Default 7 | 8 | EnableCodeIndexing: Yes 9 | UseSpacesForTab: Yes 10 | NumSpacesForTab: 2 11 | Encoding: UTF-8 12 | 13 | RnwWeave: Sweave 14 | LaTeX: pdfLaTeX 15 | 16 | AutoAppendNewline: Yes 17 | StripTrailingWhitespace: Yes 18 | LineEndingConversion: Windows 19 | 20 | BuildType: Package 21 | PackageUseDevtools: Yes 22 | PackageInstallArgs: --no-multiarch --with-keep.source 23 | PackageRoxygenize: rd,collate,namespace 24 | -------------------------------------------------------------------------------- /man/peer_grp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{peer_grp} 5 | \alias{peer_grp} 6 | \title{Gross Regional Product by region, with peers, 2001-17} 7 | \format{ 8 | A tibble. 121 rows and 5 variables: 9 | \describe{ 10 | \item{area}{Factor. name of the region} 11 | \item{year}{Double. year of the data} 12 | \item{grp}{Double. real gross regional product} 13 | } 14 | } 15 | \source{ 16 | CMAP traded clusters report 17 | } 18 | \usage{ 19 | peer_grp 20 | } 21 | \description{ 22 | A test dataset containing real GRP data for the CMAP and peer regions. 23 | } 24 | \examples{ 25 | # a time-series line chart 26 | ggplot(grp_over_time, aes(x = year, y = realgrp, color = cluster)) + 27 | geom_line() 28 | 29 | } 30 | \keyword{datasets} 31 | -------------------------------------------------------------------------------- /man/percentile_wages.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{percentile_wages} 5 | \alias{percentile_wages} 6 | \title{Wage percentiles by cluster} 7 | \format{ 8 | A tibble. 45 rows and 3 variables: 9 | \describe{ 10 | \item{cluster}{Chr. The name of the cluster} 11 | \item{percentile}{Double. The percentile wage being reported} 12 | \item{wage}{Double. The wage. I believe 2017 data.} 13 | } 14 | } 15 | \source{ 16 | CMAP traded clusters report 17 | } 18 | \usage{ 19 | percentile_wages 20 | } 21 | \description{ 22 | A test dataset containing the 10th, 25th, 50th, 75th, and 90th percentile wage by cluster in the CMAP region. 23 | } 24 | \examples{ 25 | # a non-time-series line chart 26 | ggplot(percentile_wages, aes(x = percentile, y = wage, color = cluster)) + 27 | geom_line() 28 | 29 | } 30 | \keyword{datasets} 31 | -------------------------------------------------------------------------------- /man/transit_ridership.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{transit_ridership} 5 | \alias{transit_ridership} 6 | \title{Transit ridership in the Chicago region, 1980-2024} 7 | \format{ 8 | A tibble. 225 rows and 3 variables 9 | \describe{ 10 | \item{year}{Double. Year of data} 11 | \item{system}{Char. Name of system (includes CTA bus, CTA rail, Metra, Pace, and Pace ADA)} 12 | \item{ridership}{Double. Annual unlinked passenger trips in millions} 13 | } 14 | } 15 | \source{ 16 | Regional Transportation Authority \url{http://www.rtams.org/rtams/systemRidership.jsp} 17 | } 18 | \usage{ 19 | transit_ridership 20 | } 21 | \description{ 22 | A test dataset containing 1980-2019 transit ridership for the three service 23 | boards that provide transit in Northeastern Illinois. 24 | } 25 | \examples{ 26 | # A line graph 27 | ggplot(transit_ridership,aes(x = year,y=ridership,group=system,color=system)) + 28 | geom_line(na.rm=TRUE) 29 | 30 | 31 | } 32 | \keyword{datasets} 33 | -------------------------------------------------------------------------------- /man/grp_over_time.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{grp_over_time} 5 | \alias{grp_over_time} 6 | \title{Gross Regional Product by cluster, 2007-17} 7 | \format{ 8 | A tibble. 121 rows and 5 variables: 9 | \describe{ 10 | \item{cluster}{Chr. The name of the cluster} 11 | \item{category}{Factor. "goods-producing" or "services"} 12 | \item{assessment}{Factor. "Trailing", "Mixed", or "Leading"} 13 | \item{year}{Double. The year of the data} 14 | \item{realgrp}{Double. The real gross regional product of the cluster in year `year`. 15 | Not exactly sure on the inflation year but I believe it is 2012} 16 | } 17 | } 18 | \source{ 19 | CMAP traded clusters report 20 | } 21 | \usage{ 22 | grp_over_time 23 | } 24 | \description{ 25 | A test dataset containing real GRP data for the CMAP region. 26 | } 27 | \examples{ 28 | # a time-series line chart 29 | ggplot(grp_over_time, aes(x = year, y = realgrp, color = cluster)) + 30 | geom_line() 31 | 32 | } 33 | \keyword{datasets} 34 | -------------------------------------------------------------------------------- /man/cluster_jobchange.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{cluster_jobchange} 5 | \alias{cluster_jobchange} 6 | \title{Job change in CMAP region by cluster, 2001-17} 7 | \format{ 8 | A tibble. 22 rows and 5 variables: 9 | \describe{ 10 | \item{code}{Integer. code of cluster} 11 | \item{name}{Char. textual description/cluster title} 12 | \item{category}{Factor. Either "goods-producing" or "services"} 13 | \item{assessment}{Factor. "Leading", "Mixed", or "Trailing"} 14 | \item{jobchange}{Integer. Total change in employment in the cluster between 2001-17} 15 | } 16 | } 17 | \source{ 18 | CMAP traded clusters report 19 | } 20 | \usage{ 21 | cluster_jobchange 22 | } 23 | \description{ 24 | A test dataset containing 2001-17 job change and other data for the 22 specialized traded clusters 25 | analyzed in the CMAP Traded Clusters report. 26 | } 27 | \examples{ 28 | # A bar chart 29 | ggplot(cluster_jobchange, aes(x = reorder(name, jobchange), y = jobchange, fill = category)) + 30 | geom_col() + 31 | coord_flip() 32 | 33 | 34 | } 35 | \keyword{datasets} 36 | -------------------------------------------------------------------------------- /man/pop_and_laborforce_by_age.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{pop_and_laborforce_by_age} 5 | \alias{pop_and_laborforce_by_age} 6 | \title{Population and Labor Force by Age} 7 | \format{ 8 | A tibble. 12 rows and 4 variables: 9 | \describe{ 10 | \item{variable}{Chr. Indicates the meaning of the data stored in `value`. "population" or "laborforce".} 11 | \item{year}{Factor. 2010 or 2017.} 12 | \item{age}{Chr. The age bucket. Either 16-24, 25-54, or 55+.} 13 | \item{value}{Double. The value indicated by the other variables.} 14 | } 15 | } 16 | \source{ 17 | CMAP traded clusters report 18 | } 19 | \usage{ 20 | pop_and_laborforce_by_age 21 | } 22 | \description{ 23 | A test dataset containing percentage breakdowns of the population and labor force by various age buckets in 2010 and 2017. 24 | } 25 | \examples{ 26 | # a grouped and stacked bar chart (via `interaction()`) 27 | ggplot(pop_and_laborforce_by_age, aes(x = interaction(year, variable), y = value, fill = age)) + 28 | geom_col(position = position_stack(reverse = TRUE)) 29 | 30 | } 31 | \keyword{datasets} 32 | -------------------------------------------------------------------------------- /man/vehicle_ownership.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{vehicle_ownership} 5 | \alias{vehicle_ownership} 6 | \title{Vehicle ownership in the CMAP seven county region} 7 | \format{ 8 | A tibble. 40 rows and 3 variables 9 | \describe{ 10 | \item{county}{Char. Name of county} 11 | \item{number_of_veh}{Char. Number of vehicles owned by household} 12 | \item{pct}{Numeric. Share of households with the given number of vehicles (values between 0 and 1)} 13 | } 14 | } 15 | \source{ 16 | CMAP Travel Inventory Survey Data Summary \url{https://www.cmap.illinois.gov/documents/10180/77659/Travel+Inventory+Survey+Data+Summary_weighted_V2.pdf/d4b33cdd-1c44-4322-b32f-2f54b85207cb} 17 | } 18 | \usage{ 19 | vehicle_ownership 20 | } 21 | \description{ 22 | A test dataset containing vehicle ownership rates in the seven county region 23 | of northeastern Illinois. 24 | } 25 | \examples{ 26 | # A stacked bar chart 27 | ggplot(vehicle_ownership, 28 | aes(x = county, y = pct, fill = number_of_veh)) + 29 | geom_bar(position = position_stack(), stat = "identity") 30 | 31 | } 32 | \keyword{datasets} 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2020 Chicago Metropolitan Agency for Planning 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /man/economy_basic.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{economy_basic} 5 | \alias{economy_basic} 6 | \title{Basic regional economic stats in 2001 and 2017} 7 | \format{ 8 | A tibble. 18 rows and 4 variables: 9 | \describe{ 10 | \item{variable}{Chr. Indicates the meaning of the data stored in `value`. Jobs, Real Earnings, or Establishments.} 11 | \item{year}{Factor. 2001 or 2017.} 12 | \item{sector}{Chr. local, tradedgoods, or tradedservices. Together, these three sectors account for all clusters in the region.} 13 | \item{value}{Int. The value indicated as described by the other columns} 14 | } 15 | } 16 | \source{ 17 | CMAP traded clusters report 18 | } 19 | \usage{ 20 | economy_basic 21 | } 22 | \description{ 23 | A test dataset containing count of jobs, earnings, and establishments in the Chicago region in both 2001 and 2017. 24 | } 25 | \examples{ 26 | # a grouped and stacked bar chart (via `interaction()`) 27 | ggplot(economy_basic, aes(x = interaction(year, variable), y = value, fill = sector)) + 28 | geom_col(position = "fill") + 29 | scale_y_continuous(labels = scales::percent) 30 | 31 | } 32 | \keyword{datasets} 33 | -------------------------------------------------------------------------------- /man/traded_emp_by_race.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{traded_emp_by_race} 5 | \alias{traded_emp_by_race} 6 | \title{Traded employment by race} 7 | \format{ 8 | A tibble. 12 rows and 4 variables: 9 | \describe{ 10 | \item{Race}{Chr. White, Asian, Hispanic, Other, Black, or Regional Average.} 11 | \item{variable}{Chr. SpecializedTraded, UnspecializedTraded, or Total. 12 | Total is a sum of SpecializedTraded and UnspecializedTraded. The invisible remainder (e.g. `1-Total` or 13 | `1-(SpecializedTraded+UnspecializedTraded)`) is the percentage employed in local clusters.} 14 | \item{value}{Double. The value indicated by the other variables.} 15 | } 16 | } 17 | \source{ 18 | CMAP traded clusters report 19 | } 20 | \usage{ 21 | traded_emp_by_race 22 | } 23 | \description{ 24 | A test dataset containing the percentage breakdowns of the working population employed in traded clusters, by race. 25 | } 26 | \examples{ 27 | # a stacked bar chart 28 | \dontshow{library(dplyr)} 29 | df <- dplyr::filter( 30 | traded_emp_by_race, 31 | variable \%in\% c("SpecializedTraded", "UnspecializedTraded") 32 | ) 33 | ggplot(df, aes(x = reorder(Race, -value), y = value, fill = variable)) + 34 | geom_col(position = position_stack(reverse = TRUE)) + 35 | scale_y_continuous(labels = scales::percent) 36 | 37 | } 38 | \keyword{datasets} 39 | -------------------------------------------------------------------------------- /man/cmapplot.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cmapplot.R 3 | \docType{package} 4 | \name{cmapplot} 5 | \alias{cmapplot-package} 6 | \alias{cmapplot} 7 | \title{cmapplot} 8 | \description{ 9 | This package contains extra palettes, themes and geoms for \pkg{ggplot2}, 10 | based on Chicago Metropolitan Agency for Planning (CMAP) design guidelines. 11 | } 12 | \details{ 13 | Detailed documentation can be viewed at 14 | \url{https://cmap-repos.github.io/cmapplot}. 15 | 16 | Please report issues and suggest improvements at 17 | \url{https://github.com/CMAP-REPOS/cmapplot/issues}. 18 | } 19 | \seealso{ 20 | Useful links: 21 | \itemize{ 22 | \item \url{https://cmap-repos.github.io/cmapplot} 23 | \item \url{https://github.com/CMAP-REPOS/cmapplot} 24 | \item Report bugs at \url{https://github.com/CMAP-REPOS/cmapplot/issues} 25 | } 26 | 27 | } 28 | \author{ 29 | \strong{Maintainer}: Sean Connelly \email{sconnelly@cmap.illinois.gov} 30 | 31 | Authors: 32 | \itemize{ 33 | \item Daniel Comeaux (\href{https://orcid.org/0000-0002-8412-9092}{ORCID}) 34 | \item Sarah Buchhorn 35 | \item Martin Menninger \email{mmenninger@cmap.illinois.gov} 36 | \item Noel Peterson (\href{https://orcid.org/0000-0003-1290-2362}{ORCID}) 37 | \item Greta Ritzenthaler 38 | \item Matthew Stern (\href{https://orcid.org/0000-0001-9119-2121}{ORCID}) 39 | \item David Wells \email{dwells@cmap.illinois.gov} 40 | } 41 | 42 | Other contributors: 43 | \itemize{ 44 | \item Chicago Metropolitan Agency for Planning [copyright holder, funder] 45 | } 46 | 47 | } 48 | \keyword{internal} 49 | -------------------------------------------------------------------------------- /man/cmap_fill_continuous.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/colors_continuous.R 3 | \name{cmap_fill_continuous} 4 | \alias{cmap_fill_continuous} 5 | \alias{cmap_color_continuous} 6 | \alias{cmap_colour_continuous} 7 | \title{Apply continuous CMAP palettes (gradients) to ggplot2 aesthetics} 8 | \usage{ 9 | cmap_fill_continuous(palette = "blues", reverse = FALSE, middle = 0, ...) 10 | 11 | cmap_color_continuous(palette = "blues", reverse = FALSE, middle = 0, ...) 12 | 13 | cmap_colour_continuous(palette = "blues", reverse = FALSE, middle = 0, ...) 14 | } 15 | \arguments{ 16 | \item{palette}{String; Choose from 'cmap_gradients' list} 17 | 18 | \item{reverse}{Logical; Reverse color order?} 19 | 20 | \item{middle}{Numeric; Sets midpoint for diverging color palettes. Default = 21 | 0.} 22 | 23 | \item{...}{Additional parameters passed on to the scale type} 24 | } 25 | \description{ 26 | Pick the function depending on the aesthetic of your ggplot object (fill or 27 | color). On diverging palettes, a midpoint can be manually adjusted (defaults 28 | to 0). See \code{\link{cmap_gradients}} for a listing of available gradients. 29 | } 30 | \section{Functions}{ 31 | \itemize{ 32 | \item \code{cmap_fill_continuous()}: for fill aesthetic 33 | 34 | \item \code{cmap_color_continuous()}: for color aesthetic 35 | 36 | \item \code{cmap_colour_continuous()}: for color aesthetic 37 | 38 | }} 39 | \examples{ 40 | ggplot(dplyr::filter(grp_over_time, cluster=="Biopharmaceuticals"), 41 | aes(x = year, y = realgrp, color = realgrp)) + 42 | geom_line() + 43 | cmap_color_continuous(palette = "red_purple") 44 | 45 | } 46 | -------------------------------------------------------------------------------- /man/cmap_fill_discrete.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/colors_discrete.R 3 | \name{cmap_fill_discrete} 4 | \alias{cmap_fill_discrete} 5 | \alias{cmap_color_discrete} 6 | \alias{cmap_colour_discrete} 7 | \title{Apply discrete CMAP palettes to ggplot2 aesthetics} 8 | \usage{ 9 | cmap_fill_discrete(palette = "main", reverse = FALSE, ...) 10 | 11 | cmap_color_discrete(palette = "main", reverse = FALSE, ...) 12 | 13 | cmap_colour_discrete(palette = "main", reverse = FALSE, ...) 14 | } 15 | \arguments{ 16 | \item{palette}{Choose from 'cmap_palettes' list, or use one of the gradients 17 | defined in the 'cmap_gradients' list (gradients will be automatically 18 | converted into discrete bins)} 19 | 20 | \item{reverse}{Logical; reverse color order?} 21 | 22 | \item{...}{Additional parameters passed on to the scale type} 23 | } 24 | \description{ 25 | Pick the function depending on the aesthetic of your ggplot object (fill or 26 | color). See \code{link{cmap_palettes}} for a listing of available gradients. 27 | } 28 | \section{Functions}{ 29 | \itemize{ 30 | \item \code{cmap_fill_discrete()}: For fill aesthetic 31 | 32 | \item \code{cmap_color_discrete()}: For color aesthetic 33 | 34 | \item \code{cmap_colour_discrete()}: For color aesthetic 35 | 36 | }} 37 | \examples{ 38 | ggplot(pop_and_laborforce_by_age, aes(x = variable, y = value, fill = age)) + 39 | geom_col(position = position_stack(reverse = TRUE)) + 40 | facet_wrap(~year) + 41 | cmap_fill_discrete(palette = "community") 42 | 43 | ggplot(percentile_wages, aes(x = percentile, y = wage, color = cluster)) + 44 | geom_line() + 45 | cmap_color_discrete(palette = "prosperity") 46 | 47 | } 48 | -------------------------------------------------------------------------------- /pkgdown/_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://cmap-repos.github.io/cmapplot 2 | 3 | home: 4 | title: CMAP Themes and Color Palettes 5 | description: > 6 | Provides themes and color scales for 'ggplot2', based on Chicago 7 | Metropolitan Agency for Planning (CMAP) design guidelines. 8 | 9 | authors: 10 | Chicago Metropolitan Agency for Planning: 11 | href: https://www.cmap.illinois.gov 12 | html: > 13 | Chicago Metropolitan Agency for Planning logo 15 | 16 | template: 17 | params: 18 | bootswatch: flatly 19 | 20 | reference: 21 | - title: Key functions 22 | - contents: 23 | - theme_cmap 24 | - finalize_plot 25 | - title: Additional geoms & auxiliary functions 26 | - contents: 27 | - starts_with("geom_") 28 | - ends_with("cmap_default_aes") 29 | - abbr_years 30 | - title: Color palettes & gradients 31 | - contents: 32 | - starts_with("viz_") 33 | - starts_with("cmap_fill_") 34 | - title: Sample datasets 35 | - contents: 36 | - cluster_jobchange 37 | - economy_basic 38 | - peer_grp 39 | - grp_over_time 40 | - percentile_wages 41 | - pop_and_laborforce_by_age 42 | - traded_emp_by_race 43 | - transit_ridership 44 | - vehicle_ownership 45 | - title: Lesser used objects 46 | - contents: 47 | - contains("cmapplot_global") 48 | - gg_lwd_convert 49 | - update_recessions 50 | - customproto 51 | 52 | articles: 53 | - title: Articles 54 | navbar: ~ 55 | contents: 56 | - installation 57 | - plots 58 | - finalize 59 | - colors 60 | - cookbook 61 | 62 | resource_files: 63 | - man/figures/margins1.png 64 | - man/figures/margins2.png 65 | -------------------------------------------------------------------------------- /man/customproto.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/geom_pandemics.R, R/geom_recessions.R, 3 | % R/geom_text_lastonly.R, R/geom_text_lastonly_repel.R 4 | \docType{data} 5 | \name{customproto} 6 | \alias{customproto} 7 | \alias{GeomPandemics} 8 | \alias{GeomPandemicsText} 9 | \alias{GeomRecessions} 10 | \alias{GeomRecessionsText} 11 | \alias{GeomTextLast} 12 | \alias{GeomPointLast} 13 | \alias{GeomTextLastRepel} 14 | \title{Custom ggproto classes} 15 | \description{ 16 | The \code{cmapplot} package contains a few custom ggproto objects. For the 17 | most part, these are slightly tweaked versions of ggplot2's default proto 18 | objects. For more information about these, see 19 | \code{\link[ggplot2:ggplot2-ggproto]{ggplot2::ggplot2-ggproto}}. 20 | 21 | The \code{cmapplot} package contains a few custom ggproto objects. For the 22 | most part, these are slightly tweaked versions of ggplot2's default proto 23 | objects. For more information about these, see 24 | \code{\link[ggplot2:ggplot2-ggproto]{ggplot2::ggplot2-ggproto}}. 25 | } 26 | \section{Functions}{ 27 | \itemize{ 28 | \item \code{GeomPandemics}: Add Pandemic bars to plot. 29 | 30 | \item \code{GeomPandemicsText}: Add Pandemic bar labels to plot. 31 | 32 | \item \code{GeomRecessions}: Add recession bars to plot. 33 | 34 | \item \code{GeomRecessionsText}: Add recession bar labels to plot. 35 | 36 | \item \code{GeomTextLast}: Add text to plot for maximum x-value in dataset only. 37 | 38 | \item \code{GeomPointLast}: Add points to plot for maximum x-value in dataset only. 39 | 40 | \item \code{GeomTextLastRepel}: Add text to plot for maximum x-value in dataset only. 41 | 42 | \item \code{GeomPointLast}: Add points to plot for maximum x-value in dataset only. 43 | 44 | }} 45 | \keyword{datasets} 46 | -------------------------------------------------------------------------------- /man/cmap_fill_race.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/colors_race.R 3 | \name{cmap_fill_race} 4 | \alias{cmap_fill_race} 5 | \alias{cmap_color_race} 6 | \alias{cmap_colour_race} 7 | \title{Apply official CMAP race/ethnicity chart colors to ggplot2 aesthetics} 8 | \usage{ 9 | cmap_fill_race(white, black, hispanic, asian, other) 10 | 11 | cmap_color_race(white, black, hispanic, asian, other) 12 | 13 | cmap_colour_race(white, black, hispanic, asian, other) 14 | } 15 | \arguments{ 16 | \item{white}{Data value to map CMAP's White/Caucasian color onto (case-sensitive).} 17 | 18 | \item{black}{Data value to map CMAP's Black/African American color onto (case-sensitive).} 19 | 20 | \item{hispanic}{Data value to map CMAP's Hispanic/Latino color onto (case-sensitive).} 21 | 22 | \item{asian}{Data value to map CMAP's Asian color onto (case-sensitive).} 23 | 24 | \item{other}{Data value to map CMAP's Other/Multiple Races color onto (case-sensitive).} 25 | } 26 | \description{ 27 | Pick the function depending on the aesthetic of your ggplot object (fill or color). 28 | Specify your dataset's unique race factor names (as case-sensitive strings) in the arguments. 29 | All categories are optional in case your dataset does not have some of them or contains the default 30 | values of the race palette. 31 | } 32 | \section{Functions}{ 33 | \itemize{ 34 | \item \code{cmap_fill_race()}: For fill aesthetic 35 | 36 | \item \code{cmap_color_race()}: For color aesthetic 37 | 38 | \item \code{cmap_colour_race()}: For color aesthetic 39 | 40 | }} 41 | \examples{ 42 | ggplot(dplyr::filter(traded_emp_by_race, Race!="Regional average" & 43 | variable=="SpecializedTraded")) + 44 | geom_col(aes(x = Race, y = value, fill = Race)) + 45 | cmap_fill_race(white = "White", black = "Black", 46 | hispanic = "Hispanic", asian = "Asian", 47 | other = "Other") 48 | 49 | } 50 | -------------------------------------------------------------------------------- /man/update_recessions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/geom_recessions.R 3 | \name{update_recessions} 4 | \alias{update_recessions} 5 | \title{Update recessions table} 6 | \source{ 7 | \url{https://www.nber.org/data/cycles/cycle dates pasted.csv} 8 | } 9 | \usage{ 10 | update_recessions(url = NULL, quietly = FALSE) 11 | } 12 | \arguments{ 13 | \item{url}{Char, the web location of the NBER machine-readable CSV file. The 14 | default, \code{NULL}, uses the most recently identified URL known to the 15 | package development team, which appears to be the most stable location for 16 | updates over time.} 17 | 18 | \item{quietly}{Logical, suppresses messages produced by 19 | \code{utils::download.file}.} 20 | } 21 | \value{ 22 | A data frame with the following variables: \itemize{ \item 23 | \code{start_char, end_char}: Chr. Easily readable labels for the beginning 24 | and end of the recession. \item \code{start_date, end_date}: Date. Dates 25 | expressed in R datetime format, using the first day of the specified month. 26 | \item \code{ongoing}: Logical. Whether or not the recession is ongoing as of 27 | the latest available NBER data. } 28 | } 29 | \description{ 30 | The cmapplot package contains an internal dataset \code{recessions} of all 31 | recessions in American history as recorded by the National Bureau of Economic 32 | Research (NBER). However, users may need to replace the built-in data, such as 33 | in the event of new recessions and/or changes to the NBER consensus on 34 | recession dates. This function fetches and interprets this data from the NBER 35 | website. 36 | } 37 | \examples{ 38 | recessions <- update_recessions() 39 | 40 | # package maintainers can update the internal dataset from within 41 | # package by running the following code: 42 | \dontrun{ 43 | recessions <- update_recessions() 44 | usethis::use_data(recessions, internal = TRUE, overwrite = TRUE) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /man/position_nudge_repel.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/position_nudge_repel.R 3 | \name{position_nudge_repel} 4 | \alias{position_nudge_repel} 5 | \title{Position Nudge Repel} 6 | \arguments{ 7 | \item{x, y}{Amount of horizontal and vertical distance to move. Same units 8 | as the data on the x and y axes.} 9 | } 10 | \description{ 11 | Nudge labels a fixed distance from points 12 | } 13 | \details{ 14 | \code{position_nudge_repel} is useful for adjusting the starting 15 | position of text labels before they are repelled from data points. 16 | } 17 | \examples{ 18 | 19 | library(ggrepel) 20 | 21 | df <- data.frame( 22 | x = c(1,3,2,5), 23 | y = c("a","c","d","c") 24 | ) 25 | 26 | ggplot(df, aes(x, y)) + 27 | geom_point() + 28 | geom_text_repel(aes(label = y)) 29 | 30 | ggplot(df, aes(x, y)) + 31 | geom_point() + 32 | geom_text_repel( 33 | aes(label = y), 34 | min.segment.length = 0, 35 | position = position_nudge_repel(x = 0.1, y = 0.15) 36 | ) 37 | 38 | # The values for x and y can be vectors 39 | ggplot(df, aes(x, y)) + 40 | geom_point() + 41 | geom_text_repel( 42 | aes(label = y), 43 | min.segment.length = 0, 44 | position = position_nudge_repel( 45 | x = c(0.1, 0, -0.1, 0), 46 | y = c(0.1, 0.2, -0.1, -0.2) 47 | ) 48 | ) 49 | 50 | # We can also use geom_text_repel() with arguments nudge_x, nudge_y 51 | ggplot(df, aes(x, y)) + 52 | geom_point() + 53 | geom_text_repel( 54 | aes(label = y), 55 | min.segment.length = 0, 56 | nudge_x = 0.1, 57 | nudge_y = 0.15 58 | ) 59 | 60 | # The arguments nudge_x, nudge_y also accept vectors 61 | ggplot(df, aes(x, y)) + 62 | geom_point() + 63 | geom_text_repel( 64 | aes(label = y), 65 | min.segment.length = 0, 66 | nudge_x = c(0.1, 0, -0.1, 0), 67 | nudge_y = c(0.1, 0.2, -0.1, -0.2) 68 | ) 69 | 70 | } 71 | \concept{position adjustments} 72 | \keyword{internal} 73 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: cmapplot 2 | Title: CMAP Themes and Color Palettes 3 | Version: 1.2.3 4 | Authors@R: c( 5 | person("Daniel", "Comeaux", 6 | role = c("aut"), 7 | comment = c(ORCID = "0000-0002-8412-9092")), 8 | person("Sarah", "Buchhorn", 9 | role = "aut", 10 | email = ""), 11 | person("Martin", "Menninger", 12 | role = "aut", 13 | email = "mmenninger@cmap.illinois.gov"), 14 | person("Noel", "Peterson", 15 | role = "aut", 16 | comment = c(ORCID = "0000-0003-1290-2362")), 17 | person("Greta", "Ritzenthaler", 18 | role = "aut"), 19 | person("Matthew", "Stern", 20 | role = "aut", 21 | comment = c(ORCID = "0000-0001-9119-2121")), 22 | person("Sean", "Connelly", 23 | role = c("aut", "cre"), 24 | email = "sconnelly@cmap.illinois.gov"), 25 | person("David", "Wells", 26 | role = "aut", 27 | email = "dwells@cmap.illinois.gov"), 28 | person("Chicago Metropolitan Agency for Planning", 29 | role = c("cph", "fnd"))) 30 | Description: Provides themes and color scales for 'ggplot2', based on Chicago 31 | Metropolitan Agency for Planning (CMAP) design guidelines. 32 | URL: https://cmap-repos.github.io/cmapplot, https://github.com/CMAP-REPOS/cmapplot 33 | BugReports: https://github.com/CMAP-REPOS/cmapplot/issues 34 | License: MIT + file LICENSE 35 | Encoding: UTF-8 36 | LazyData: true 37 | Depends: 38 | R (>= 3.5.0), 39 | ggplot2 40 | Imports: 41 | dplyr, 42 | generics, 43 | ggpubr, 44 | glue, 45 | graphics, 46 | grDevices, 47 | grid, 48 | gridExtra, 49 | gridtext, 50 | ggrepel, 51 | lubridate, 52 | purrr, 53 | ragg, 54 | Rcpp, 55 | rlang, 56 | rstudioapi, 57 | scales, 58 | stringr, 59 | systemfonts, 60 | tibble, 61 | withr 62 | Suggests: 63 | knitr, 64 | rmarkdown, 65 | testthat (>= 3.0.0), 66 | tidyverse 67 | RoxygenNote: 7.3.3 68 | VignetteBuilder: knitr 69 | Config/testthat/edition: 3 70 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(makeContent,textlastrepel) 4 | export(.lwd) 5 | export(GeomPandemics) 6 | export(GeomPandemicsText) 7 | export(GeomPointLast) 8 | export(GeomRecessions) 9 | export(GeomRecessionsText) 10 | export(GeomTextLast) 11 | export(GeomTextLastRepel) 12 | export(abbr_years) 13 | export(apply_cmap_default_aes) 14 | export(cmap_color_continuous) 15 | export(cmap_color_discrete) 16 | export(cmap_color_highlight) 17 | export(cmap_color_race) 18 | export(cmap_colour_continuous) 19 | export(cmap_colour_discrete) 20 | export(cmap_colour_highlight) 21 | export(cmap_colour_race) 22 | export(cmap_fill_continuous) 23 | export(cmap_fill_discrete) 24 | export(cmap_fill_highlight) 25 | export(cmap_fill_race) 26 | export(fetch_pal) 27 | export(finalize_plot) 28 | export(geom_pandemics) 29 | export(geom_recessions) 30 | export(geom_text_lastonly) 31 | export(geom_text_lastonly_repel) 32 | export(get_cmapplot_global) 33 | export(get_cmapplot_globals) 34 | export(gg_lwd_convert) 35 | export(position_nudge_repel) 36 | export(set_cmapplot_global) 37 | export(theme_cmap) 38 | export(unapply_cmap_default_aes) 39 | export(update_recessions) 40 | export(viz_gradient) 41 | export(viz_palette) 42 | import(Rcpp) 43 | import(dplyr) 44 | import(ggplot2) 45 | import(ggrepel) 46 | import(grDevices) 47 | import(graphics) 48 | import(grid) 49 | import(gridtext) 50 | import(ragg) 51 | import(rlang) 52 | import(rstudioapi) 53 | import(scales) 54 | import(systemfonts) 55 | importFrom(generics,intersect) 56 | importFrom(ggpubr,get_legend) 57 | importFrom(glue,glue) 58 | importFrom(glue,glue_collapse) 59 | importFrom(gridExtra,arrangeGrob) 60 | importFrom(lubridate,day) 61 | importFrom(lubridate,decimal_date) 62 | importFrom(lubridate,month) 63 | importFrom(lubridate,year) 64 | importFrom(purrr,compact) 65 | importFrom(purrr,map) 66 | importFrom(purrr,walk2) 67 | importFrom(stats,na.omit) 68 | importFrom(stringr,str_length) 69 | importFrom(stringr,str_replace) 70 | importFrom(stringr,str_trunc) 71 | importFrom(tibble,tribble) 72 | importFrom(utils,modifyList) 73 | importFrom(utils,read.csv) 74 | -------------------------------------------------------------------------------- /man/dot-lwd.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utilities.R 3 | \docType{data} 4 | \name{.lwd} 5 | \alias{.lwd} 6 | \alias{gg_lwd_convert} 7 | \title{Line width conversion} 8 | \format{ 9 | An object of class \code{numeric} of length 1. 10 | } 11 | \usage{ 12 | .lwd 13 | 14 | gg_lwd_convert(value, unit = "bigpts") 15 | } 16 | \arguments{ 17 | \item{value}{Numeric, the value to be converted.} 18 | 19 | \item{unit}{Char, the unit of the value to be converted. Can be any of the 20 | units accepted by \code{grid::unit()}, including "bigpts", "pt", "mm", and 21 | "in". Default is \code{bigpts}.} 22 | } 23 | \description{ 24 | The factor \code{.lwd} is used to calculate correct output sizes for line 25 | widths. For line widths in \code{ggplot2}, the size in mm must be divided 26 | by this factor for correct output. Because the user is likely to prefer 27 | other units besides for mm, \code{gg_lwd_convert()} is provided as a 28 | convenience function, converting from any unit all the way to ggplot units. 29 | } 30 | \details{ 31 | \code{.lwd} is equal to \code{ggplot2::.stroke / ggplot2::.pt}. In 32 | \code{ggplot2}, the size in mm is divided by \code{.lwd} to achieve the 33 | correct output. In the \code{grid} package, however, the size in points 34 | (\code{pts} (or maybe \code{bigpts}? Unclear.) must be divided by 35 | \code{.lwd}. The user is unlikely to interact directly with \code{grid}, 36 | but this is how \code{finalize_plot()} does its work. 37 | 38 | This is closely related to \code{ggplot::.pt}, which is the factor that 39 | font sizes (in \code{pts}) must be divided by for text geoms within 40 | \code{ggplot2}. Confusingly, \code{.pt} is not required for \code{ggplot2} 41 | font sizes outside the plot area: e.g. axis titles, etc. 42 | } 43 | \section{Functions}{ 44 | \itemize{ 45 | \item \code{gg_lwd_convert()}: Function to convert from any unit directly to ggplot2's 46 | preferred millimeters. 47 | 48 | }} 49 | \examples{ 50 | ggplot() + coord_cartesian(xlim = c(-3, 3), ylim = c(-3, 3)) + 51 | 52 | # a green line 3 points wide 53 | geom_hline(yintercept = 1, color = "green", size = gg_lwd_convert(3)) + 54 | 55 | # black text of size 24 points 56 | annotate("text", -2, 0, label = "text", size = 24/ggplot2::.pt) 57 | 58 | 59 | # a blue line 6 points wide, drawn over the plot with the `grid` package 60 | grid::grid.lines(y = 0.4, gp = grid::gpar(col = "blue", lwd = 6 / .lwd)) 61 | 62 | } 63 | \seealso{ 64 | grid's \code{\link[grid]{unit}}, ggplot2's 65 | \code{\link[ggplot2]{.pt}}, and 66 | \url{https://stackoverflow.com/questions/17311917/ggplot2-the-unit-of-size} 67 | } 68 | \keyword{datasets} 69 | -------------------------------------------------------------------------------- /man/abbr_years.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/axis_handling.R 3 | \name{abbr_years} 4 | \alias{abbr_years} 5 | \title{Axis handling helper functions} 6 | \usage{ 7 | abbr_years(full_by_pos = c(1), full_by_year = NULL, dateaxis = FALSE) 8 | } 9 | \arguments{ 10 | \item{full_by_pos}{Vector of integers, the position of breaks that should not 11 | be abbreviated. This defaults to \code{c(1)}, which retains the original 12 | first label and abbreviates subsequent ones. If all breaks should be 13 | abbreviated, this can be set to NULL.} 14 | 15 | \item{full_by_year}{Vector of integers, the value of breaks that should not be 16 | abbreviated. Defaults to NULL.} 17 | 18 | \item{dateaxis}{Bool. \code{FALSE}, the default, directs the function to treat 19 | the breaks as integers. If set to \code{TRUE} the function will instead 20 | treat the breaks as date objects. \code{TRUE} should be used when called 21 | within a \code{scale_*_date} ggplot element.} 22 | } 23 | \description{ 24 | `abbr_years()` is a helper functions that allows users to abbreviate year 25 | labels to their two-digit representation (e.g., 2008 to '08), but not 26 | abbreviate any specified breaks. It does so by creating a new function that 27 | takes the breaks supplied by \code{ggplot2} as its only argument. The 28 | function was modeled after the syntax and approach of the labeling functions 29 | in the \code{scales::label_*} family. 30 | } 31 | \examples{ 32 | 33 | # basic functionality 34 | abbr_years()(c(2010:2020)) 35 | abbr_years(full_by_year = 2000)(c(1990:2010)) 36 | 37 | 38 | # Default implementation - this will abbreviate all labels except the first 39 | # for both continuous and date scales. 40 | 41 | df2 <- dplyr::mutate(transit_ridership, year2 = as.Date(lubridate::date_decimal(year))) 42 | df1 <- dplyr::filter(df2, year >= 2000) 43 | 44 | ggplot(df1, 45 | aes(x = year, y = ridership, color = system)) + 46 | geom_line() + 47 | scale_x_continuous(labels = abbr_years()) 48 | 49 | ggplot(df1, 50 | aes(x = year2, y = ridership, color = system)) + 51 | geom_line() + 52 | scale_x_date(labels = abbr_years(dateaxis = TRUE)) 53 | 54 | # If customizations are desired, users can use \code{full_by_pos} and/or 55 | # \code{full_by_year} to maintain the full version of the specified labels. 56 | 57 | ggplot(df2, 58 | aes(x = year2, y = ridership, color = system)) + 59 | geom_line() + 60 | scale_x_date(labels = abbr_years(full_by_year = c(2000), dateaxis = TRUE)) 61 | 62 | # You can also remove the default maintenance of the first label and only 63 | # specify specific years. 64 | ggplot(df2, 65 | aes(x = year, y = ridership, color = system)) + 66 | geom_line() + 67 | scale_x_continuous(labels = abbr_years(full_by_pos = NULL, 68 | full_by_year = c(1990,2020))) 69 | 70 | 71 | } 72 | -------------------------------------------------------------------------------- /R/colors_race.R: -------------------------------------------------------------------------------- 1 | #' Race palette prep 2 | #' 3 | #' @param white White/Caucasian 4 | #' @param black Black/African American 5 | #' @param hispanic Hispanic/Latino 6 | #' @param asian Asian 7 | #' @param other Other 8 | #' 9 | #' @noRd 10 | make_race_palette <- function(white, black, hispanic, asian, other) { 11 | 12 | race_palette <- fetch_pal("race") 13 | pal <- c() 14 | 15 | if (missing(white) & missing(black) & missing(hispanic) & missing(asian) & missing(other)) { 16 | pal <- race_palette # if no parameters specified, return default race palette 17 | } else { 18 | 19 | passed <- unlist(as.list(match.call())[-1]) # vector of args actually passed 20 | 21 | for (i in names(race_palette)) { 22 | if (i %in% names(passed)) { 23 | pal[passed[i]] <- race_palette[i] 24 | } 25 | } 26 | } 27 | 28 | return (pal) 29 | } 30 | 31 | #' Apply official CMAP race/ethnicity chart colors to ggplot2 aesthetics 32 | #' 33 | #' Pick the function depending on the aesthetic of your ggplot object (fill or color). 34 | #' Specify your dataset's unique race factor names (as case-sensitive strings) in the arguments. 35 | #' All categories are optional in case your dataset does not have some of them or contains the default 36 | #' values of the race palette. 37 | #' 38 | #' @param white Data value to map CMAP's White/Caucasian color onto (case-sensitive). 39 | #' @param black Data value to map CMAP's Black/African American color onto (case-sensitive). 40 | #' @param hispanic Data value to map CMAP's Hispanic/Latino color onto (case-sensitive). 41 | #' @param asian Data value to map CMAP's Asian color onto (case-sensitive). 42 | #' @param other Data value to map CMAP's Other/Multiple Races color onto (case-sensitive). 43 | #' 44 | #' @examples 45 | #' ggplot(dplyr::filter(traded_emp_by_race, Race!="Regional average" & 46 | #' variable=="SpecializedTraded")) + 47 | #' geom_col(aes(x = Race, y = value, fill = Race)) + 48 | #' cmap_fill_race(white = "White", black = "Black", 49 | #' hispanic = "Hispanic", asian = "Asian", 50 | #' other = "Other") 51 | #' 52 | #' @describeIn cmap_fill_race For fill aesthetic 53 | #' @export 54 | cmap_fill_race <- function(white, black, hispanic, asian, other) { 55 | .args <- as.list(match.call()[-1]) 56 | race_palette <- do.call(make_race_palette, .args) 57 | ggplot2::scale_fill_manual(values = race_palette) 58 | } 59 | 60 | 61 | #' @describeIn cmap_fill_race For color aesthetic 62 | #' @export 63 | cmap_color_race <- function(white, black, hispanic, asian, other) { 64 | .args <- as.list(match.call()[-1]) 65 | race_palette <- do.call(make_race_palette, .args) 66 | ggplot2::scale_color_manual(values = race_palette) 67 | } 68 | 69 | #' @describeIn cmap_fill_race For color aesthetic 70 | #' @export 71 | cmap_colour_race <- cmap_color_race 72 | -------------------------------------------------------------------------------- /man/viz_palette.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/colors_continuous.R, R/colors_discrete.R, 3 | % R/utilities.R 4 | \name{viz_gradient} 5 | \alias{viz_gradient} 6 | \alias{viz_palette} 7 | \alias{cmap_palettes} 8 | \alias{cmap_gradients} 9 | \alias{cmap_colors} 10 | \alias{fetch_pal} 11 | \title{Visualizing CMAP color palettes} 12 | \usage{ 13 | viz_gradient(pal, ttl = NULL) 14 | 15 | viz_palette(pal, ttl = NULL, num = NULL) 16 | 17 | fetch_pal( 18 | pal, 19 | which = c("discrete", "sequential", "divergent"), 20 | return = c("colors", "type", "exists") 21 | ) 22 | } 23 | \arguments{ 24 | \item{pal}{character, name of a a cmapplot palette, or a vector of colors 25 | representing a palette} 26 | 27 | \item{ttl}{character, title to be displayed (the name of the palette)} 28 | 29 | \item{num}{numeric, the number of colors to display} 30 | 31 | \item{which}{a vector of palette types to consider} 32 | 33 | \item{return}{Value to return. "colors", the default, returns the palette as 34 | a vector of colors. "type" returns the palette's type. "Exists" returns 35 | TRUE or FALSE based on whether the name is found in the palettes table.} 36 | } 37 | \description{ 38 | The cmapplot package contains a many color palettes extracted from the 39 | larger, official CMAP color palette. Helper functions allow the user to 40 | inspect the various palettes before applying them to plots. 41 | } 42 | \details{ 43 | Palettes are stored in a tibble the \code{cmapplot_globals} environment. The 44 | user can access this tibble with \code{\link{get_cmapplot_global}}, but it is 45 | easier to access information about a single palette with \code{fetch_pal}. 46 | 47 | \code{viz_palette} and \code{viz_gradient} draw the palette to the plots 48 | window. These functions are modified with respect from the 49 | \href{https://github.com/ropenscilabs/ochRe}{ochRe package}. 50 | 51 | For more information about available cmapplot color palettes and how to apply 52 | them, see \code{vignette("colors")}. 53 | } 54 | \section{Functions}{ 55 | \itemize{ 56 | \item \code{viz_gradient()}: Interpolates the range of colors a sequential or 57 | divergent palette offers when used on a continuous scale. 58 | 59 | \item \code{viz_palette()}: Displays the colors of any cmapplot palette 60 | 61 | \item \code{fetch_pal()}: Returns details about a palette 62 | 63 | }} 64 | \examples{ 65 | # Vizualize a sequential or divergent palette with interpolation 66 | viz_gradient("green_teal_blue") 67 | 68 | # Visualize a single palette as individual colors 69 | viz_palette("legislation") 70 | 71 | # Print names and types of all available palettes 72 | as.data.frame(get_cmapplot_global("palettes")[1:2]) 73 | 74 | # Identify the first two colors of the Prosperity Palette 75 | fetch_pal("prosperity")[1:2] 76 | 77 | # Confirm that "reds" is a sequential palette 78 | fetch_pal("reds", which = "sequential", return = "exists") 79 | 80 | } 81 | -------------------------------------------------------------------------------- /man/cmap_default_aes.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/default_aes.R 3 | \name{cmap_default_aes} 4 | \alias{cmap_default_aes} 5 | \alias{apply_cmap_default_aes} 6 | \alias{unapply_cmap_default_aes} 7 | \title{Fetch and set aesthetic defaults} 8 | \usage{ 9 | apply_cmap_default_aes(quietly = FALSE) 10 | 11 | unapply_cmap_default_aes(quietly = FALSE) 12 | } 13 | \arguments{ 14 | \item{quietly}{Should the function suppress confirmation message?} 15 | } 16 | \description{ 17 | These functions allow for setting and resetting default aesthetic values for 18 | certain ggplot2 geoms. This is necessary for geoms to be "themed" to CMAP 19 | style standards, because (at least at the moment) setting geom aesthetic 20 | defaults on a plot-by-plot basis (such as with \code{ggplot2::theme}) is not 21 | possible. The geoms impacted are stored in 22 | \code{cmapplot_globals$geoms_that_change}. 23 | } 24 | \details{ 25 | These functions are employed implicitly within \code{\link{finalize_plot}} to 26 | apply preferred aesthetic defaults to final outputs. They are only necessary 27 | to use explicitly if you would like plots to use these defaults 28 | pre-\code{finalize}. 29 | 30 | CAUTION: Running \code{apply_cmap_default_aes} will set defaults for all 31 | ggplot2 plots drawn in the current session. To reset to \code{ggplot2} 32 | defaults (technically, to whatever the defaults were when \code{cmapplot} 33 | was loaded), use \code{unapply_cmap_default_aes}. 34 | 35 | Note: CMAP aesthetic defaults are loaded into 36 | \code{cmapplot_globals$default_aes_cmap} by the internal pkg function 37 | \code{init_cmap_default_aes} when \code{cmapplot} is first loaded into R. 38 | } 39 | \section{Functions}{ 40 | \itemize{ 41 | \item \code{apply_cmap_default_aes()}: Apply CMAP aesthetic defaults to all ggplots in 42 | the current session 43 | 44 | \item \code{unapply_cmap_default_aes()}: Reset modified geom aesthetics to their values 45 | when \code{cmapplot} was first loaded 46 | 47 | }} 48 | \examples{ 49 | \dontrun{ 50 | g <- ggplot(filter(grp_over_time, category == "Services"), 51 | aes(x = year, y = realgrp, color = cluster)) + 52 | geom_recessions(ymax = 0.4, text_nudge_x = 0.1) + 53 | theme_cmap(hline = 0, 54 | axislines = "x", 55 | legend.max.columns = 2) + 56 | ggtitle("Change in gross regional product over time") + 57 | geom_line() + 58 | scale_x_continuous("Year", breaks = seq(2007, 2017, 2)) + 59 | coord_cartesian(clip = "off") + 60 | geom_text_lastonly(aes(label = realgrp), add_points = TRUE) 61 | 62 | # print normally 63 | g 64 | 65 | # overwrite `default_aes` 66 | apply_cmap_default_aes() 67 | g 68 | 69 | # reset `default_aes` 70 | unapply_cmap_default_aes() 71 | g 72 | 73 | # finalize alters the defaults implicitly, but then resets them automatically. 74 | finalize_plot(g) 75 | 76 | # you can also use `finalize` without these modifications 77 | finalize_plot(g, use_cmap_aes = FALSE) 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /R/position_nudge_repel.R: -------------------------------------------------------------------------------- 1 | #' Position Nudge Repel 2 | #' 3 | #' Nudge labels a fixed distance from points 4 | #' 5 | #' \code{position_nudge_repel} is useful for adjusting the starting 6 | #' position of text labels before they are repelled from data points. 7 | #' 8 | #' @family position adjustments 9 | #' @param x,y Amount of horizontal and vertical distance to move. Same units 10 | #' as the data on the x and y axes. 11 | #' 12 | #' @examples 13 | #' 14 | #' library(ggrepel) 15 | #' 16 | #' df <- data.frame( 17 | #' x = c(1,3,2,5), 18 | #' y = c("a","c","d","c") 19 | #' ) 20 | #' 21 | #' ggplot(df, aes(x, y)) + 22 | #' geom_point() + 23 | #' geom_text_repel(aes(label = y)) 24 | #' 25 | #' ggplot(df, aes(x, y)) + 26 | #' geom_point() + 27 | #' geom_text_repel( 28 | #' aes(label = y), 29 | #' min.segment.length = 0, 30 | #' position = position_nudge_repel(x = 0.1, y = 0.15) 31 | #' ) 32 | #' 33 | #' # The values for x and y can be vectors 34 | #' ggplot(df, aes(x, y)) + 35 | #' geom_point() + 36 | #' geom_text_repel( 37 | #' aes(label = y), 38 | #' min.segment.length = 0, 39 | #' position = position_nudge_repel( 40 | #' x = c(0.1, 0, -0.1, 0), 41 | #' y = c(0.1, 0.2, -0.1, -0.2) 42 | #' ) 43 | #' ) 44 | #' 45 | #' # We can also use geom_text_repel() with arguments nudge_x, nudge_y 46 | #' ggplot(df, aes(x, y)) + 47 | #' geom_point() + 48 | #' geom_text_repel( 49 | #' aes(label = y), 50 | #' min.segment.length = 0, 51 | #' nudge_x = 0.1, 52 | #' nudge_y = 0.15 53 | #' ) 54 | #' 55 | #' # The arguments nudge_x, nudge_y also accept vectors 56 | #' ggplot(df, aes(x, y)) + 57 | #' geom_point() + 58 | #' geom_text_repel( 59 | #' aes(label = y), 60 | #' min.segment.length = 0, 61 | #' nudge_x = c(0.1, 0, -0.1, 0), 62 | #' nudge_y = c(0.1, 0.2, -0.1, -0.2) 63 | #' ) 64 | #' 65 | #' @format NULL 66 | #' @usage NULL 67 | #' @keywords internal 68 | #' @export 69 | position_nudge_repel <- function(x = 0, y = 0) { 70 | ggproto(NULL, PositionNudgeRepel, x = x, y = y) 71 | } 72 | 73 | PositionNudgeRepel <- ggproto( 74 | "PositionNudgeRepel", 75 | Position, 76 | x = 0, 77 | y = 0, 78 | 79 | setup_params = function(self, data) { 80 | list(x = self$x, y = self$y) 81 | }, 82 | 83 | compute_layer = function(self, data, params, layout) { 84 | x_orig <- data$x 85 | y_orig <- data$y 86 | # transform only the dimensions for which non-zero nudging is requested 87 | if (any(params$x != 0)) { 88 | if (any(params$y != 0)) { 89 | data <- transform_position(data, function(x) x + params$x, function(y) { 90 | y + params$y 91 | }) 92 | } else { 93 | data <- transform_position(data, function(x) x + params$x, NULL) 94 | } 95 | } else if (any(params$y != 0)) { 96 | data <- transform_position(data, NULL, function(y) y + params$y) 97 | } 98 | data$x_orig <- x_orig 99 | data$y_orig <- y_orig 100 | data 101 | } 102 | ) 103 | -------------------------------------------------------------------------------- /man/cmap_fill_highlight.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/colors_highlight.R 3 | \name{cmap_fill_highlight} 4 | \alias{cmap_fill_highlight} 5 | \alias{cmap_color_highlight} 6 | \alias{cmap_colour_highlight} 7 | \title{Highlight one or more discrete groups in a comparison graph} 8 | \usage{ 9 | cmap_fill_highlight( 10 | field, 11 | value, 12 | color_value = "#008FD5", 13 | color_other = "#b0bdcf" 14 | ) 15 | 16 | cmap_color_highlight( 17 | field, 18 | value, 19 | color_value = "#008FD5", 20 | color_other = "#b0bdcf" 21 | ) 22 | 23 | cmap_colour_highlight( 24 | field, 25 | value, 26 | color_value = "#008FD5", 27 | color_other = "#b0bdcf" 28 | ) 29 | } 30 | \arguments{ 31 | \item{field}{character vector, the vector in which the value to highlight is 32 | found. Values need not be unique. Typically, pass the table column that 33 | defines the color/fill aesthetic as 'table$field'} 34 | 35 | \item{value}{character string or vector, the name of group(s) to highlight} 36 | 37 | \item{color_value}{Specify the highlight color(s). Default is #00b0f0 (blue)} 38 | 39 | \item{color_other}{Specify non-highlighted color. Default is #b0bdcf (gray)} 40 | } 41 | \description{ 42 | Pick the function depending on the aesthetic of your ggplot object (fill or 43 | color). Specify the unique factor name(s) of the group(s) you're highlighting 44 | (as a case-sensitive string), and the vector it's found in. 45 | } 46 | \details{ 47 | You may specify multiple groups to highlight. If you do, you may specify a 48 | single highlight color, or a vector of highlight colors of equal length to 49 | the vector of highlight values. 50 | 51 | This function does not make any modifications to the legend, so legend 52 | behavior is not perfect out-of-the-box. For example, if the plot aesthetic 53 | differentiates between five unique values, all five values will appear in the 54 | legend, even though four use the same color. You will likely want to hide the 55 | legend altogether, or manufacture a new data field that contains only the 56 | value(s) to highlight and some generic "Other" label. 57 | } 58 | \section{Functions}{ 59 | \itemize{ 60 | \item \code{cmap_fill_highlight()}: For fill aesthetic 61 | 62 | \item \code{cmap_color_highlight()}: For color aesthetic 63 | 64 | \item \code{cmap_colour_highlight()}: For color aesthetic, if you're British 65 | 66 | }} 67 | \examples{ 68 | p <- ggplot(data = dplyr::filter(transit_ridership, year=="2019"), 69 | mapping = aes(x = system, y = ridership, fill = system)) + 70 | geom_col() 71 | 72 | # one value, default colors 73 | p + cmap_fill_highlight(field = transit_ridership$system, 74 | value = "metra") 75 | 76 | # multiple values, default colors 77 | p + cmap_fill_highlight(field = transit_ridership$system, 78 | value = c("metra", "pace_ada")) 79 | 80 | # multiple values, multiple colors 81 | p + cmap_fill_highlight( 82 | field = transit_ridership$system, 83 | value = c("metra", "pace_ada"), 84 | color_value = c("red", "orange") 85 | ) 86 | 87 | } 88 | -------------------------------------------------------------------------------- /vignettes/installation.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Installing cmapplot on a CMAP computer" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Installing cmapplot on a CMAP computer} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | This vignette provides an overview of how to install cmapplot, with a specific focus on CMAP-issued computers. This guide presumes that the user does *not* have administrator privileges. 11 | 12 | 13 | ## Prerequisites 14 | 15 | Your CMAP computer should already have both R and RStudio installed. If it does not, please submit an IT helpdesk request to install them. (While you can install RStudio on your own by [downloading](https://rstudio.com/products/rstudio/download/#download) the .zip -- *not* .exe -- version, installing R itself requires administrator privileges). 16 | 17 | Once you have working versions of R and RStudio on your computer, you will also need to download an additional utility called Rtools. You can download the installation file for Rtools [here](https://cran.rstudio.com/bin/windows/Rtools/). Because of IT restrictions, you will need to choose an installation folder that you have write access to. The Rtools installer defaults to **C:\\rtools40**, which is fine. However, if you do not want to install it there, you may create another folder in your own user sub-directory (e.g. **C:\\Users\\your_username\\rtools**). 18 | 19 | Finally, before you can install cmapplot, you will need to download and install the "devtools" and "tidyverse" packages. You can install them by running the following code in RStudio: 20 | 21 | ```{r install-devtools, eval=FALSE} 22 | ## Install devtools & tidyverse 23 | install.packages(c("devtools", "tidyverse")) 24 | ``` 25 | 26 | 27 | ## Installing and loading cmapplot 28 | 29 | Once you have successfully downloaded and installed Rtools and the "devtools" and "tidyverse" packages, you are ready to install cmapplot. Run the following code to install and load cmapplot: 30 | 31 | ```{r install-cmapplot, eval=FALSE} 32 | ## Install/update cmapplot from GitHub 33 | devtools::install_github("CMAP-REPOS/cmapplot", build_vignettes=TRUE) 34 | 35 | ## Load cmapplot 36 | library(cmapplot) 37 | ``` 38 | 39 | After completing these steps, your computer should be ready to use and export graphics using cmapplot. 40 | 41 | 42 | ## CMAP fonts 43 | 44 | CMAP's design standards require the usage of the Whitney typeface. Whitney is not freely available, but rather requires a license. On CMAP computers, which should already have the Whitney font family installed, cmapplot will use Whitney without any issues. If you receive a warning that Whitney is not installed when you load the package, please verify that the Whitney fonts (specifically, the Book, Medium and Semibold variants) are installed. If they are not, please submit an IT helpdesk request to get them installed. If the Whitney font family *is* already installed and you are receiving the warning message, please reach out to a member of the cmapplot development team. 45 | 46 | Non-CMAP users will have to license Whitney on their own, or else use cmapplot without. The package will default to your system's default sans-serif font, which is likely Arial. 47 | -------------------------------------------------------------------------------- /.github/workflows/check-standard.yaml: -------------------------------------------------------------------------------- 1 | # Automatically checks the package on Windows and Mac each time master branch 2 | # (or a pull request to master) gets a new commit. 3 | # Based on . 4 | # For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag. 5 | # https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions 6 | on: 7 | push: 8 | branches: 9 | - main 10 | - master 11 | pull_request: 12 | branches: 13 | - main 14 | - master 15 | 16 | name: R-CMD-check 17 | 18 | jobs: 19 | R-CMD-check: 20 | runs-on: ${{ matrix.config.os }} 21 | 22 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 23 | 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | config: 28 | - {os: windows-latest, r: 'release'} 29 | - {os: macOS-latest, r: 'release'} 30 | #- {os: ubuntu-20.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"} 31 | #- {os: ubuntu-20.04, r: 'devel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/focal/latest"} 32 | 33 | env: 34 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 35 | RSPM: ${{ matrix.config.rspm }} 36 | 37 | steps: 38 | - uses: actions/checkout@v3 39 | 40 | - uses: r-lib/actions/setup-r@v2 41 | with: 42 | r-version: ${{ matrix.config.r }} 43 | 44 | - uses: r-lib/actions/setup-pandoc@v2 45 | 46 | - name: Query dependencies 47 | run: | 48 | install.packages('remotes') 49 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 50 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 51 | shell: Rscript {0} 52 | 53 | - name: Cache R packages 54 | if: runner.os != 'Windows' 55 | uses: actions/cache@v3 56 | with: 57 | path: ${{ env.R_LIBS_USER }} 58 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 59 | restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- 60 | 61 | #- name: Install system dependencies 62 | # if: runner.os == 'Linux' 63 | # run: | 64 | # while read -r cmd 65 | # do 66 | # eval sudo $cmd 67 | # done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') 68 | 69 | - name: Install XQuartz on macOS 70 | if: runner.os == 'macOS' 71 | run: | 72 | brew install --cask xquartz 73 | 74 | - name: Install dependencies 75 | run: | 76 | remotes::install_deps(dependencies = TRUE) 77 | remotes::install_cran("rcmdcheck") 78 | shell: Rscript {0} 79 | 80 | - name: Check 81 | env: 82 | _R_CHECK_CRAN_INCOMING_REMOTE_: false 83 | run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "error", check_dir = "check") 84 | shell: Rscript {0} 85 | 86 | - name: Upload check results 87 | if: failure() 88 | uses: actions/upload-artifact@main 89 | with: 90 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results 91 | path: check 92 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Automatically rebuilds pkgdown website any time master branch is updated. 2 | # Also builds pkgdown on "gh-pages-test" on commits to pull requests. 3 | # Based on . 4 | # Conditional based on . 5 | on: 6 | push: 7 | branches: master 8 | pull_request: 9 | 10 | name: pkgdown 11 | 12 | jobs: 13 | pkgdown: 14 | runs-on: macOS-latest 15 | env: 16 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - uses: r-lib/actions/setup-r@v2 21 | 22 | - uses: r-lib/actions/setup-pandoc@v2 23 | 24 | - name: Checkout CMAP fonts repo 25 | uses: actions/checkout@v3 26 | with: 27 | repository: CMAP-REPOS/cmap-fonts 28 | token: ${{ secrets.CMAP_REPO_FULL_ACCESS }} 29 | path: cmap-fonts 30 | 31 | - name: Install CMAP fonts for R access 32 | # Inspiration: https://gist.github.com/Kevin-Lee/328e9993d6b3ad250636023fb2c7827f 33 | run: | 34 | repo_dir="$GITHUB_WORKSPACE/cmap-fonts" 35 | font_dir="$HOME/Library/Fonts" 36 | mkdir -p $font_dir 37 | find_command="find \"$repo_dir\" -name '*.[o,t]tf' -type f -print0" 38 | eval $find_command | xargs -0 -I % 39 | eval $find_command | xargs -0 -I % cp "%" "$font_dir/" 40 | find "$font_dir" -name '*.[o,t]tf' -print0 | xargs -0 -I % 41 | 42 | - name: Query dependencies 43 | run: | 44 | install.packages('remotes') 45 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 46 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 47 | shell: Rscript {0} 48 | 49 | - name: Cache R packages 50 | uses: actions/cache@v3 51 | with: 52 | path: ${{ env.R_LIBS_USER }} 53 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 54 | restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- 55 | 56 | - name: Install dependencies 57 | run: | 58 | remotes::install_deps(dependencies = TRUE) 59 | install.packages("pkgdown") 60 | shell: Rscript {0} 61 | 62 | - name: Check Whitney availability in R 63 | run: | 64 | all_fonts <- systemfonts::system_fonts() 65 | message("WHITNEY FONTS AUTOMATICALLY AVAILABLE TO SYSTEMFONTS:") 66 | message(paste(all_fonts$name[grepl("^Whitney", all_fonts$name)], collapse = "\n")) 67 | user_dir <- paste0(Sys.getenv("HOME"), "/Library/Fonts") 68 | library_fonts <- list.files(user_dir) 69 | message(paste0("WHITNEY FONTS IN ", user_dir, " (MUST BE REGISTERED):")) 70 | message(paste(library_fonts[grepl("^Whitney", library_fonts)], collapse = "\n")) 71 | shell: Rscript {0} 72 | 73 | - name: Install package 74 | run: R CMD INSTALL . 75 | 76 | - name: Deploy package to live branch 77 | if: ${{ github.ref == 'refs/heads/master' }} 78 | run: | 79 | echo "This is $GITHUB_REF. Deploying to gh-pages branch." 80 | git config --local user.email "actions@github.com" 81 | git config --local user.name "GitHub Actions" 82 | Rscript -e 'pkgdown::deploy_to_branch(clean = TRUE)' 83 | 84 | - name: Deploy package to test branch 85 | if: ${{ github.ref != 'refs/heads/master' }} 86 | run: | 87 | echo "This is $GITHUB_REF. Deploying to gh-pages-test branch." 88 | git config --local user.email "actions@github.com" 89 | git config --local user.name "GitHub Actions" 90 | Rscript -e 'pkgdown::deploy_to_branch(branch="gh-pages-test", clean = TRUE)' 91 | -------------------------------------------------------------------------------- /R/colors_highlight.R: -------------------------------------------------------------------------------- 1 | #' Highlight one or more discrete groups in a comparison graph 2 | #' 3 | #' Pick the function depending on the aesthetic of your ggplot object (fill or 4 | #' color). Specify the unique factor name(s) of the group(s) you're highlighting 5 | #' (as a case-sensitive string), and the vector it's found in. 6 | #' 7 | #' You may specify multiple groups to highlight. If you do, you may specify a 8 | #' single highlight color, or a vector of highlight colors of equal length to 9 | #' the vector of highlight values. 10 | #' 11 | #' This function does not make any modifications to the legend, so legend 12 | #' behavior is not perfect out-of-the-box. For example, if the plot aesthetic 13 | #' differentiates between five unique values, all five values will appear in the 14 | #' legend, even though four use the same color. You will likely want to hide the 15 | #' legend altogether, or manufacture a new data field that contains only the 16 | #' value(s) to highlight and some generic "Other" label. 17 | #' 18 | #' @param field character vector, the vector in which the value to highlight is 19 | #' found. Values need not be unique. Typically, pass the table column that 20 | #' defines the color/fill aesthetic as 'table$field' 21 | #' @param value character string or vector, the name of group(s) to highlight 22 | #' @param color_value Specify the highlight color(s). Default is #00b0f0 (blue) 23 | #' @param color_other Specify non-highlighted color. Default is #b0bdcf (gray) 24 | #' 25 | #' @examples 26 | #' p <- ggplot(data = dplyr::filter(transit_ridership, year=="2019"), 27 | #' mapping = aes(x = system, y = ridership, fill = system)) + 28 | #' geom_col() 29 | #' 30 | #' # one value, default colors 31 | #' p + cmap_fill_highlight(field = transit_ridership$system, 32 | #' value = "metra") 33 | #' 34 | #' # multiple values, default colors 35 | #' p + cmap_fill_highlight(field = transit_ridership$system, 36 | #' value = c("metra", "pace_ada")) 37 | #' 38 | #' # multiple values, multiple colors 39 | #' p + cmap_fill_highlight( 40 | #' field = transit_ridership$system, 41 | #' value = c("metra", "pace_ada"), 42 | #' color_value = c("red", "orange") 43 | #' ) 44 | #' 45 | #' @describeIn cmap_fill_highlight For fill aesthetic 46 | #' @export 47 | cmap_fill_highlight <- function(field, 48 | value, 49 | color_value = "#008FD5", 50 | color_other = "#b0bdcf") { 51 | 52 | palette <- make_highlight_palette(field, value, color_value, color_other) 53 | 54 | ggplot2::scale_fill_manual(values = palette) 55 | 56 | } 57 | 58 | #' @describeIn cmap_fill_highlight For color aesthetic 59 | #' @export 60 | cmap_color_highlight <- function(field, 61 | value, 62 | color_value = "#008FD5", 63 | color_other = "#b0bdcf") { 64 | 65 | palette <- make_highlight_palette(field, value, color_value, color_other) 66 | 67 | ggplot2::scale_color_manual(values = palette) 68 | 69 | } 70 | 71 | #' @describeIn cmap_fill_highlight For color aesthetic, if you're British 72 | #' @export 73 | cmap_colour_highlight <- cmap_color_highlight 74 | 75 | 76 | 77 | #' Highlight palette prep function 78 | #' 79 | #' Create named list to serve as palette input for following fill/color highlight functions 80 | #' 81 | #' @param field group vector 82 | #' @param value name of group of interest 83 | #' @param color_value hexcode of highlighted color 84 | #' @param color_other hexcode of non-highlighted color 85 | #' 86 | #' @noRd 87 | make_highlight_palette <- function(field, value, color_value, color_other) { 88 | 89 | # check that value and color_value are the same length. 90 | # if color_value has a length of 1, it will be repeated 91 | if (length(value) > 1) { 92 | if (length(color_value) == 1) { 93 | color_value <- rep.int(color_value, length(value)) 94 | } else if (length(value) != length(color_value)) { 95 | stop("Length of `value` and `color_value` must be equal.", .call = FALSE) 96 | } 97 | } 98 | 99 | # identify palette length 100 | n <- length(unique(field)) 101 | 102 | # construct initial palette 103 | palette <- rep.int(c(other = color_other), n) 104 | names(palette) <- levels(factor(field)) 105 | 106 | # replace highlight value(s) with highlight color(s) 107 | for (i in seq_along(value)) { 108 | palette[[value[i]]] <- color_value[i] 109 | } 110 | 111 | return(palette) 112 | 113 | } 114 | -------------------------------------------------------------------------------- /R/colors_continuous.R: -------------------------------------------------------------------------------- 1 | #' Visualizing CMAP color palettes 2 | #' 3 | #' @describeIn viz_palette Interpolates the range of colors a sequential or 4 | #' divergent palette offers when used on a continuous scale. 5 | #' 6 | #' @examples 7 | #' # Vizualize a sequential or divergent palette with interpolation 8 | #' viz_gradient("green_teal_blue") 9 | #' 10 | #' @export 11 | viz_gradient <- function(pal, ttl = NULL) { 12 | 13 | # if `pal` is a named sequential or divergent CMAP palette... 14 | if (fetch_pal(pal[1], c("sequential", "divergent"), "exists")) { 15 | # use the palette as the title (unless a custom title has been provided) 16 | if (is.null(ttl) | missing(ttl)){ ttl <- pal } 17 | # and extract the palette colors 18 | pal <- fetch_pal(pal) 19 | } else { 20 | # otherwise, use the object name as the title (unless a custom title has been provided) 21 | if (is.null(ttl) | missing(ttl)){ ttl <- deparse(substitute(pal)) } 22 | } 23 | 24 | pal_func <- grDevices::colorRampPalette(pal, space = "Lab") 25 | graphics::image(seq_len(300), 1, as.matrix(seq_len(300)), col = pal_func(300), 26 | main = ttl, xlab = "", ylab = "", 27 | xaxt = "n", yaxt = "n", bty = "n") 28 | } 29 | 30 | 31 | #' Continuous palette prep function 32 | #' 33 | #' @param palette A CMAP palette name 34 | #' @param reverse Logical; reverse color order? 35 | #' 36 | #' @noRd 37 | cmap_pal_continuous <- function(palette = "blues", reverse = FALSE) { 38 | pal <- fetch_pal(palette) 39 | if (reverse) { pal <- rev(pal) } 40 | return(grDevices::colorRampPalette(pal)) 41 | } 42 | 43 | 44 | #' Internal helper function to rescale. Credit for idea is due to ijlyttle: 45 | # \url{https://github.com/tidyverse/ggplot2/issues/3738#issuecomment-583750802} 46 | #' 47 | #' @noRd 48 | mid_rescaler2 <- function(mid) { 49 | function(x, to = c(0, 1), from = range(x, na.rm = TRUE)) { 50 | scales::rescale_mid(x, to, from, mid) 51 | } 52 | } 53 | 54 | 55 | #' Apply continuous CMAP palettes (gradients) to ggplot2 aesthetics 56 | #' 57 | #' Pick the function depending on the aesthetic of your ggplot object (fill or 58 | #' color). On diverging palettes, a midpoint can be manually adjusted (defaults 59 | #' to 0). See \code{\link{cmap_gradients}} for a listing of available gradients. 60 | #' 61 | #' @param palette String; Choose from 'cmap_gradients' list 62 | #' @param reverse Logical; Reverse color order? 63 | #' @param middle Numeric; Sets midpoint for diverging color palettes. Default = 64 | #' 0. 65 | #' @param ... Additional parameters passed on to the scale type 66 | #' 67 | #' @examples 68 | #' ggplot(dplyr::filter(grp_over_time, cluster=="Biopharmaceuticals"), 69 | #' aes(x = year, y = realgrp, color = realgrp)) + 70 | #' geom_line() + 71 | #' cmap_color_continuous(palette = "red_purple") 72 | #' 73 | #' @describeIn cmap_fill_continuous for fill aesthetic 74 | #' 75 | #' @export 76 | cmap_fill_continuous <- function(palette = "blues", 77 | reverse = FALSE, 78 | middle = 0, 79 | ...) { 80 | type <- fetch_pal(palette, return = "type") 81 | 82 | if (type == "divergent") { 83 | ggplot2::scale_fill_gradientn( 84 | colours = cmap_pal_continuous(palette, reverse = reverse)(256), 85 | rescaler = mid_rescaler2(middle), 86 | ... 87 | ) 88 | } else if (type == "sequential"){ 89 | ggplot2::scale_fill_gradientn( 90 | colours = cmap_pal_continuous(palette, reverse = reverse)(256), 91 | ... 92 | ) 93 | } else { 94 | NULL 95 | } 96 | } 97 | 98 | 99 | #' @describeIn cmap_fill_continuous for color aesthetic 100 | #' 101 | #' @export 102 | cmap_color_continuous <- function(palette = "blues", 103 | reverse = FALSE, 104 | middle = 0, 105 | ...) { 106 | type <- fetch_pal(palette, return = "type") 107 | 108 | if (type == "divergent") { 109 | ggplot2::scale_colour_gradientn( 110 | colours = cmap_pal_continuous(palette, reverse = reverse)(256), 111 | rescaler = mid_rescaler2(middle), 112 | ... 113 | ) 114 | } else if (type == "sequential"){ 115 | ggplot2::scale_colour_gradientn( 116 | colours = cmap_pal_continuous(palette, reverse = reverse)(256), 117 | ... 118 | ) 119 | } else { 120 | NULL 121 | } 122 | } 123 | 124 | #' @describeIn cmap_fill_continuous for color aesthetic 125 | #' @export 126 | cmap_colour_continuous <- cmap_color_continuous 127 | -------------------------------------------------------------------------------- /man/get_cmapplot_globals.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cmapplot_globals.R 3 | \name{get_cmapplot_globals} 4 | \alias{get_cmapplot_globals} 5 | \alias{cmapplot_globals} 6 | \alias{get_cmapplot_global} 7 | \alias{set_cmapplot_global} 8 | \title{The cmapplot_globals environment} 9 | \usage{ 10 | get_cmapplot_globals() 11 | 12 | get_cmapplot_global(...) 13 | 14 | set_cmapplot_global(value, ..., quietly = FALSE) 15 | } 16 | \arguments{ 17 | \item{...}{The path to the variable within \code{cmapplot_globals} to be 18 | get/set. The function willparse \code{$}, or recursive list elements can be 19 | split over multiple arguments (e.g. \code{"font$strong$family"} is 20 | equivalent to \code{"font", "strong", "family"}).} 21 | 22 | \item{value}{the value to be set} 23 | 24 | \item{quietly}{suppress confirmatory messages} 25 | } 26 | \description{ 27 | The \code{cmapplot_globals} environment contains a list of predefined 28 | variables for use by the cmapplot package and its users. It includes commonly 29 | used colors, font and font size specifications, and a list of constants which 30 | aid in drawing cmap-themed plots. It cannot be accessed directly, but the 31 | helper functions described here provide the user access if needed. 32 | } 33 | \section{Functions}{ 34 | \itemize{ 35 | \item \code{get_cmapplot_globals()}: Get the entire environment as a list. 36 | 37 | \item \code{get_cmapplot_global()}: Get a specific global value 38 | 39 | \item \code{set_cmapplot_global()}: Set a specific global value 40 | 41 | }} 42 | \section{Plot Constants}{ 43 | The primary portion of these global variables of 44 | interest to the user is \code{cmapplot_globals$consts}, a list of default 45 | constants that set certain plot aesthetics. Units of all plot constants are 46 | "bigpts": 1/72 of an inch. Most plot constants are invoked (and can be 47 | overridden) in \code{\link{finalize_plot}}: these are marked below with an 48 | \strong{F}. Some are used/can be overridden in \code{\link{theme_cmap}}: 49 | these are marked with \strong{T}. 50 | 51 | \itemize{ \item \code{lwd_strongline}: This stronger-width line is drawn 52 | vertically or horizontally with the \code{hline, vline} args of 53 | \code{theme_cmap()}. \strong{(T)} \item \code{lwd_gridline}: This 54 | thinner-width line is drawn vertically or horizontally with the 55 | \code{gridlines, axislines} args of \code{theme_cmap()}. \strong{(T)} \item 56 | \code{lwd_plotline}: The width of any lines drawn by geoms in the plot (e.g. 57 | \code{geom_line}) but not explicitly sized by the geom's aesthetic. 58 | Implemented by \code{finalize_plot} or by \code{apply_cmap_default_aes} but 59 | not overridable in either context. (Modify by setting the size explicitly in 60 | the geom, but see \code{gg_lwd_convert} first.) \item \code{lwd_topline}: 61 | The width of the line above the plot. \strong{(F)} \item 62 | \code{length_ticks}: The length of the axis ticks (if shown). \strong{(T)} 63 | \item \code{margin_topline_t}: The margin between the top edge of the image 64 | and the top line. \strong{(F)} \item \code{margin_title_t}: The margin 65 | between the top line and the title. \strong{(F)} \item 66 | \code{margin_title_b}: The margin between the title and the caption when 67 | both are drawn in the sidebar. \strong{(F)} \item \code{margin_caption_b}: 68 | The margin between the bottom of the caption and the bottom edge of the 69 | image. \strong{(F)} \item \code{margin_legend_t}: The margin between the top 70 | line and the plot box (i.e., the top of the legend). \strong{(F)} \item 71 | \code{margin_legend_i}: The margin between legends (this only applies in 72 | plots with two or more legends and does not affect legend spacing on plots 73 | with single legends that have multiple rows). \strong{(T, F)} \item 74 | \code{margin_legend_b}: The margin between the bottom of the legend and the 75 | rest of the plot. \strong{(T, F)} \item \code{margin_plot_b}: The margin 76 | between the bottom of the plot and the bottom edge of the image (or top of 77 | caption). \strong{(F)} \item \code{margin_sidebar_l}: The margin between the 78 | left edge of the image and the title and caption, when the sidebar exists. 79 | Deducted from \code{title_width}. \strong{(F)} \item \code{margin_plot_l}: 80 | The margin between the left edge of the plot and the sodebar. \strong{(F)} 81 | \item \code{margin_plot_r}: The margin between the right edge of the plot 82 | and the edge of the image. \strong{(F)} \item \code{margin_panel_r}: Padding 83 | between the plot and its right-hand drawing extent. Override this based on 84 | space needed for x axis labels. \strong{(T)} \item \code{leading_title}: 85 | Text leading for Title text. \strong{(F)} \item \code{leading_caption}: Text 86 | leading for Caption text. \strong{(F)} } 87 | } 88 | 89 | \examples{ 90 | 91 | # These are the same: 92 | get_cmapplot_global("consts$lwd_gridline") 93 | get_cmapplot_global("consts", "lwd_gridline") 94 | 95 | 96 | # Globals can be modified if needed 97 | set_cmapplot_global(5, "consts$lwd_gridline") 98 | get_cmapplot_global("consts$lwd_gridline") 99 | 100 | } 101 | -------------------------------------------------------------------------------- /R/axis_handling.R: -------------------------------------------------------------------------------- 1 | #'Axis handling helper functions 2 | #' 3 | #'`abbr_years()` is a helper functions that allows users to abbreviate year 4 | #'labels to their two-digit representation (e.g., 2008 to '08), but not 5 | #'abbreviate any specified breaks. It does so by creating a new function that 6 | #'takes the breaks supplied by \code{ggplot2} as its only argument. The 7 | #'function was modeled after the syntax and approach of the labeling functions 8 | #'in the \code{scales::label_*} family. 9 | #' 10 | #'@importFrom stringr str_length 11 | #'@importFrom lubridate year month day 12 | #'@importFrom stats na.omit 13 | #' 14 | #'@examples 15 | #' 16 | #'# basic functionality 17 | #'abbr_years()(c(2010:2020)) 18 | #'abbr_years(full_by_year = 2000)(c(1990:2010)) 19 | #' 20 | #' 21 | #' # Default implementation - this will abbreviate all labels except the first 22 | #' # for both continuous and date scales. 23 | #' 24 | #' df2 <- dplyr::mutate(transit_ridership, year2 = as.Date(lubridate::date_decimal(year))) 25 | #' df1 <- dplyr::filter(df2, year >= 2000) 26 | #' 27 | #' ggplot(df1, 28 | #' aes(x = year, y = ridership, color = system)) + 29 | #' geom_line() + 30 | #' scale_x_continuous(labels = abbr_years()) 31 | #' 32 | #' ggplot(df1, 33 | #' aes(x = year2, y = ridership, color = system)) + 34 | #' geom_line() + 35 | #' scale_x_date(labels = abbr_years(dateaxis = TRUE)) 36 | #' 37 | #' # If customizations are desired, users can use \code{full_by_pos} and/or 38 | #' # \code{full_by_year} to maintain the full version of the specified labels. 39 | #' 40 | #' ggplot(df2, 41 | #' aes(x = year2, y = ridership, color = system)) + 42 | #' geom_line() + 43 | #' scale_x_date(labels = abbr_years(full_by_year = c(2000), dateaxis = TRUE)) 44 | #' 45 | #' # You can also remove the default maintenance of the first label and only 46 | #' # specify specific years. 47 | #' ggplot(df2, 48 | #' aes(x = year, y = ridership, color = system)) + 49 | #' geom_line() + 50 | #' scale_x_continuous(labels = abbr_years(full_by_pos = NULL, 51 | #' full_by_year = c(1990,2020))) 52 | #' 53 | #' 54 | #'@param full_by_pos Vector of integers, the position of breaks that should not 55 | #' be abbreviated. This defaults to \code{c(1)}, which retains the original 56 | #' first label and abbreviates subsequent ones. If all breaks should be 57 | #' abbreviated, this can be set to NULL. 58 | #'@param full_by_year Vector of integers, the value of breaks that should not be 59 | #' abbreviated. Defaults to NULL. 60 | #'@param dateaxis Bool. \code{FALSE}, the default, directs the function to treat 61 | #' the breaks as integers. If set to \code{TRUE} the function will instead 62 | #' treat the breaks as date objects. \code{TRUE} should be used when called 63 | #' within a \code{scale_*_date} ggplot element. 64 | #' 65 | #'@export 66 | abbr_years <- function(full_by_pos = c(1), 67 | full_by_year = NULL, 68 | dateaxis = FALSE) { 69 | 70 | fxn <- function(breaks) { 71 | 72 | # If a date axis, breaks are stored by ggplot as the number of days since 73 | # the origin date of January 1, 1970. These must be converted to integer 74 | # years, but this should error if all breaks don't fall on the same calendar 75 | # day of a distinct year. 76 | if (dateaxis) { 77 | dates <- as.Date(breaks, origin = "1970-01-01") 78 | 79 | if (length(unique(month(stats::na.omit(dates)))) != 1 | 80 | length(unique(day(stats::na.omit(dates)))) != 1) { 81 | message(paste( 82 | paste("Currently, breaks are:", paste(dates[!is.na(dates)], collapse = ", ")), 83 | "This function only works if all breaks are on identical calendar days.", 84 | sep = "\n") 85 | ) 86 | stop("Breaks cannot be abbreviated.", call. = FALSE) 87 | } 88 | 89 | breaks <- lubridate::year(dates) 90 | } 91 | 92 | # Stop if the breaks are not in a four-digit format. 93 | if (!all(stringr::str_length(breaks) == 4, na.rm = TRUE)) { 94 | message(paste( 95 | paste("Currently, breaks are:", paste(breaks[!is.na(breaks)], collapse = ", ")), 96 | "Remove any breaks that contain decimals. Consider `breaks = scales::pretty_breaks()`", 97 | "If the axis is in date format, use `abbr_years(dateaxis = TRUE)`.", 98 | sep = "\n") 99 | ) 100 | stop("Breaks cannot be abbreviated.", call. = FALSE) 101 | } 102 | 103 | # Abbreviate all values 104 | abbr <- paste0("'",substr(breaks,3,4)) 105 | 106 | # If there is a leading NA, increment up positions accordingly 107 | leading_na <- which.min(is.na(breaks)) - 1 108 | if(!is.null(full_by_pos)) { 109 | full_by_pos <- full_by_pos + leading_na 110 | } 111 | 112 | # Convert specified years into positions 113 | if(!is.null(full_by_year)) { 114 | full_by_pos <- sort(unique(c(full_by_pos,match(full_by_year,breaks)))) 115 | } 116 | 117 | # Add back full years for specified positions 118 | abbr[full_by_pos] <- breaks[full_by_pos] 119 | 120 | return(abbr) 121 | } 122 | 123 | return(fxn) 124 | } 125 | -------------------------------------------------------------------------------- /R/colors_discrete.R: -------------------------------------------------------------------------------- 1 | #' Visualizing CMAP color palettes 2 | #' 3 | #' The cmapplot package contains a many color palettes extracted from the 4 | #' larger, official CMAP color palette. Helper functions allow the user to 5 | #' inspect the various palettes before applying them to plots. 6 | #' 7 | #' Palettes are stored in a tibble the \code{cmapplot_globals} environment. The 8 | #' user can access this tibble with \code{\link{get_cmapplot_global}}, but it is 9 | #' easier to access information about a single palette with \code{fetch_pal}. 10 | #' 11 | #' \code{viz_palette} and \code{viz_gradient} draw the palette to the plots 12 | #' window. These functions are modified with respect from the 13 | #' \href{https://github.com/ropenscilabs/ochRe}{ochRe package}. 14 | #' 15 | #' For more information about available cmapplot color palettes and how to apply 16 | #' them, see \code{vignette("colors")}. 17 | #' 18 | #' @describeIn viz_palette Displays the colors of any cmapplot palette 19 | #' 20 | #' @param pal character, name of a a cmapplot palette, or a vector of colors 21 | #' representing a palette 22 | #' @param ttl character, title to be displayed (the name of the palette) 23 | #' @param num numeric, the number of colors to display 24 | #' 25 | #' @examples 26 | #' # Visualize a single palette as individual colors 27 | #' viz_palette("legislation") 28 | #' 29 | #' # Print names and types of all available palettes 30 | #' as.data.frame(get_cmapplot_global("palettes")[1:2]) 31 | #' 32 | #' @aliases cmap_palettes cmap_gradients cmap_colors 33 | #' 34 | #' @export 35 | viz_palette <- function(pal, ttl = NULL, num = NULL) { 36 | 37 | # if `pal` is a named CMAP palette of any type... 38 | if (fetch_pal(pal[1], return = "exists")) { 39 | # use the palette as the title (unless a custom title has been provided) 40 | if (is.null(ttl) | missing(ttl)){ ttl <- pal } 41 | # and extract the palette colors 42 | pal <- fetch_pal(pal) 43 | } else { 44 | # otherwise, use the object name as the title (unless a custom title has been provided) 45 | if (is.null(ttl) | missing(ttl)){ ttl <- deparse(substitute(pal)) } 46 | } 47 | 48 | # use the palette's intrinsic length (unless a custom length has been provided) 49 | if (is.null(num) | missing(num)){ num <- length(pal) } 50 | 51 | pal_func <- grDevices::colorRampPalette(pal) 52 | graphics::image(seq_len(num), 1, as.matrix(seq_len(num)), col = pal_func(num), 53 | main = paste0(ttl, " (", length(pal), " colors in palette, ", 54 | num, " displayed)"), 55 | xlab = "", ylab = "", xaxt = "n", yaxt = "n", bty = "n") 56 | } 57 | 58 | #' Discrete palette prep function 59 | #' 60 | #' @param palette Choose from 'cmap_palettes' list, or use one of the gradients 61 | #' defined in the 'cmap_gradients' list (gradients will be automatically 62 | #' converted into discrete bins) 63 | #' @param reverse Logical; reverse color order?cma 64 | #' @param ... Additional parameters passed on to the scale type 65 | #' 66 | #' @noRd 67 | cmap_pal_discrete <- function(palette = "main", reverse = FALSE) { 68 | pal <- fetch_pal(palette) 69 | 70 | if(palette == "race"){ 71 | message("WARNING: The `race` palette should only be used with `cmap_fill_race()` or `cmap_color_race()`.") 72 | } 73 | 74 | if (reverse) { 75 | pal <- rev(pal) 76 | } 77 | return(scales::manual_pal(pal)) 78 | } 79 | 80 | #' Apply discrete CMAP palettes to ggplot2 aesthetics 81 | #' 82 | #' Pick the function depending on the aesthetic of your ggplot object (fill or 83 | #' color). See \code{link{cmap_palettes}} for a listing of available gradients. 84 | #' 85 | #' @param palette Choose from 'cmap_palettes' list, or use one of the gradients 86 | #' defined in the 'cmap_gradients' list (gradients will be automatically 87 | #' converted into discrete bins) 88 | #' @param reverse Logical; reverse color order? 89 | #' @param ... Additional parameters passed on to the scale type 90 | #' 91 | #' @examples 92 | #' ggplot(pop_and_laborforce_by_age, aes(x = variable, y = value, fill = age)) + 93 | #' geom_col(position = position_stack(reverse = TRUE)) + 94 | #' facet_wrap(~year) + 95 | #' cmap_fill_discrete(palette = "community") 96 | #' 97 | #' ggplot(percentile_wages, aes(x = percentile, y = wage, color = cluster)) + 98 | #' geom_line() + 99 | #' cmap_color_discrete(palette = "prosperity") 100 | #' 101 | #' @describeIn cmap_fill_discrete For fill aesthetic 102 | #' @export 103 | cmap_fill_discrete <- function(palette = "main", reverse = FALSE, ...) { 104 | ggplot2::discrete_scale( 105 | "fill", "cmap_palettes", 106 | palette = cmap_pal_discrete(palette, reverse = reverse), 107 | ... 108 | ) 109 | } 110 | 111 | #' @describeIn cmap_fill_discrete For color aesthetic 112 | #' @export 113 | cmap_color_discrete <- function(palette = "main", reverse = FALSE, ...) { 114 | ggplot2::discrete_scale( 115 | "colour", "cmap_palettes", 116 | palette = cmap_pal_discrete(palette, reverse = reverse), 117 | ... 118 | ) 119 | } 120 | 121 | #' @describeIn cmap_fill_discrete For color aesthetic 122 | #' @export 123 | cmap_colour_discrete <- cmap_color_discrete 124 | -------------------------------------------------------------------------------- /man/theme_cmap.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/theme_cmap.R 3 | \name{theme_cmap} 4 | \alias{theme_cmap} 5 | \title{Add CMAP theme to ggplot chart} 6 | \usage{ 7 | theme_cmap( 8 | xlab = NULL, 9 | ylab = NULL, 10 | hline = NULL, 11 | vline = NULL, 12 | gridlines = c("h", "v", "hv", "none"), 13 | axislines = c("none", "x", "y", "xy"), 14 | axisticks = c("none", "x", "y", "xy"), 15 | show.legend = TRUE, 16 | legend.max.columns = NULL, 17 | debug = FALSE, 18 | overrides = list(), 19 | ... 20 | ) 21 | } 22 | \arguments{ 23 | \item{xlab, ylab}{Char, the string used to label the x and y axes, 24 | respectively. If unspecified, the axis label will be left off the graph. See 25 | details for unexpected outcomes when using these arguments along with 26 | \code{coord_flip()}.} 27 | 28 | \item{hline, vline}{Numeric, the location of a strong horizontal or vertical 29 | line to be added to the plot. Use \code{hline = 0}, for example, to place a 30 | line at y = 0 to differentiate between positive and negative values. The 31 | width of this line is determined by 32 | \code{cmapplot_globals$consts$lwd_strongline}. Note that on most displays 33 | the difference between this line and gridlines is impossible to discern in 34 | R. The difference will be visible upon export.} 35 | 36 | \item{gridlines}{Char, the grid lines to be displayed on the chart. If left as 37 | default, horizontal grid lines will be displayed while vertical grid lines 38 | will be masked. Acceptable values are "h" (horizontal only), "v" (vertical 39 | only), "hv" (both horizontal and vertical), and "none" (neither).} 40 | 41 | \item{axislines}{Char, the axis lines to be displayed on the chart. Acceptable 42 | values are "x" (x axis only), "y" (y axis only), "xy" (both axes), and 43 | "none" (neither, the default).} 44 | 45 | \item{axisticks}{Char, the axis ticks to be displayed on the chart. Acceptable 46 | values are "x" (x axis only), "y" (y axis only), "xy" (both axes), and 47 | "none" (neither, the default). Because \code{ggplot2} defaults to moderately 48 | expanding the range of displayed data, this may need to be accompanied by a 49 | call to \code{expand = c(0, 0)} within an appropriate \code{scale_*_*} 50 | argument in order for ticks to appear to touch the outermost gridline(s).} 51 | 52 | \item{show.legend}{Bool, \code{TRUE} is the default. \code{FALSE} to hide the 53 | legend.} 54 | 55 | \item{legend.max.columns}{Integer, the maximum number of columns in the 56 | legend. If no value is set, the chart will rely on `ggplot`'s default and 57 | automatic column handling behavior, which should work for most cases. Manual 58 | adjustment may be required if legend entries are particularly numerous 59 | and/or lengthy. Note that `ggplot` will still auto-adjust in ways that may 60 | mean the total number of columns is less than the maximum (e.g., if there 61 | are five items in a legend with four columns as the maximum, the output will 62 | be one row of three and another row of two).} 63 | 64 | \item{debug}{Bool, Defaults to \code{FALSE}. Set to \code{TRUE} to show 65 | rectangles around all \code{geom_rect()} elements for debugging.} 66 | 67 | \item{overrides}{Named list, overrides the default drawing attributes defined 68 | in \code{cmapplot_globals$consts} which are drawn by 69 | \code{\link{theme_cmap}}. Units are in bigpts (1/72 of an inch).} 70 | 71 | \item{...}{pass additional arguments to ggplot2's \code{\link[ggplot2]{theme}} 72 | function to override any elements of the default CMAP theme.} 73 | } 74 | \description{ 75 | Return one or more ggplot objects that together construct a plot area in 76 | accordance with CMAP design standards. 77 | } 78 | \details{ 79 | Using either the \code{xlab} or \code{ylab} argument, but not both, will have 80 | undesireable outcomes in a ggplot that also invokes \code{coord_flip()}. Under 81 | the hood, \code{theme_cmap(xlab = "foo")} both sets \code{ggplot2::xlab = 82 | "foo"} and 'turns on' the ggplot theme element \code{axis.title.x}. With 83 | \code{coord_flip()}, the xlab travels with the data (becoming the ylab) but 84 | the theme modifier stays on the x axis. To solve this, rewrite your ggplot 85 | construction to avoid \code{coord_flip()} or manually turn off and on the 86 | correct elements from ggplot2's \code{\link[ggplot2]{theme}} function in the 87 | \code{...} of this function. 88 | } 89 | \examples{ 90 | 91 | \dontrun{ 92 | 93 | # The only way to place the origin line (`hline`, `vline`) behind any data geoms 94 | # is to or place `theme_cmap()` before the geoms: 95 | ggplot(grp_over_time, aes(x = year, y = realgrp, color = cluster)) + 96 | theme_cmap(hline = 0, ylab = "Percent change") + 97 | geom_line() + 98 | scale_x_continuous(breaks = scales::breaks_pretty(11)) 99 | 100 | 101 | df <- dplyr::filter(traded_emp_by_race, variable \%in\% c("SpecializedTraded", 102 | "UnspecializedTraded")) 103 | 104 | ggplot(df, aes(x = reorder(Race, -value), y = value, fill = variable)) + 105 | geom_col(position = position_stack(reverse = TRUE)) + 106 | scale_y_continuous(labels = scales::percent) + 107 | theme_cmap(hline = 0, ylab = "This is the y axis") 108 | 109 | ggplot(df, aes(y = reorder(Race, -value), x = value, fill = variable)) + 110 | geom_col(position = position_stack(reverse = TRUE)) + 111 | scale_x_continuous(labels = scales::percent) + 112 | theme_cmap(vline = 0, gridlines = "v") 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /R/RcppExports.R: -------------------------------------------------------------------------------- 1 | # Generated by using Rcpp::compileAttributes() -> do not edit by hand 2 | # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | #' Euclidean distance between two points. 5 | #' @param a A point. 6 | #' @param b A point. 7 | #' @return The distance between two points. 8 | #' @noRd 9 | NULL 10 | 11 | #' Squared Euclidean distance between two points. 12 | #' @param a A point. 13 | #' @param b A point. 14 | #' @return The distance between two points. 15 | #' @noRd 16 | NULL 17 | 18 | #' Move a box into the area specificied by x limits and y limits. 19 | #' @param b A box like \code{c(x1, y1, x2, y2)} 20 | #' @param xlim A Point with limits on the x axis like \code{c(xmin, xmax)} 21 | #' @param ylim A Point with limits on the y axis like \code{c(xmin, xmax)} 22 | #' @param force Magnitude of the force (defaults to \code{1e-6}) 23 | #' @noRd 24 | NULL 25 | 26 | #' Get the coordinates of the center of a box. 27 | #' @param b A box like \code{c(x1, y1, x2, y2)} 28 | #' @noRd 29 | NULL 30 | 31 | #' Test if a box overlaps another box. 32 | #' @param a A box like \code{c(x1, y1, x2, y2)} 33 | #' @param b A box like \code{c(x1, y1, x2, y2)} 34 | #' @noRd 35 | NULL 36 | 37 | #' Test if a box overlaps another box. 38 | #' @param a A box like \code{c(x1, y1, x2, y2)} 39 | #' @param b A box like \code{c(x1, y1, x2, y2)} 40 | #' @noRd 41 | NULL 42 | 43 | #' Compute the repulsion force upon point \code{a} from point \code{b}. 44 | #' 45 | #' The force decays with the squared distance between the points, similar 46 | #' to the force of repulsion between magnets. 47 | #' 48 | #' @param a A point like \code{c(x, y)} 49 | #' @param b A point like \code{c(x, y)} 50 | #' @param force Magnitude of the force (defaults to \code{1e-6}) 51 | #' @param direction direction in which to exert force, either "both", "x", or "y" 52 | #' @noRd 53 | NULL 54 | 55 | #' Compute the spring force upon point \code{a} from point \code{b}. 56 | #' 57 | #' The force increases with the distance between the points, similar 58 | #' to Hooke's law for springs. 59 | #' 60 | #' @param a A point like \code{c(x, y)} 61 | #' @param b A point like \code{c(x, y)} 62 | #' @param force Magnitude of the force (defaults to \code{1e-6}) 63 | #' @param direction direction in which to exert force, either "both", "x", or "y" 64 | #' @noRd 65 | NULL 66 | 67 | #' Euclidean distance between two points. 68 | #' @param a A numeric vector. 69 | #' @param b A numeric vector. 70 | #' @return The distance between two points. 71 | #' @noRd 72 | euclid <- function(a, b) { 73 | .Call('_ggrepel_euclid', PACKAGE = 'ggrepel', a, b) 74 | } 75 | 76 | #' Get the coordinates of the center of a box. 77 | #' @param b A box like \code{c(x1, y1, x2, y2)} 78 | #' @noRd 79 | centroid <- function(b, hjust, vjust) { 80 | .Call('_ggrepel_centroid', PACKAGE = 'ggrepel', b, hjust, vjust) 81 | } 82 | 83 | #' Find the intersections between a line and a rectangle. 84 | #' @param c A circle like \code{c(x, y, r)} 85 | #' @param r A rectangle like \code{c(x1, y1, x2, y2)} 86 | #' @noRd 87 | intersect_circle_rectangle <- function(c, r) { 88 | .Call('_ggrepel_intersect_circle_rectangle', PACKAGE = 'ggrepel', c, r) 89 | } 90 | 91 | #' Find the intersection between a line and a circle. 92 | #' @param p1 A point on the line like \code{c(x, y)} 93 | #' @param p2 A point at the circle's center 94 | #' @param r The circle's radius 95 | #' @noRd 96 | intersect_line_circle <- function(p1, p2, r) { 97 | .Call('_ggrepel_intersect_line_circle', PACKAGE = 'ggrepel', p1, p2, r) 98 | } 99 | 100 | #' Find the intersections between a line and a rectangle. 101 | #' @param p1 A point like \code{c(x, y)} 102 | #' @param p2 A point like \code{c(x, y)} 103 | #' @param b A rectangle like \code{c(x1, y1, x2, y2)} 104 | #' @noRd 105 | intersect_line_rectangle <- function(p1, p2, b) { 106 | .Call('_ggrepel_intersect_line_rectangle', PACKAGE = 'ggrepel', p1, p2, b) 107 | } 108 | 109 | select_line_connection <- function(p1, b) { 110 | .Call('_ggrepel_select_line_connection', PACKAGE = 'ggrepel', p1, b) 111 | } 112 | 113 | approximately_equal <- function(x1, x2) { 114 | .Call('_ggrepel_approximately_equal', PACKAGE = 'ggrepel', x1, x2) 115 | } 116 | 117 | #' Adjust the layout of a list of potentially overlapping boxes. 118 | #' @param data_points A numeric matrix with rows representing points like 119 | #' \code{rbind(c(x, y), c(x, y), ...)} 120 | #' @param point_size A numeric vector representing the sizes of data points. 121 | #' @param point_padding_x Padding around each data point on the x axis. 122 | #' @param point_padding_y Padding around each data point on the y axis. 123 | #' @param boxes A numeric matrix with rows representing boxes like 124 | #' \code{rbind(c(x1, y1, x2, y2), c(x1, y1, x2, y2), ...)} 125 | #' @param xlim A numeric vector representing the limits on the x axis like 126 | #' \code{c(xmin, xmax)} 127 | #' @param ylim A numeric vector representing the limits on the y axis like 128 | #' \code{c(ymin, ymax)} 129 | #' @param force Magnitude of the force (defaults to \code{1e-6}) 130 | #' @param max_time Maximum number of seconds to try to resolve overlaps 131 | #' (defaults to 0.1) 132 | #' @param max_iter Maximum number of iterations to try to resolve overlaps 133 | #' (defaults to 2000) 134 | #' @noRd 135 | repel_boxes2 <- function(data_points, point_size, point_padding_x, point_padding_y, boxes, xlim, ylim, hjust, vjust, force_push = 1e-7, force_pull = 1e-7, max_time = 0.1, max_overlaps = 10, max_iter = 2000L, direction = "both", verbose = 0L) { 136 | .Call('_ggrepel_repel_boxes2', PACKAGE = 'ggrepel', data_points, point_size, point_padding_x, point_padding_y, boxes, xlim, ylim, hjust, vjust, force_push, force_pull, max_time, max_overlaps, max_iter, direction, verbose) 137 | } 138 | 139 | -------------------------------------------------------------------------------- /R/cmapplot.R: -------------------------------------------------------------------------------- 1 | #' cmapplot 2 | #' 3 | #' This package contains extra palettes, themes and geoms for \pkg{ggplot2}, 4 | #' based on Chicago Metropolitan Agency for Planning (CMAP) design guidelines. 5 | #' 6 | #' Detailed documentation can be viewed at 7 | #' \url{https://cmap-repos.github.io/cmapplot}. 8 | #' 9 | #' Please report issues and suggest improvements at 10 | #' \url{https://github.com/CMAP-REPOS/cmapplot/issues}. 11 | #' 12 | #' @name cmapplot 13 | #' @docType package 14 | #' @import dplyr ggplot2 graphics grDevices grid gridtext Rcpp ragg rlang scales systemfonts 15 | #' @importFrom glue glue glue_collapse 16 | #' @keywords internal 17 | "_PACKAGE" 18 | 19 | 20 | #' Update fonts based on system -- *must* be done with .onLoad() 21 | #' 22 | #' @noRd 23 | #' @import rstudioapi 24 | .onLoad <- function(...) { 25 | 26 | family <- name <- path <- NULL 27 | 28 | # If font registry already contains Whitney core, set use_whitney == TRUE 29 | fonts_present <- systemfonts::registry_fonts() %>% 30 | dplyr::filter(family %in% cmapplot_globals$preferred_font) %>% 31 | nrow() >= 12 32 | 33 | assign("use_whitney", 34 | fonts_present, 35 | envir = cmapplot_globals) 36 | 37 | 38 | # Else, find and register necessary Whitney variants using systemfonts (or, 39 | # alternatively, find them manually in ~/Library/Fonts). Then, if font 40 | # registry contains Whitney core, set use_whitney == TRUE. 41 | if(!get("use_whitney", envir = cmapplot_globals)){ 42 | whitney_paths <- dplyr::filter(systemfonts::system_fonts(), family == "Whitney") 43 | whitney_paths <- whitney_paths[["path"]] 44 | 45 | # On some OSX systems (e.g. pkgdown GHA VM) system_fonts() cannot find fonts 46 | # installed in the user fonts directory. In any case where system_fonts() 47 | # sees no Whitney fonts, if `user_dir` exists, it too is checked for fonts. 48 | user_dir <- paste0(Sys.getenv("HOME"), "/Library/Fonts") 49 | if(length(whitney_paths) == 0 & dir.exists(user_dir)){ 50 | whitney_paths <- list.files(user_dir, full.names = TRUE) 51 | whitney_paths <- grep("Whitney-", whitney_paths, value = TRUE) 52 | } 53 | 54 | # Register preferred fonts using the paths found above. This will only be 55 | # attempted if at least 10 paths are found, as 10 distinct faces are needed 56 | # to register all possible variants of the three needed fonts. If the 57 | # correct face cannot be found, `find_path` will error and the try object 58 | # will fail before `use_whitney` is set to TRUE. 59 | if (length(whitney_paths) >= 10){ 60 | try({ 61 | 62 | # register preferred strong font (Whitney Semibold), with variants 63 | systemfonts::register_font( 64 | name = cmapplot_globals$preferred_font$strong, 65 | plain = find_path("Whitney-Semibold-Adv", whitney_paths), 66 | bold = find_path("Whitney-Black-Adv", whitney_paths), 67 | italic = find_path("Whitney-SemiboldItal-Adv", whitney_paths), 68 | bolditalic = find_path("Whitney-BlackItal-Adv", whitney_paths) 69 | ) 70 | 71 | # register preferred regular font (Whitney Medium), with variants 72 | systemfonts::register_font( 73 | name = cmapplot_globals$preferred_font$regular, 74 | plain = find_path("Whitney-Medium-Adv", whitney_paths), 75 | bold = find_path("Whitney-Bold-Adv", whitney_paths), 76 | italic = find_path("Whitney-MediumItal-Adv", whitney_paths), 77 | bolditalic = find_path("Whitney-BoldItal-Adv", whitney_paths) 78 | ) 79 | 80 | # register preferred light font (Whitney Book), with variants 81 | systemfonts::register_font( 82 | name = cmapplot_globals$preferred_font$light, 83 | plain = find_path("Whitney-Book-Adv", whitney_paths), 84 | bold = find_path("Whitney-Semibold-Adv", whitney_paths), 85 | italic = find_path("Whitney-BookItal-Adv", whitney_paths), 86 | bolditalic = find_path("Whitney-SemiboldItal-Adv", whitney_paths) 87 | ) 88 | 89 | packageStartupMessage(paste0( 90 | "cmapplot has registered the following fonts for use in this R session:\n ", 91 | paste(cmapplot_globals$preferred_font, collapse = ", ") 92 | )) 93 | 94 | assign("use_whitney", 95 | TRUE, 96 | envir = cmapplot_globals) 97 | }) 98 | } 99 | } 100 | 101 | # If Whitney is available... 102 | if(get("use_whitney", envir = cmapplot_globals)){ 103 | # ... Update font names 104 | assign("font", 105 | list(strong = list(family = cmapplot_globals$preferred_font$strong, face = "plain"), 106 | regular = list(family = cmapplot_globals$preferred_font$regular, face = "plain"), 107 | light = list(family = cmapplot_globals$preferred_font$light, face = "plain")), 108 | envir = cmapplot_globals) 109 | 110 | # ... and check on rstudio graphics 111 | if (rstudioapi::isAvailable()){ 112 | if(rstudioapi::getVersion() > "1.4"){ 113 | if(getOption("RStudioGD.backend", FALSE) != "ragg"){ 114 | options(RStudioGD.backend = "ragg") 115 | packageStartupMessage(paste( 116 | "cmapplot has set RStudio graphics to `ragg` for the current session.", 117 | "You can make this change permanent:\n ", 118 | "Tools > Global Options > General > Graphics > Graphics Device > Backend == 'AGG'." 119 | )) 120 | } 121 | } else { 122 | packageStartupMessage(paste( 123 | "cmapplot requires RStudio v1.4 or greater to use Whitney fonts", 124 | "in the R plots window.\nPlease update RStudio.")) 125 | } 126 | # If using vanilla R, encourage RStudio installation 127 | } else { 128 | packageStartupMessage(paste( 129 | "cmapplot requires RStudio to use Whitney fonts in the R plots window.\n ", 130 | "Please install RStudio. ")) 131 | } 132 | # Otherwise, notify user 133 | } else { 134 | packageStartupMessage( 135 | "cmapplot cannot locate Whitney fonts, so CMAP themes will use your default sans-serif font." 136 | ) 137 | } 138 | 139 | # Load CMAP preferred default.aes (can't be done until fonts are specified) 140 | assign("default_aes_cmap", 141 | init_cmap_default_aes(), 142 | envir = cmapplot_globals) 143 | 144 | # Cache existing default.aes 145 | assign("default_aes_cached", 146 | fetch_current_default_aes(), 147 | envir = cmapplot_globals) 148 | } 149 | -------------------------------------------------------------------------------- /man/geom_pandemics.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/geom_pandemics.R 3 | \name{geom_pandemics} 4 | \alias{geom_pandemics} 5 | \title{Add Pandemics to time series graphs} 6 | \usage{ 7 | geom_pandemics( 8 | xformat = "numeric", 9 | text = TRUE, 10 | label = " Pandemic", 11 | ymin = -Inf, 12 | ymax = Inf, 13 | fill = "#002d49", 14 | text_nudge_x = 0.2, 15 | text_nudge_y = 0, 16 | show.legend = FALSE, 17 | rect_aes = NULL, 18 | text_aes = NULL, 19 | update_Pandemics = FALSE, 20 | show_ongoing = TRUE, 21 | ... 22 | ) 23 | } 24 | \arguments{ 25 | \item{xformat}{Char, a string indicating whether the x axis of the primary 26 | data being graphed is in integer or date format. This argument will 27 | currently accept one of \code{c("numeric", "date")}.} 28 | 29 | \item{text}{Logical, whether or not to include labels that identify each box 30 | as a Pandemic.} 31 | 32 | \item{label}{Char, the text to label each Pandemic. Defaults to " Pandemic". 33 | (The space is a more consistent y axix buffer than text_nudge_y because it 34 | not relative to the scale of the y axis.)} 35 | 36 | \item{ymin, ymax}{Numeric, The height of the Pandemic rectangles. Defaults to 37 | -Inf and +Inf. Override to the top and bottom gridlines to implement ideal 38 | CMAP design standards.} 39 | 40 | \item{fill}{Char, the fill color for the Pandemic rectangles. Defaults to 41 | \code{#002d49} for compliance with CMAP design standards.} 42 | 43 | \item{text_nudge_x, text_nudge_y}{Numeric, the amount to shift the labels along 44 | each axis. Defaults to 0.2 and 0, respectively. Note that these use the x 45 | and y scales so will need to be adjusted depending on what is being graphed. 46 | `text_nudge_y` only works when `ymax` is not set to `+Inf`, which is the 47 | default. Consider setting `ymax` equal to the top of your graph or top 48 | gridline as an additional argument in `geom_pandemics()`.} 49 | 50 | \item{show.legend}{Logical, whether to render the rectangles in the legend. 51 | Defaults to \code{FALSE}.} 52 | 53 | \item{rect_aes, text_aes}{Named list, additional aesthetics to send to the 54 | rectangle and text geoms, respectively.} 55 | 56 | \item{update_Pandemics}{Logical or data frame. \code{FALSE}, the default, 57 | relies on the package's built in Pandemics table, which was last updated in 58 | March 2021 and is loaded into the \code{sysdata.R} file located in the 59 | \code{R} directory. \code{TRUE} calls the function 60 | \code{update_Pandemics}, which attempts to fetch the current Pandemics 61 | table from the NBER website. A custom data table of Pandemics can also be 62 | passed to this argument, but it must be structured identically to the 63 | five-column data table described in the the documentation file for the 64 | function \code{update_Pandemics}.} 65 | 66 | \item{show_ongoing}{Logical. \code{TRUE}, the default, will display an ongoing 67 | Pandemic that does not yet have a defined end date. If an ongoing Pandemic 68 | exists, it will be displayed as extending through the maximum extent of the 69 | graph's data (up to 2200). \code{FALSE} will remove the ongoing Pandemic 70 | from the graph.} 71 | 72 | \item{...}{additional aesthetics to send to BOTH the rectangle and text geoms.} 73 | } 74 | \description{ 75 | \code{geom_pandemics} returns one or two ggplot geoms that add rectangles 76 | representing Pandemics to a plot. It will either return only rectangles or, 77 | by default, both rectangles and text identifying each Pandemic. 78 | } 79 | \section{Important notes}{ 80 | If \code{show.legend = TRUE} you must place any 81 | categorical aesthetics (e.g. color, size) specific to the primary data in 82 | the geom(s) used to display that data. Otherwise, the legend will inherit 83 | aesthetics from geom_pandemics. 84 | 85 | It is best to place this object before your primary geom (likely 86 | \code{geom_line()}) in your code, so that ggplot draws it behind the primary 87 | data being drawn. 88 | } 89 | 90 | \section{Default color}{ 91 | The CMAP color palette gray used for Pandemics is 92 | \code{#e3e8eb}. The rectangle geom has default fill and alpha values of 93 | \code{#002d49} and \code{0.11} built into the function. These replicate the 94 | palette color at the highest possible transparency. This is done because 95 | there is no known way to place the Pandemic geom behind the graph's grid 96 | lines. The default therefore produces the approved CMAP color while altering 97 | the appearance of any overlapping grid lines as little as possible. These 98 | can be overridden, but separately. Override fill using the top-level 99 | argument, as in \code{fill = "red"}. Override alpha within rect_aes as in 100 | \code{rect_aes = list(alpha = 0.5)}. Color and alpha were calculated using 101 | the hints found here: 102 | \url{https://stackoverflow.com/questions/6672374/convert-rgb-to-rgba-over-white}. 103 | } 104 | 105 | \section{Under the hood}{ 106 | This function calls two custom geoms, constructed 107 | with ggproto. The custom GeomPandemics and GeomPandemicsText are modified 108 | versions of GeomRect and GeomText, respectively. The only variations to each 109 | occur in \code{default_aes}, \code{required_aes}, and \code{setup_data} 110 | arguments. These variations allow the the primary dataframe (specified in 111 | \code{ggplot(data = XXX)}) to filter the Pandemics displayed. 112 | } 113 | 114 | \examples{ 115 | grp_goods <- dplyr::filter(grp_over_time, category == "Goods-Producing") 116 | grp_goods <- dplyr::mutate(grp_goods, year2 = as.Date(lubridate::date_decimal(year))) 117 | 118 | # INTEGER X AXIS: 119 | ggplot(grp_over_time, aes(x = year, y = realgrp, color = cluster)) + 120 | geom_pandemics() + 121 | geom_line() + 122 | scale_x_continuous("Year") + 123 | theme_minimal() 124 | 125 | # DATE X AXIS: 126 | ggplot(data = grp_goods, 127 | mapping = aes(x = year2, y = realgrp, color = cluster)) + 128 | geom_pandemics(xformat = "date") + 129 | geom_line() + 130 | scale_x_date("Year") + 131 | theme_minimal() 132 | 133 | # MODIFIED AESTHETICS: 134 | ggplot(grp_over_time, aes(x = year, y = realgrp)) + 135 | geom_pandemics(show.legend = TRUE, fill = "blue", text = FALSE, 136 | rect_aes = list(alpha = 1, color = "red")) + 137 | geom_line(aes(color = cluster)) + 138 | scale_x_continuous("Year") + 139 | theme_minimal() 140 | 141 | } 142 | \seealso{ 143 | \itemize{ \item \url{https://ggplot2-book.org/extensions.html} \item 144 | \url{https://github.com/brodieG/ggbg/blob/development/inst/doc/extensions.html#stat-compute} 145 | \item \url{https://rpubs.com/hadley/97970} \item 146 | \url{https://ggplot2.tidyverse.org/articles/extending-ggplot2.html} } 147 | } 148 | -------------------------------------------------------------------------------- /man/geom_text_lastonly.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/geom_text_lastonly.R 3 | \name{geom_text_lastonly} 4 | \alias{geom_text_lastonly} 5 | \title{Text (Last Only)} 6 | \usage{ 7 | geom_text_lastonly( 8 | mapping = NULL, 9 | data = NULL, 10 | stat = "identity", 11 | position = NULL, 12 | parse = FALSE, 13 | nudge_x = 0.25, 14 | nudge_y = 0, 15 | check_overlap = FALSE, 16 | na.rm = FALSE, 17 | show.legend = FALSE, 18 | inherit.aes = TRUE, 19 | add_points = FALSE, 20 | text_aes = NULL, 21 | point_aes = NULL, 22 | ... 23 | ) 24 | } 25 | \arguments{ 26 | \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2:aes]{aes()}}. If specified and 27 | \code{inherit.aes = TRUE} (the default), it is combined with the default mapping 28 | at the top level of the plot. You must supply \code{mapping} if there is no plot 29 | mapping.} 30 | 31 | \item{data}{The data to be displayed in this layer. There are three 32 | options: 33 | 34 | If \code{NULL}, the default, the data is inherited from the plot 35 | data as specified in the call to \code{\link[ggplot2:ggplot]{ggplot()}}. 36 | 37 | A \code{data.frame}, or other object, will override the plot 38 | data. All objects will be fortified to produce a data frame. See 39 | \code{\link[ggplot2:fortify]{fortify()}} for which variables will be created. 40 | 41 | A \code{function} will be called with a single argument, 42 | the plot data. The return value must be a \code{data.frame}, and 43 | will be used as the layer data. A \code{function} can be created 44 | from a \code{formula} (e.g. \code{~ head(.x, 10)}).} 45 | 46 | \item{stat}{The statistical transformation to use on the data for this layer. 47 | When using a \verb{geom_*()} function to construct a layer, the \code{stat} 48 | argument can be used to override the default coupling between geoms and 49 | stats. The \code{stat} argument accepts the following: 50 | \itemize{ 51 | \item A \code{Stat} ggproto subclass, for example \code{StatCount}. 52 | \item A string naming the stat. To give the stat as a string, strip the 53 | function name of the \code{stat_} prefix. For example, to use \code{stat_count()}, 54 | give the stat as \code{"count"}. 55 | \item For more information and other ways to specify the stat, see the 56 | \link[ggplot2:layer_stats]{layer stat} documentation. 57 | }} 58 | 59 | \item{position}{Position adjustment, either as a string, or the result of a 60 | call to a position adjustment function. Cannot be jointy specified with 61 | \code{nudge_x} or \code{nudge_y}.} 62 | 63 | \item{parse}{If \code{TRUE}, the labels will be parsed into expressions and 64 | displayed as described in \code{?plotmath}.} 65 | 66 | \item{nudge_x, nudge_y}{Horizontal and vertical adjustment to nudge labels by. 67 | Useful for offsetting text from points, particularly on discrete scales. 68 | Cannot be jointy specified with \code{position}.} 69 | 70 | \item{check_overlap}{If \code{TRUE}, text that overlaps previous text in the 71 | same layer will not be plotted. \code{check_overlap} happens at draw time 72 | and in the order of the data. Therefore data should be arranged by the 73 | label column before calling \code{geom_text_lastonly()}.} 74 | 75 | \item{na.rm}{If \code{FALSE}, the default, missing values are removed with 76 | a warning. If \code{TRUE}, missing values are silently removed.} 77 | 78 | \item{show.legend}{logical. Should this layer be included in the legends? 79 | \code{NA}, the default, includes if any aesthetics are mapped. 80 | \code{FALSE} never includes, and \code{TRUE} always includes. 81 | It can also be a named logical vector to finely select the aesthetics to 82 | display. To include legend keys for all levels, even 83 | when no data exists, use \code{TRUE}. If \code{NA}, all levels are shown in legend, 84 | but unobserved levels are omitted.} 85 | 86 | \item{inherit.aes}{If \code{FALSE}, overrides the default aesthetics, 87 | rather than combining with them. This is most useful for helper functions 88 | that define both data and aesthetics and shouldn't inherit behaviour from 89 | the default plot specification, e.g. \code{\link[ggplot2:annotation_borders]{annotation_borders()}}.} 90 | 91 | \item{add_points}{If \code{TRUE}, points will be added to the plot (for the 92 | labeled data only). Default size=2, color will match line color.} 93 | 94 | \item{text_aes, point_aes}{Named list, additional aesthetics to send to the 95 | text and point geoms, respectively.} 96 | 97 | \item{...}{Additional aesthetics to send to BOTH the point and text geoms. 98 | Note that if \code{add_points = FALSE}, additional parameters can be passed 99 | to the text geom here, rather than in \code{text_aes}, without breaking.} 100 | } 101 | \description{ 102 | Label only the last point(s) on a plot. \code{geom_text_lastonly()} can be 103 | used instead of \code{ggplot2::geom_text()} when only the last point(s) 104 | should be labeled. This is accomplished by identifying the maximum value of 105 | \code{x} in \code{data} and applying a filter to omit records where \code{x} 106 | is less than the maximum. 107 | } 108 | \details{ 109 | Labels are placed by default to the right of the final point, and may be 110 | partially cut off by the plot limits. There are two known ways to address 111 | this: \enumerate{ \item Turn off panel clipping, e.g. with 112 | \code{coord_cartesian(clip = "off")}. Substitute the correct coordinate 113 | system for your plot--all have a \code{clip} argument available. Note that 114 | this will allow all geoms in the plot to draw outside the panel area, which 115 | may have unintended consequences. \item Manually expand the \code{x} scale, 116 | e.g. with \code{scale_x_continuous(expand=expand_scale(mult=0.10))} or 117 | \code{coord_cartesian(xlim = c(min, max))}. } 118 | 119 | Code was mostly copied from the source of \code{ggplot2::geom_text()} and 120 | \code{ggplot2::geom_point()}. 121 | } 122 | \examples{ 123 | df <- data.frame(year=2010:2020, value=runif(22), var=c(rep("A", 11), rep("B", 11))) 124 | 125 | # Without points, label formatting or x-axis expansion 126 | ggplot(df, aes(x=year, y=value, color=var)) + 127 | geom_line() + 128 | labs(title="Random lines") + 129 | scale_y_continuous("Percentage of absolutely nothing") + 130 | scale_x_continuous("Year") + 131 | geom_text_lastonly() 132 | 133 | # With points, label formatting and x-axis expansion 134 | ggplot(df, aes(x=year, y=value, color=var, label=sprintf("\%.1f\%\%", 100*value))) + 135 | geom_line() + 136 | labs(title="Random lines") + 137 | scale_y_continuous("Percentage of absolutely nothing", labels=scales::percent) + 138 | scale_x_continuous("Year", expand=expansion(mult=c(0.05, 0.10))) + 139 | geom_text_lastonly(add_points=TRUE, text_aes=list(fontface="bold"), point_aes=list(size=2.5)) 140 | 141 | } 142 | -------------------------------------------------------------------------------- /man/finalize_plot.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/finalize_plot.R 3 | \name{finalize_plot} 4 | \alias{finalize_plot} 5 | \title{Arrange and save CMAP ggplot chart} 6 | \usage{ 7 | finalize_plot( 8 | plot = NULL, 9 | title = "", 10 | caption = "", 11 | width = 670/72, 12 | height = 400/72, 13 | sidebar_width = NULL, 14 | caption_align = 0, 15 | mode = c("plot"), 16 | filename = NULL, 17 | overwrite = FALSE, 18 | ppi = 300, 19 | fill_bg = "white", 20 | fill_canvas = "gray90", 21 | overrides = list(), 22 | inherit = c("tc", "t", "c", "none"), 23 | legend_shift = TRUE, 24 | debug = FALSE, 25 | use_cmap_aes = TRUE, 26 | caption_valign, 27 | title_width, 28 | ... 29 | ) 30 | } 31 | \arguments{ 32 | \item{plot}{ggplot object, the variable name of the plot you have created that 33 | you want to finalize. If null (the default), the most recent plot will be 34 | retrieved via \code{ggplot2::last_plot()}.} 35 | 36 | \item{title, caption}{Char, the text you want to appear in the title and 37 | caption blocks. If empty, any non-Null values from \code{plot} will be 38 | retrieved. These blocks take html formatting, so manual text breaks can be 39 | created with \code{
} and formatting can be changed with \code{}.} 40 | 41 | \item{width, height}{Numeric, the dimensions for the output image, including 42 | the title. Units in inches, which interacts with \code{ppi} to define the 43 | pixel dimensions of raster outputs. Default is 9.31 inches wide (670/72) and 44 | 5.56 inches tall (400/72), to match Comms specification for web graphics.} 45 | 46 | \item{sidebar_width}{Numeric, the width in inches for the sidebar. If 47 | unspecified, use 25 percent of the total output width (per Comms guidance). 48 | If set to 0, the title, if present, is moved above the topline and the 49 | caption, if present, is moved to below the plot.} 50 | 51 | \item{caption_align}{Numeric, alignment of the caption text. When the caption 52 | is in the title column (when \code{sidebar_width > 0}), 0 (the default) 53 | aligns text to bottom; 1 aligns top. When the caption is located below the 54 | plot, 0 aligns left and 1 aligns right. 0.5 aligns center.} 55 | 56 | \item{mode}{Vector, the action(s) to be taken with the plot. View in R with 57 | \code{plot}, the default. Save using any of the following: \code{png}, 58 | \code{tiff}, \code{jpeg}, \code{svg}, \code{pdf}, \code{ps}. Run 59 | multiple simultaneous outputs with a vector, e.g. \code{c("plot", "png", 60 | "pdf")}.} 61 | 62 | \item{filename}{Char, the file path and name you want the plot to be saved to. 63 | You may specify an extension to use. If you don't, the correct extension 64 | will be added for you.} 65 | 66 | \item{overwrite}{Bool, set to \code{TRUE} if you would like the function to 67 | overwrite existing files by the same name. The default is \code{FALSE}.} 68 | 69 | \item{ppi}{Numeric, the resolution of exported images (pixels per inch). 70 | Default = 300.} 71 | 72 | \item{fill_bg, fill_canvas}{Char, strings that represent colors R can 73 | interpret. They are used to fill behind and around the finished plot, 74 | respectively.} 75 | 76 | \item{overrides}{Named list, overrides the default drawing attributes defined 77 | in \code{cmapplot_globals$consts} which are drawn by 78 | \code{\link{finalize_plot}}. Units are in bigpts (1/72 of an inch).} 79 | 80 | \item{inherit}{Char, a string of characters that represent which elements of 81 | the underlying ggplot object the function should attempt to inherit if not 82 | specified in this function. If left as default, the function will attempt to 83 | replace blank titles and captions with those from the underlying plot 84 | object. Acceptable values are "t" (inherit title only), "c" (inherit caption 85 | only), "tc" (the default, inherit both title and caption), and "none" 86 | (inherit nothing).} 87 | 88 | \item{legend_shift}{Bool, \code{TRUE}, the default, attempts to align the 89 | legend all the way left (on top of the y axis labels) per CMAP design 90 | standards. \code{FALSE} maintains the alignment used in the original plot.} 91 | 92 | \item{debug}{Bool, \code{TRUE} enables outlines around components of finalized 93 | plot. Defaults to \code{FALSE}.} 94 | 95 | \item{use_cmap_aes}{Bool, \code{TRUE}, the default, temporarily implements 96 | CMAP default aesthetic settings for geoms (see 97 | \code{\link{apply_cmap_default_aes}}) for the present plot.} 98 | 99 | \item{caption_valign}{This is deprecated as of cmapplot 1.1.0 and will be 100 | removed in future releases. Replace with \code{caption_align} argument.} 101 | 102 | \item{title_width}{This is deprecated as of cmapplot 1.1.1 and will be removed 103 | in future releases. Replace with \code{sidebar_width} argument.} 104 | 105 | \item{...}{Pass additional arguments to ggplot2's \code{\link[ggplot2]{theme}} 106 | function to override any elements of the plot's theme when drawing.} 107 | } 108 | \value{ 109 | This function invisibly returns the finished graphic as a gTree 110 | object. If stored (e.g. \code{g <- finalize_plot(...)}), the gTree can be 111 | drawn later with \code{grid} (e.g. \code{grid::grid.draw(g)}). 112 | } 113 | \description{ 114 | Place a ggplot into a frame defined by CMAP design standards. It will align 115 | your title and caption to the left, add a horizontal line on top, and make 116 | other adjustments. It can show you the final plot and/or export it as a raster 117 | or vector file. This function will not apply CMAP design standards to the plot 118 | itself: use with \code{theme_cmap()} for that. This function uses ragg drivers 119 | in R and for raster exports, svg for svg, and cairo_pdf for PDFs. 120 | } 121 | \examples{ 122 | \dontrun{ 123 | econ_plot <- ggplot(data = cluster_jobchange, 124 | mapping = aes( 125 | y = reorder(name, jobchange), 126 | x = jobchange, 127 | fill = category)) + 128 | geom_col() + 129 | theme_cmap(gridlines = "v", vline = 0) + 130 | scale_x_continuous(labels = scales::comma) 131 | 132 | finalize_plot(econ_plot, 133 | "Cluster-level employment changes in the Chicago MSA, 2001-17", 134 | "Source: Chicago Metropolitan Agency for Planning analysis", 135 | mode = "plot", 136 | height = 6, 137 | width = 8, 138 | sidebar_width = 2.5, 139 | overrides = list(margin_plot_r = 30)) 140 | 141 | transit_plot <- transit_ridership \%>\% 142 | mutate(system = case_when( 143 | system == "cta_bus" ~ "CTA (Bus)", 144 | system == "cta_rail" ~ "CTA (Rail)", 145 | system == "metra" ~ "Metra", 146 | system == "pace" ~ "Pace", 147 | system == "pace_ada" ~ "Paratransit" 148 | )) \%>\% 149 | ggplot(aes(x = year, y = ridership, color = system)) + 150 | geom_line() + 151 | theme_cmap(legend.max.columns = 3) 152 | 153 | finalize_plot(transit_plot, 154 | "Transit ridership in the RTA region over time, 1980-2019 155 | (in millions)", 156 | "Source: Chicago Metropolitan Agency for Planning 157 | analysis of data from the Regional Transportation Authority", 158 | mode=c("plot", "pdf"), 159 | filename = "foo") 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /man/geom_recessions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/geom_recessions.R 3 | \name{geom_recessions} 4 | \alias{geom_recessions} 5 | \title{Add recessions to time series graphs} 6 | \usage{ 7 | geom_recessions( 8 | xformat = "numeric", 9 | text = TRUE, 10 | label = " Recession", 11 | ymin = -Inf, 12 | ymax = Inf, 13 | fill = "#002d49", 14 | text_nudge_x = 0.2, 15 | text_nudge_y = 0, 16 | show.legend = FALSE, 17 | rect_aes = NULL, 18 | text_aes = NULL, 19 | update_recessions = FALSE, 20 | show_ongoing = TRUE, 21 | ... 22 | ) 23 | } 24 | \arguments{ 25 | \item{xformat}{Char, a string indicating whether the x axis of the primary 26 | data being graphed is in integer or date format. This argument will 27 | currently accept one of \code{c("numeric", "date")}.} 28 | 29 | \item{text}{Logical, whether or not to include labels that identify each box 30 | as a recession.} 31 | 32 | \item{label}{Char, the text to label each recession. Defaults to " Recession". 33 | (The space is a more consistent y axix buffer than text_nudge_y because it 34 | not relative to the scale of the y axis.)} 35 | 36 | \item{ymin, ymax}{Numeric, The height of the recession rectangles. Defaults to 37 | -Inf and +Inf. Override to the top and bottom gridlines to implement ideal 38 | CMAP design standards.} 39 | 40 | \item{fill}{Char, the fill color for the recession rectangles. Defaults to 41 | \code{#002d49} for compliance with CMAP design standards.} 42 | 43 | \item{text_nudge_x, text_nudge_y}{Numeric, the amount to shift the labels along 44 | each axis. Defaults to 0.2 and 0, respectively. Note that these use the x 45 | and y scales so will need to be adjusted depending on what is being graphed. 46 | `text_nudge_y` only works when `ymax` is not set to `+Inf`, which is the 47 | default. Consider setting `ymax` equal to the top of your graph or top 48 | gridline as an additional argument in `geom_recessions()`.} 49 | 50 | \item{show.legend}{Logical, whether to render the rectangles in the legend. 51 | Defaults to \code{FALSE}.} 52 | 53 | \item{rect_aes, text_aes}{Named list, additional aesthetics to send to the 54 | rectangle and text geoms, respectively.} 55 | 56 | \item{update_recessions}{Logical or data frame. \code{FALSE}, the default, 57 | relies on the package's built in recessions table, which was last updated in 58 | March 2021 and is loaded into the \code{sysdata.R} file located in the 59 | \code{R} directory. \code{TRUE} calls the function 60 | \code{update_recessions}, which attempts to fetch the current recessions 61 | table from the NBER website. A custom data table of recessions can also be 62 | passed to this argument, but it must be structured identically to the 63 | five-column data table described in the the documentation file for the 64 | function \code{update_recessions}.} 65 | 66 | \item{show_ongoing}{Logical. \code{TRUE}, the default, will display an ongoing 67 | recession that does not yet have a defined end date. If an ongoing recession 68 | exists, it will be displayed as extending through the maximum extent of the 69 | graph's data (up to 2200). \code{FALSE} will remove the ongoing recession 70 | from the graph.} 71 | 72 | \item{...}{additional aesthetics to send to BOTH the rectangle and text geoms.} 73 | } 74 | \description{ 75 | \code{geom_recessions} returns one or two ggplot geoms that add rectangles 76 | representing recessions to a plot. It will either return only rectangles or, 77 | by default, both rectangles and text identifying each recession. 78 | } 79 | \section{Important notes}{ 80 | If \code{show.legend = TRUE} you must place any 81 | categorical aesthetics (e.g. color, size) specific to the primary data in 82 | the geom(s) used to display that data. Otherwise, the legend will inherit 83 | aesthetics from geom_recessions. 84 | 85 | It is best to place this object before your primary geom (likely 86 | \code{geom_line()}) in your code, so that ggplot draws it behind the primary 87 | data being drawn. 88 | } 89 | 90 | \section{Default color}{ 91 | The CMAP color palette gray used for recessions is 92 | \code{#e3e8eb}. The rectangle geom has default fill and alpha values of 93 | \code{#002d49} and \code{0.11} built into the function. These replicate the 94 | palette color at the highest possible transparency. This is done because 95 | there is no known way to place the recession geom behind the graph's grid 96 | lines. The default therefore produces the approved CMAP color while altering 97 | the appearance of any overlapping grid lines as little as possible. These 98 | can be overridden, but separately. Override fill using the top-level 99 | argument, as in \code{fill = "red"}. Override alpha within rect_aes as in 100 | \code{rect_aes = list(alpha = 0.5)}. Color and alpha were calculated using 101 | the hints found here: 102 | \url{https://stackoverflow.com/questions/6672374/convert-rgb-to-rgba-over-white}. 103 | } 104 | 105 | \section{Under the hood}{ 106 | This function calls two custom geoms, constructed 107 | with ggproto. The custom GeomRecessions and GeomRecessionsText are modified 108 | versions of GeomRect and GeomText, respectively. The only variations to each 109 | occur in \code{default_aes}, \code{required_aes}, and \code{setup_data} 110 | arguments. These variations allow the the primary dataframe (specified in 111 | \code{ggplot(data = XXX)}) to filter the recessions displayed. 112 | } 113 | 114 | \examples{ 115 | grp_goods <- dplyr::filter(grp_over_time, category == "Goods-Producing") 116 | grp_goods <- dplyr::mutate(grp_goods, year2 = as.Date(lubridate::date_decimal(year))) 117 | 118 | # INTEGER X AXIS: 119 | ggplot(grp_over_time, aes(x = year, y = realgrp, color = cluster)) + 120 | geom_recessions() + 121 | geom_line() + 122 | scale_x_continuous("Year") + 123 | theme_minimal() 124 | 125 | # DATE X AXIS: 126 | ggplot(data = grp_goods, 127 | mapping = aes(x = year2, y = realgrp, color = cluster)) + 128 | geom_recessions(xformat = "date") + 129 | geom_line() + 130 | scale_x_date("Year") + 131 | theme_minimal() 132 | 133 | # MODIFIED AESTHETICS: 134 | ggplot(grp_over_time, aes(x = year, y = realgrp)) + 135 | geom_recessions(show.legend = TRUE, fill = "blue", text = FALSE, 136 | rect_aes = list(alpha = 1, color = "red")) + 137 | geom_line(aes(color = cluster)) + 138 | scale_x_continuous("Year") + 139 | theme_minimal() 140 | 141 | 142 | # BELOW EXAMPLES SHOW MORE THAN 1 RECESSION 143 | df <- data.frame(year_dec=1950:1999, value=rnorm(100), var=c(rep("A", 50), rep("B", 50))) 144 | df$year_date <- as.Date(lubridate::date_decimal(df$year_dec)) 145 | 146 | # A plot with an integer-based x axis 147 | ggplot(df, mapping = aes(x = year_dec, y = value)) + 148 | geom_recessions() + 149 | geom_line(aes(color = var)) + 150 | scale_x_continuous("Year") + 151 | theme_minimal() 152 | 153 | # A plot with a date-based x axis 154 | ggplot(df, mapping = aes(x = year_date, y = value)) + 155 | geom_recessions(xformat = "date", show.legend = TRUE) + 156 | geom_line(aes(color = var)) + 157 | scale_x_date() + 158 | theme_minimal() 159 | 160 | } 161 | \seealso{ 162 | \itemize{ \item \url{https://ggplot2-book.org/extensions.html} \item 163 | \url{https://github.com/brodieG/ggbg/blob/development/inst/doc/extensions.html#stat-compute} 164 | \item \url{https://rpubs.com/hadley/97970} \item 165 | \url{https://ggplot2.tidyverse.org/articles/extending-ggplot2.html} } 166 | } 167 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 32 | 33 | ```{r setup, include = FALSE} 34 | knitr::opts_chunk$set( 35 | collapse = TRUE, 36 | comment = "#>", 37 | fig.path = "man/figures/README-", 38 | fig.width = 7, 39 | fig.asp = 400/670, 40 | fig.retina = 4, 41 | fig.align = "center", 42 | dev = "ragg_png" 43 | ) 44 | 45 | devtools::load_all() 46 | library(tidyverse) 47 | ``` 48 | 49 | # cmapplot cmapplot logo 50 | 51 | 52 | [![R build status](https://github.com/CMAP-REPOS/cmapplot/workflows/R-CMD-check/badge.svg)](https://github.com/CMAP-REPOS/cmapplot/actions?query=workflow%3AR-CMD-check) 53 | [![pkgdown build status](https://github.com/CMAP-REPOS/cmapplot/workflows/pkgdown/badge.svg)](https://github.com/CMAP-REPOS/cmapplot/actions?query=workflow%3Apkgdown) 54 | 55 | 56 | This R package provides themes, color scales, and other custom functions for [ggplot2](https://github.com/tidyverse/ggplot2), based on Chicago Metropolitan Agency for Planning (CMAP) design guidelines. 57 | 58 | CMAP staff who are interested in using this package, or merely staying in the loop, are encouraged to join the [R team](https://teams.microsoft.com/l/team/19%3ad705bfd7596a4518b588ad529d2367c8%40thread.skype/conversations?groupId=d7bab529-9c30-441e-9db6-4edfdca8202c&tenantId=43b185b9-e6d9-45a5-8e36-4c08dc0ab1a2) in Microsoft Teams and follow the "cmapplot" channel. 59 | 60 | 61 | ## The basics 62 | The cmapplot package contains a few key components: 63 | 64 | 1. Apply a CMAP theme to ggplots with `theme_cmap()` 65 | 2. Easily provide common CMAP plot customizations, such as with custom geoms `geom_recessions()` and `geom_text_lastonly()` 66 | 3. Apply CMAP colors using a variety of custom functions (e.g. `cmap_fill_discrete()`) 67 | 4. Place the themed plot within a CMAP layout, and export the plot from R if desired with `finalize_plot()` 68 | 69 | 70 | ## Installation 71 | 72 | Run the following to install or update cmapplot: 73 | 74 | ```{r install, eval=FALSE, message=FALSE, warning=FALSE} 75 | ## Install current version from GitHub 76 | devtools::install_github("CMAP-REPOS/cmapplot", build_vignettes=TRUE) 77 | 78 | ## Then load the package as you would any other 79 | library(cmapplot) 80 | ``` 81 | 82 | For more detailed information about installing the package, particularly on a CMAP-issued computer, see [this article](https://cmap-repos.github.io/cmapplot/articles/installation.html). 83 | 84 | To install on macOS, users must install [XQuartz](https://www.xquartz.org) before cmapplot can be loaded. (This can be easily accomplished via the [Homebrew](https://brew.sh) package manager with the command `brew install --cask xquartz`.) 85 | 86 | **A note about fonts**: The cmapplot package works best when installed on a computer with the Whitney family of fonts installed (specifically the Book, Medium, and Semibold variants). If installed on a computer *without* Whitney, the package will still work, but the fonts will default to your computer's default sans-serif font (probably Arial). 87 | 88 | 89 | ## CMAP theme and colors 90 | 91 | The function `theme_cmap()` returns a complete ggplot2 theme that can be added to a ggplot code block (similar to `ggplot2::theme_minimal()` or `ggplot2::theme_bw()`). Additionally, `theme_cmap()` accepts a variety of arguments to additionally customize the theme output. CMAP color functions apply colors from the CMAP color palette to the plot. 92 | 93 | ```{r theme, message=FALSE} 94 | ggplot(data = pop_and_laborforce_by_age, 95 | aes(x = value, 96 | y = interaction(year, variable, sep = " "), 97 | fill = age)) + 98 | geom_col(position = position_stack(reverse = TRUE)) + 99 | scale_x_continuous(labels = scales::percent) + 100 | theme_cmap(xlab = "Percent", 101 | gridlines = "v", 102 | vline = 0) + 103 | cmap_fill_discrete(palette = "environment") 104 | ``` 105 | 106 | 107 | ## Finalizing the plot 108 | 109 | The function `finalize_plot()` places a ggplot within a frame defined by CMAP design standards. It provides a variety of customization options via arguments, and allows for in-R viewing and/or exporting in various formats. 110 | 111 | ```{r finalize, message=FALSE} 112 | finalize_plot(title = "Regional population and labor force participation", 113 | caption = "Data from the American Community Survey", 114 | width = 7, height = 4.25) 115 | ``` 116 | 117 | 118 | ## Additional reading 119 | 120 | While this package is designed to make the application of CMAP design standards to plots relatively easy, developing a professional, finished plot in R will require a decent familiarity with the grammar of [ggplot2](ggplot2.tidyverse.org/). Excellent resources in this category already exist: 121 | 122 | - The [R graphics cookbook](https://r-graphics.org/) provides accessible examples of how to make almost [any type](https://r-graphics.org/recipe-miscgraph-vectorfield) of plot, as well as how to modify things like [limits, scales](https://r-graphics.org/recipe-axes-range), [coordinate systems](https://r-graphics.org/recipe-axes-polar), and [facets](https://r-graphics.org/recipe-facet-basic). 123 | - The [ggplot2 book](https://ggplot2-book.org/) delves deeper into why and how ggplot2 works the way it does, also with distinct chapters on topics like [scales](https://ggplot2-book.org/scales-guides.html), [coordinate systems](https://ggplot2-book.org/coord.html), [facets](https://ggplot2-book.org/facet.html), etc. 124 | - The [ggplot2](ggplot2.tidyverse.org/) website. 125 | - The [R for Data Science (R4DS)](https://r4ds.had.co.nz/) book, especially the [Graphics for Communication](https://r4ds.had.co.nz/graphics-for-communication.html) chapter. 126 | -------------------------------------------------------------------------------- /man/geom_text_lastonly_repel.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/geom_text_lastonly_repel.R 3 | \name{geom_text_lastonly_repel} 4 | \alias{geom_text_lastonly_repel} 5 | \title{Text (Last Only) Repel} 6 | \usage{ 7 | geom_text_lastonly_repel( 8 | mapping = NULL, 9 | data = NULL, 10 | stat = "identity", 11 | position = NULL, 12 | parse = FALSE, 13 | box.padding = 0.25, 14 | point.padding = 1e-06, 15 | min.segment.length = 0.5, 16 | arrow = NULL, 17 | force = 1, 18 | force_pull = 1, 19 | max.time = 0.5, 20 | max.iter = 10000, 21 | max.overlaps = getOption("ggrepel.max.overlaps", default = 10), 22 | nudge_x = 0.4, 23 | nudge_y = 0, 24 | xlim = c(NA, NA), 25 | ylim = c(NA, NA), 26 | na.rm = FALSE, 27 | direction = c("y", "x", "both"), 28 | seed = NA, 29 | verbose = FALSE, 30 | show.legend = FALSE, 31 | inherit.aes = TRUE, 32 | add_points = FALSE, 33 | text_aes = NULL, 34 | point_aes = NULL, 35 | ... 36 | ) 37 | } 38 | \arguments{ 39 | \item{mapping}{Set of aesthetic mappings created by \code{\link[ggplot2]{aes}} or 40 | \code{\link[ggplot2]{aes_}}. If specified and \code{inherit.aes = TRUE} (the 41 | default), is combined with the default mapping at the top level of the 42 | plot. You only need to supply \code{mapping} if there isn't a mapping 43 | defined for the plot.} 44 | 45 | \item{data}{A data frame. If specified, overrides the default data frame 46 | defined at the top level of the plot.} 47 | 48 | \item{stat}{The statistical transformation to use on the data for this 49 | layer, as a string.} 50 | 51 | \item{position}{Position adjustment, either as a string, or the result of 52 | a call to a position adjustment function.} 53 | 54 | \item{parse}{If TRUE, the labels will be parsed into expressions and 55 | displayed as described in ?plotmath} 56 | 57 | \item{box.padding}{Amount of padding around bounding box, as unit or number. 58 | Defaults to 0.25. (Default unit is lines, but other units can be specified 59 | by passing \code{unit(x, "units")}).} 60 | 61 | \item{point.padding}{Amount of padding around labeled point, as unit or 62 | number. Defaults to 0. (Default unit is lines, but other units can be 63 | specified by passing \code{unit(x, "units")}).} 64 | 65 | \item{min.segment.length}{Skip drawing segments shorter than this, as unit or 66 | number. Defaults to 0.5. (Default unit is lines, but other units can be 67 | specified by passing \code{unit(x, "units")}).} 68 | 69 | \item{arrow}{specification for arrow heads, as created by \code{\link[grid]{arrow}}} 70 | 71 | \item{force}{Force of repulsion between overlapping text labels. Defaults 72 | to 1.} 73 | 74 | \item{force_pull}{Force of attraction between a text label and its 75 | corresponding data point. Defaults to 1.} 76 | 77 | \item{max.time}{Maximum number of seconds to try to resolve overlaps. 78 | Defaults to 0.5.} 79 | 80 | \item{max.iter}{Maximum number of iterations to try to resolve overlaps. 81 | Defaults to 10000.} 82 | 83 | \item{max.overlaps}{Exclude text labels when they overlap too many other 84 | things. For each text label, we count how many other text labels or other 85 | data points it overlaps, and exclude the text label if it has too many overlaps. 86 | Defaults to 10.} 87 | 88 | \item{nudge_x, nudge_y}{Horizontal and vertical adjustments to nudge the 89 | starting position of each text label. The units for \code{nudge_x} and 90 | \code{nudge_y} are the same as for the data units on the x-axis and y-axis.} 91 | 92 | \item{xlim, ylim}{Limits for the x and y axes. Text labels will be constrained 93 | to these limits. By default, text labels are constrained to the entire plot 94 | area.} 95 | 96 | \item{na.rm}{If \code{FALSE} (the default), removes missing values with 97 | a warning. If \code{TRUE} silently removes missing values.} 98 | 99 | \item{direction}{"both", "x", or "y" -- direction in which to adjust position of labels} 100 | 101 | \item{seed}{Random seed passed to \code{\link[base]{set.seed}}. Defaults to 102 | \code{NA}, which means that \code{set.seed} will not be called.} 103 | 104 | \item{verbose}{If \code{TRUE}, some diagnostics of the repel algorithm are printed} 105 | 106 | \item{show.legend}{logical. Should this layer be included in the legends? 107 | \code{NA}, the default, includes if any aesthetics are mapped. 108 | \code{FALSE} never includes, and \code{TRUE} always includes.} 109 | 110 | \item{inherit.aes}{If \code{FALSE}, overrides the default aesthetics, 111 | rather than combining with them. This is most useful for helper functions 112 | that define both data and aesthetics and shouldn't inherit behaviour from 113 | the default plot specification, e.g. \code{\link[ggplot2]{borders}}.} 114 | 115 | \item{add_points}{If \code{TRUE}, points will be added to the plot (for the 116 | labeled data only). Default size=2, color will match line color.} 117 | 118 | \item{text_aes, point_aes}{Named list, additional aesthetics to send to the 119 | text and point geoms, respectively.} 120 | 121 | \item{...}{Additional aesthetics to send to BOTH the point and text geoms. 122 | Note that if \code{add_points = FALSE}, additional parameters can be passed 123 | to the text geom here, rather than in \code{text_aes}, without breaking.} 124 | } 125 | \description{ 126 | Label only the last point(s) on a plot. \code{geom_text_lastonly_repel()} can be 127 | used instead of \code{ggplot2::geom_text()} when only the last point(s) 128 | should be labeled. This is accomplished by identifying the maximum value of 129 | \code{x} in \code{data} and applying a filter to omit records where \code{x} 130 | is less than the maximum. 131 | } 132 | \details{ 133 | Labels are placed by default to the right of the final point, and may be 134 | partially cut off by the plot limits. There are two known ways to address 135 | this: \enumerate{ \item Turn off panel clipping, e.g. with 136 | \code{coord_cartesian(clip = "off")}. Substitute the correct coordinate 137 | system for your plot--all have a \code{clip} argument available. Note that 138 | this will allow all geoms in the plot to draw outside the panel area, which 139 | may have unintended consequences. \item Manually expand the \code{x} scale, 140 | e.g. with \code{scale_x_continuous(expand=expand_scale(mult=0.10))} or 141 | \code{coord_cartesian(xlim = c(min, max))}. } 142 | 143 | Code was mostly copied from the source of \code{ggrepel::geom_text_repel()} and 144 | \code{ggplot2::geom_point()}. 145 | } 146 | \section{Alignment with \code{hjust} or \code{vjust}}{ 147 | 148 | The arguments \code{hjust} and \code{vjust} are supported, but they only 149 | control the initial positioning, so repulsive forces may disrupt alignment. 150 | Alignment with \code{hjust} will be preserved if labels only move up and down 151 | by using \code{direction="y"}. For \code{vjust}, use \code{direction="x"}. 152 | } 153 | 154 | \examples{ 155 | 156 | library(tidyverse) 157 | 158 | df <- transit_ridership \%>\% 159 | filter(system != "pace_ada") \%>\% 160 | mutate(system = recode_factor(system, 161 | cta_bus = "CTA bus", 162 | cta_rail = "CTA rail", 163 | metra = "Metra", 164 | pace = "Pace")) 165 | 166 | # Without points, label formatting or x-axis expansion 167 | ggplot(df, aes(x = year, y = ridership, color = system)) + 168 | geom_line() + 169 | labs(title = "Annual Transit Ridership") + 170 | scale_y_continuous("Ridership (Millions)") + 171 | scale_x_continuous("Year") + 172 | geom_text_lastonly() 173 | 174 | 175 | } 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 29 | 30 | # cmapplot cmapplot logo 31 | 32 | 33 | 34 | [![R build 35 | status](https://github.com/CMAP-REPOS/cmapplot/workflows/R-CMD-check/badge.svg)](https://github.com/CMAP-REPOS/cmapplot/actions?query=workflow%3AR-CMD-check) 36 | [![pkgdown build 37 | status](https://github.com/CMAP-REPOS/cmapplot/workflows/pkgdown/badge.svg)](https://github.com/CMAP-REPOS/cmapplot/actions?query=workflow%3Apkgdown) 38 | 39 | 40 | This R package provides themes, color scales, and other custom functions 41 | for [ggplot2](https://github.com/tidyverse/ggplot2), based on Chicago 42 | Metropolitan Agency for Planning (CMAP) design guidelines. 43 | 44 | CMAP staff who are interested in using this package, or merely staying 45 | in the loop, are encouraged to join the [R 46 | team](https://teams.microsoft.com/l/team/19%3ad705bfd7596a4518b588ad529d2367c8%40thread.skype/conversations?groupId=d7bab529-9c30-441e-9db6-4edfdca8202c&tenantId=43b185b9-e6d9-45a5-8e36-4c08dc0ab1a2) 47 | in Microsoft Teams and follow the “cmapplot” channel. 48 | 49 | ## The basics 50 | 51 | The cmapplot package contains a few key components: 52 | 53 | 1. Apply a CMAP theme to ggplots with `theme_cmap()` 54 | 2. Easily provide common CMAP plot customizations, such as with custom 55 | geoms `geom_recessions()` and `geom_text_lastonly()` 56 | 3. Apply CMAP colors using a variety of custom functions 57 | (e.g. `cmap_fill_discrete()`) 58 | 4. Place the themed plot within a CMAP layout, and export the plot from 59 | R if desired with `finalize_plot()` 60 | 61 | ## Installation 62 | 63 | Run the following to install or update cmapplot: 64 | 65 | ``` r 66 | ## Install current version from GitHub 67 | devtools::install_github("CMAP-REPOS/cmapplot", build_vignettes=TRUE) 68 | 69 | ## Then load the package as you would any other 70 | library(cmapplot) 71 | ``` 72 | 73 | For more detailed information about installing the package, particularly 74 | on a CMAP-issued computer, see [this 75 | article](https://cmap-repos.github.io/cmapplot/articles/installation.html). 76 | 77 | To install on macOS, users must install 78 | [XQuartz](https://www.xquartz.org) before cmapplot can be loaded. (This 79 | can be easily accomplished via the [Homebrew](https://brew.sh) package 80 | manager with the command `brew install --cask xquartz`.) 81 | 82 | **A note about fonts**: The cmapplot package works best when installed 83 | on a computer with the Whitney family of fonts installed (specifically 84 | the Book, Medium, and Semibold variants). If installed on a computer 85 | *without* Whitney, the package will still work, but the fonts will 86 | default to your computer’s default sans-serif font (probably Arial). 87 | 88 | ## CMAP theme and colors 89 | 90 | The function `theme_cmap()` returns a complete ggplot2 theme that can be 91 | added to a ggplot code block (similar to `ggplot2::theme_minimal()` or 92 | `ggplot2::theme_bw()`). Additionally, `theme_cmap()` accepts a variety 93 | of arguments to additionally customize the theme output. CMAP color 94 | functions apply colors from the CMAP color palette to the plot. 95 | 96 | ``` r 97 | ggplot(data = pop_and_laborforce_by_age, 98 | aes(x = value, 99 | y = interaction(year, variable, sep = " "), 100 | fill = age)) + 101 | geom_col(position = position_stack(reverse = TRUE)) + 102 | scale_x_continuous(labels = scales::percent) + 103 | theme_cmap(xlab = "Percent", 104 | gridlines = "v", 105 | vline = 0) + 106 | cmap_fill_discrete(palette = "environment") 107 | ``` 108 | 109 | 110 | 111 | ## Finalizing the plot 112 | 113 | The function `finalize_plot()` places a ggplot within a frame defined by 114 | CMAP design standards. It provides a variety of customization options 115 | via arguments, and allows for in-R viewing and/or exporting in various 116 | formats. 117 | 118 | ``` r 119 | finalize_plot(title = "Regional population and labor force participation", 120 | caption = "Data from the American Community Survey", 121 | width = 7, height = 4.25) 122 | ``` 123 | 124 | 125 | 126 | ## Additional reading 127 | 128 | While this package is designed to make the application of CMAP design 129 | standards to plots relatively easy, developing a professional, finished 130 | plot in R will require a decent familiarity with the grammar of 131 | [ggplot2](ggplot2.tidyverse.org/). Excellent resources in this category 132 | already exist: 133 | 134 | - The [R graphics cookbook](https://r-graphics.org/) provides 135 | accessible examples of how to make almost [any 136 | type](https://r-graphics.org/recipe-miscgraph-vectorfield) of plot, 137 | as well as how to modify things like [limits, 138 | scales](https://r-graphics.org/recipe-axes-range), [coordinate 139 | systems](https://r-graphics.org/recipe-axes-polar), and 140 | [facets](https://r-graphics.org/recipe-facet-basic). 141 | - The [ggplot2 book](https://ggplot2-book.org/) delves deeper into why 142 | and how ggplot2 works the way it does, also with distinct chapters 143 | on topics like 144 | [scales](https://ggplot2-book.org/scales-guides.html), [coordinate 145 | systems](https://ggplot2-book.org/coord.html), 146 | [facets](https://ggplot2-book.org/facet.html), etc. 147 | - The [ggplot2](ggplot2.tidyverse.org/) website. 148 | - The [R for Data Science (R4DS)](https://r4ds.had.co.nz/) book, 149 | especially the [Graphics for 150 | Communication](https://r4ds.had.co.nz/graphics-for-communication.html) 151 | chapter. 152 | -------------------------------------------------------------------------------- /R/default_aes.R: -------------------------------------------------------------------------------- 1 | #' Initialize CMAP `default_aes` values 2 | #' 3 | #' Internal function to load in default aesthetics for modified geoms. 4 | #' Loaded in to `cmapplot_globals` in `.onLoad`. 5 | #' 6 | #' This list's names MUST equal `cmapplot_globals$geoms_that_change` 7 | #' 8 | #' @noRd 9 | init_cmap_default_aes <- function() { 10 | defaults <- list( 11 | Label = list( 12 | family = cmapplot_globals$font$strong$family, 13 | fontface = ifelse(cmapplot_globals$font$strong$face == "bold", 2, 1), 14 | size = cmapplot_globals$fsize$M / ggplot2::.pt, # pt-to-mm conversion 15 | colour = cmapplot_globals$colors$blackish 16 | ), 17 | Line = list( 18 | size = gg_lwd_convert(cmapplot_globals$consts$lwd_plotline) 19 | ), 20 | Text = list( 21 | family = cmapplot_globals$font$strong$family, 22 | fontface = ifelse(cmapplot_globals$font$strong$face == "bold", 2, 1), 23 | size = cmapplot_globals$fsize$M / ggplot2::.pt, # pt-to-mm conversion 24 | colour = cmapplot_globals$colors$blackish 25 | ), 26 | TextLast = list( 27 | family = cmapplot_globals$font$strong$family, 28 | fontface = ifelse(cmapplot_globals$font$strong$face == "bold", 2, 1), 29 | size = cmapplot_globals$fsize$M / ggplot2::.pt, # pt-to-mm conversion 30 | colour = cmapplot_globals$colors$blackish 31 | ), 32 | TextLastRepel = list( 33 | family = cmapplot_globals$font$strong$family, 34 | fontface = ifelse(cmapplot_globals$font$strong$face == "bold", 2, 1), 35 | size = cmapplot_globals$fsize$M / ggplot2::.pt, # pt-to-mm conversion 36 | colour = cmapplot_globals$colors$blackish 37 | ), 38 | PointLast = list( 39 | size = 2 * gg_lwd_convert(cmapplot_globals$consts$lwd_plotline) 40 | ), 41 | RecessionsText = list( 42 | family = cmapplot_globals$font$regular$family, 43 | fontface = ifelse(cmapplot_globals$font$regular$face == "bold", 2, 1), 44 | size = cmapplot_globals$fsize$S / ggplot2::.pt, # pt-to-mm conversion 45 | colour = cmapplot_globals$colors$blackish 46 | ), 47 | PandemicsText = list( 48 | family = cmapplot_globals$font$regular$family, 49 | fontface = ifelse(cmapplot_globals$font$regular$face == "bold", 2, 1), 50 | size = cmapplot_globals$fsize$S / ggplot2::.pt, # pt-to-mm conversion 51 | colour = cmapplot_globals$colors$blackish 52 | ) 53 | ) 54 | 55 | # Return this list only if it lines up with `geoms_that_change`. 56 | # Otherwise, throw an error. 57 | if (setequal(names(defaults), cmapplot_globals$geoms_that_change)) { 58 | return(defaults) 59 | } else { 60 | stop( 61 | "DEV ISSUE: programmed list of `default_aes` does not line up with 62 | `cmapplot_globals$geoms_that_change`.", 63 | call. = FALSE 64 | ) 65 | } 66 | } 67 | 68 | #' Fetch and set aesthetic defaults 69 | #' 70 | #' These functions allow for setting and resetting default aesthetic values for 71 | #' certain ggplot2 geoms. This is necessary for geoms to be "themed" to CMAP 72 | #' style standards, because (at least at the moment) setting geom aesthetic 73 | #' defaults on a plot-by-plot basis (such as with \code{ggplot2::theme}) is not 74 | #' possible. The geoms impacted are stored in 75 | #' \code{cmapplot_globals$geoms_that_change}. 76 | #' 77 | #' These functions are employed implicitly within \code{\link{finalize_plot}} to 78 | #' apply preferred aesthetic defaults to final outputs. They are only necessary 79 | #' to use explicitly if you would like plots to use these defaults 80 | #' pre-\code{finalize}. 81 | #' 82 | #' CAUTION: Running \code{apply_cmap_default_aes} will set defaults for all 83 | #' ggplot2 plots drawn in the current session. To reset to \code{ggplot2} 84 | #' defaults (technically, to whatever the defaults were when \code{cmapplot} 85 | #' was loaded), use \code{unapply_cmap_default_aes}. 86 | #' 87 | #' Note: CMAP aesthetic defaults are loaded into 88 | #' \code{cmapplot_globals$default_aes_cmap} by the internal pkg function 89 | #' \code{init_cmap_default_aes} when \code{cmapplot} is first loaded into R. 90 | #' 91 | #' @param quietly Should the function suppress confirmation message? 92 | #' 93 | #' @examples 94 | #' \dontrun{ 95 | #' g <- ggplot(filter(grp_over_time, category == "Services"), 96 | #' aes(x = year, y = realgrp, color = cluster)) + 97 | #' geom_recessions(ymax = 0.4, text_nudge_x = 0.1) + 98 | #' theme_cmap(hline = 0, 99 | #' axislines = "x", 100 | #' legend.max.columns = 2) + 101 | #' ggtitle("Change in gross regional product over time") + 102 | #' geom_line() + 103 | #' scale_x_continuous("Year", breaks = seq(2007, 2017, 2)) + 104 | #' coord_cartesian(clip = "off") + 105 | #' geom_text_lastonly(aes(label = realgrp), add_points = TRUE) 106 | #' 107 | #' # print normally 108 | #' g 109 | #' 110 | #' # overwrite `default_aes` 111 | #' apply_cmap_default_aes() 112 | #' g 113 | #' 114 | #' # reset `default_aes` 115 | #' unapply_cmap_default_aes() 116 | #' g 117 | #' 118 | #' # finalize alters the defaults implicitly, but then resets them automatically. 119 | #' finalize_plot(g) 120 | #' 121 | #' # you can also use `finalize` without these modifications 122 | #' finalize_plot(g, use_cmap_aes = FALSE) 123 | #' } 124 | #' 125 | #' @name cmap_default_aes 126 | NULL 127 | 128 | #' @describeIn cmap_default_aes Apply CMAP aesthetic defaults to all ggplots in 129 | #' the current session 130 | #' 131 | #' @export 132 | apply_cmap_default_aes <- function(quietly = FALSE) { 133 | set_default_aes(cmapplot_globals$default_aes_cmap) 134 | 135 | if (!quietly) { 136 | message( 137 | paste0( 138 | "Aesthetic defaults overridden in the current session for the following Geoms:", 139 | "\n ", 140 | paste(cmapplot_globals$geoms_that_change, collapse = ", "), 141 | "\nTo undo this change, run `unapply_cmap_default_aes()`." 142 | ) 143 | ) 144 | } 145 | } 146 | 147 | 148 | #' @describeIn cmap_default_aes Reset modified geom aesthetics to their values 149 | #' when \code{cmapplot} was first loaded 150 | #' 151 | #' @export 152 | unapply_cmap_default_aes <- function(quietly = FALSE) { 153 | set_default_aes(cmapplot_globals$default_aes_cached) 154 | 155 | if (!quietly) { 156 | message( 157 | paste0( 158 | "Aesthetic defaults reset to values cached when pkg `cmapplot` was first loaded for Geoms:", 159 | "\n ", 160 | paste(cmapplot_globals$geoms_that_change, collapse = ", ") 161 | ) 162 | ) 163 | } 164 | } 165 | 166 | 167 | #' Fetch `default_aes` from select geoms 168 | #' 169 | #' Internal function to fetch `default_aes` for geoms that this package plans to 170 | #' overwrite. Used in `.onLoad` and in `finalize_plot`. 171 | #' 172 | #' @importFrom purrr map 173 | #' @noRd 174 | fetch_current_default_aes <- function() { 175 | purrr::map( 176 | cmapplot_globals$geoms_that_change, 177 | function(geom) { 178 | get(paste0("Geom", geom))$default_aes 179 | } 180 | ) %>% 181 | rlang::set_names(cmapplot_globals$geoms_that_change) 182 | } 183 | 184 | 185 | #' Set `default_aes` for select geoms 186 | #' 187 | #' Internal function to overwrite `default_aes` for the necessary geoms. Used in 188 | #' `finalize_plot`, `apply_cmap_geom_defaults` and `unapply_cmap_geom_defaults`. 189 | #' 190 | #' @param values A named list of parameters, presumably from (or at least a 191 | #' parallel structure to) the output of `fetch_cmap_geom_defaults`. 192 | #' 193 | #' @importFrom purrr walk2 194 | #' @noRd 195 | set_default_aes <- function(values) { 196 | purrr::walk2( 197 | cmapplot_globals$geoms_that_change, 198 | values, 199 | ggplot2::update_geom_defaults 200 | ) 201 | } 202 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # cmapplot 1.2.1 2 | PR #143 3 | 4 | ### Bug fixes 5 | * `cmap_fill_race` and `cmap_color_race` have been updated to remove unused race/ethnicity categories from chart legend (see issue #142) 6 | 7 | 8 | # cmapplot 1.2.0 9 | PR #123 10 | 11 | This is a version-level update that includes many fixes and new features and introduces new dependencies, most significantly RStudio 1.4 or greater. 12 | 13 | ### New features 14 | * cmapplot now utilizes the new systemfonts package for custom font rendering, rather than sysfonts on Windows and X11fonts on Mac and other Unix-based machines. In addition, raster exports from `finalize_plot` now rely on the new raster drawing package ragg. These changes improve font accuracy and consistency across platforms. Note that this improvement does not yet extend to svg outputs, but may in the future (#134). 15 | * When an axis breaks are 4-digit years, new function `abbr_years` allows the conversion of specific years to 2-digit abbreviations, as is common on some designed CMAP graphics. 16 | * `cmapplot_globals`, which contains key package constants, is now an internal environment rather than an exported list. It can be accessed via new functions `get_cmapplot_globals` and `get_cmapplot_global`. Constants can now be overridden by the user for the current session by using `set_cmapplot_global`. Note that this does not yet apply to geom aesthetics set by the package, but may in the future (issue #117). 17 | * All palettes programmed into the package have been moved into a tibble at `cmapplot_globals$palettes`. A new function `fetch_pal` can be used to get details about any specific palette. 18 | 19 | ### Bug fixes 20 | * Continuous color gradients can now be used on discrete color scales. E.g. `cmap_color_discrete` and `cmap_fill_discrete` can now call gradients as well as discrete palettes (see #70 and #119) 21 | * Nomenclature in help files (e.g. "palette" vs "scale") has been adjusted for clarity. 22 | * internal table `recessions` has been updated to include the business cycle contraction that began in Feb 202 (no end date for this cycle yet). 23 | * `geom_recessions` and related `update_recessions` have been updated to allow for ongoing recessions, improve NBER source for recessions table, and decrease likelihood of data fetch errors. 24 | 25 | ### Backend changes 26 | * pkgdown site now correctly displays Whitney fonts and margin description images 27 | * backend script reorganization 28 | * improvements to pkgdown build github action, including ability to publish a test site 29 | 30 | ### Backward compatibility notes 31 | * **This package will now only render Whitney Fonts in RStudio when RStudio version >= 1.4** 32 | * `finalize_plot`'s `window` mode has been disabled for now, due to inability to use ragg drivers in independent window devices. Use `mode = "plot"` and click the "Zoom" button in the plot window instead. 33 | * `cmapplot_globals`, the exported list of package constants, has been removed (See new features `set_cmapplot_global` etc) 34 | * Color lists `cmap_palettes` and `cmap_gradients` have been removed (This information has been moved to `cmapplot_globals$palettes`. To access palette colors directly, use, say `fetch_pal("reds")` rather than `cmap_gradients$reds`. 35 | * `viz_palette` and `viz_gradient` now take as a first argument either the name of a palette (e.g. `"reds"`) or the color palette itself (e.g. `fetch_pal("reds")`). `viz_palette(cmap_gradient$reds)` no longer works. 36 | * `integer_breaks` removed from package 37 | 38 | 39 | # cmapplot 1.1.0 40 | PR #111 | February 24, 2021 41 | 42 | PR #115 | March 2, 2021 43 | 44 | This pair of updates primarily makes many changes to `finalize_plot()` to enable printing plots without the left-hand "sidebar" -- the area that contains the title and the caption. Most but not all changes are under the hood and should not impact the user. Those that will impact the user include: 45 | 46 | * There is a new argument, `inherit`, that allows the user to specify whether cmapplot should attempt to inherit titles and/or captions from the underlying ggplot object. 47 | * The argument `title_width` has been renamed `sidebar_width` to better reflect its function. 48 | * Setting `sidebar_width = 0` now has the effect of shifting the title above the topline and shifting the caption from the title column to directly below the plot. If the user does not want a title on the vertical layout graphic, this can be achieved by leaving `title = ""`, the default, and (if relevant) specifying that `finalize_plot()` should not attempt to inherit a title from the underlying ggplot object. 49 | * There is a new argument, `caption_align`, which takes numeric range 0 to 1. `0`, the default, aligns the caption bottom or left (in title-column and below-plot captions, respectively). `1` aligns the caption top or right. `0.5` centers. The argument `caption_valign` has been deprecated. 50 | * The value `margin_title_l` has been replaced with `margin_sidebar_l`, which only affects horizontal layout graphics. The margin for titles and captions in the vertical layout is based on `margin_plot_l`. 51 | * Separately, this version also creates this `NEWS.md` file for the pkgdown website. 52 | 53 | Under-the-hood changes to `finalize_plot()` are documented in PR #111, specifically [here](https://github.com/CMAP-REPOS/cmapplot/pull/111#issuecomment-782779446). 54 | 55 | ### Backward compatibility notes 56 | Users who have written code with previous versions of cmapplot should note these known compatibility issues: 57 | * In `finalize_plot()`, the argument `caption_valign` has been deprecated and will now issue a message alert (but will still work, for now). Please update your code to use the new argument `caption_align`. 58 | * In `finalize_plot()`, the argument `title_width` has been deprecated and will now issue a message alert (but will still work, for now). Please update your code to use the new argument `sidebar_width`. 59 | * Any overrides using the deprecated value `margin_title_l` will no longer have any affect. Use `margin_sidebar_l` instead. 60 | 61 | 62 | # cmapplot 1.0.4 63 | PR #110 | February 3, 2021 64 | 65 | * The ggplot2 geom `geom_label` is now added to the list of geoms for which text aesthetics are automatically updated to CMAP style (ie to Whitney fonts). Previously, only `geom_text` and the custom `geom_text_last` were available in CMAP style. 66 | 67 | 68 | # cmapplot 1.0.3 69 | PR #107 | February 2, 2021 70 | 71 | * Modified `finalize_plot()` to accept `title_width = 0` without causing a fuss. This is a short-term fix, with more improvements coming. 72 | * In `cmapplot_globals$consts`, eliminated `margin_title_r`, which created space between the title/caption and plotbox inside the title column. Replaced it with `margin_plot_l`, which creates the same buffer but does so in the plot column, not the title column. This was necessary to keep an active left-hand margin in situations where `title_width = 0`. 73 | 74 | ### Backward compatibility notes 75 | Users who have written code with previous versions of cmapplot should note these known compatibility issues: 76 | * `margin_title_r` no longer exists. Code that overrides this in `finalize_plot()` should not error, but will also have no effect on your plot. Change to `margin_plot_l`. 77 | * Titles and captions will be a bit wider (the width of the title and caption grobs are no longer modified by `margin_title_r`). 78 | * Plots will be a bit narrower (the width of the plotbox is now modified by `margin_plot_l`). 79 | 80 | 81 | # cmapplot 1.0.2 82 | PR #103 | January 11, 2021 83 | 84 | * Fixed bug where custom color functions (e.g. `cmap_fill_continuous()` etc) did not allow for passing other arguments on to ggplot2's `scale` functions (see issue #102). 85 | 86 | 87 | # cmapplot 1.0.1 88 | PR #100 | December 9, 2020 89 | 90 | * Improvement of tickmark handling via addition of `axisticks` argument in `theme_cmap()` and new `length_ticks` default value in `cmapplot_globals$consts`. 91 | * Substantial backend simplification of how `theme_cmap()` generates theme objects (not of substantial significance to users). 92 | 93 | 94 | # cmapplot 1.0.0 95 | December 1, 2020 96 | 97 | * Initial package release. 98 | -------------------------------------------------------------------------------- /R/data.R: -------------------------------------------------------------------------------- 1 | #' Job change in CMAP region by cluster, 2001-17 2 | #' 3 | #' A test dataset containing 2001-17 job change and other data for the 22 specialized traded clusters 4 | #' analyzed in the CMAP Traded Clusters report. 5 | #' 6 | #' @format A tibble. 22 rows and 5 variables: 7 | #' \describe{ 8 | #' \item{code}{Integer. code of cluster} 9 | #' \item{name}{Char. textual description/cluster title} 10 | #' \item{category}{Factor. Either "goods-producing" or "services"} 11 | #' \item{assessment}{Factor. "Leading", "Mixed", or "Trailing"} 12 | #' \item{jobchange}{Integer. Total change in employment in the cluster between 2001-17} 13 | #' } 14 | #' @source CMAP traded clusters report 15 | #' 16 | #' @examples 17 | #' # A bar chart 18 | #' ggplot(cluster_jobchange, aes(x = reorder(name, jobchange), y = jobchange, fill = category)) + 19 | #' geom_col() + 20 | #' coord_flip() 21 | #' 22 | #' 23 | "cluster_jobchange" 24 | 25 | 26 | #' Basic regional economic stats in 2001 and 2017 27 | #' 28 | #' A test dataset containing count of jobs, earnings, and establishments in the Chicago region in both 2001 and 2017. 29 | #' 30 | #' @format A tibble. 18 rows and 4 variables: 31 | #' \describe{ 32 | #' \item{variable}{Chr. Indicates the meaning of the data stored in `value`. Jobs, Real Earnings, or Establishments.} 33 | #' \item{year}{Factor. 2001 or 2017.} 34 | #' \item{sector}{Chr. local, tradedgoods, or tradedservices. Together, these three sectors account for all clusters in the region.} 35 | #' \item{value}{Int. The value indicated as described by the other columns} 36 | #' } 37 | #' @source CMAP traded clusters report 38 | #' 39 | #' @examples 40 | #' # a grouped and stacked bar chart (via `interaction()`) 41 | #' ggplot(economy_basic, aes(x = interaction(year, variable), y = value, fill = sector)) + 42 | #' geom_col(position = "fill") + 43 | #' scale_y_continuous(labels = scales::percent) 44 | #' 45 | "economy_basic" 46 | 47 | 48 | #' Gross Regional Product by cluster, 2007-17 49 | #' 50 | #' A test dataset containing real GRP data for the CMAP region. 51 | #' 52 | #' @format A tibble. 121 rows and 5 variables: 53 | #' \describe{ 54 | #' \item{cluster}{Chr. The name of the cluster} 55 | #' \item{category}{Factor. "goods-producing" or "services"} 56 | #' \item{assessment}{Factor. "Trailing", "Mixed", or "Leading"} 57 | #' \item{year}{Double. The year of the data} 58 | #' \item{realgrp}{Double. The real gross regional product of the cluster in year `year`. 59 | #' Not exactly sure on the inflation year but I believe it is 2012} 60 | #' } 61 | #' @source CMAP traded clusters report 62 | #' 63 | #' @examples 64 | #' # a time-series line chart 65 | #' ggplot(grp_over_time, aes(x = year, y = realgrp, color = cluster)) + 66 | #' geom_line() 67 | #' 68 | "grp_over_time" 69 | 70 | 71 | #' Gross Regional Product by region, with peers, 2001-17 72 | #' 73 | #' A test dataset containing real GRP data for the CMAP and peer regions. 74 | #' 75 | #' @format A tibble. 121 rows and 5 variables: 76 | #' \describe{ 77 | #' \item{area}{Factor. name of the region} 78 | #' \item{year}{Double. year of the data} 79 | #' \item{grp}{Double. real gross regional product} 80 | #' } 81 | #' @source CMAP traded clusters report 82 | #' 83 | #' @examples 84 | #' # a time-series line chart 85 | #' ggplot(grp_over_time, aes(x = year, y = realgrp, color = cluster)) + 86 | #' geom_line() 87 | #' 88 | "peer_grp" 89 | 90 | 91 | #' Wage percentiles by cluster 92 | #' 93 | #' A test dataset containing the 10th, 25th, 50th, 75th, and 90th percentile wage by cluster in the CMAP region. 94 | #' 95 | #' @format A tibble. 45 rows and 3 variables: 96 | #' \describe{ 97 | #' \item{cluster}{Chr. The name of the cluster} 98 | #' \item{percentile}{Double. The percentile wage being reported} 99 | #' \item{wage}{Double. The wage. I believe 2017 data.} 100 | #' } 101 | #' @source CMAP traded clusters report 102 | #' 103 | #' @examples 104 | #' # a non-time-series line chart 105 | #' ggplot(percentile_wages, aes(x = percentile, y = wage, color = cluster)) + 106 | #' geom_line() 107 | #' 108 | "percentile_wages" 109 | 110 | 111 | #' Population and Labor Force by Age 112 | #' 113 | #' A test dataset containing percentage breakdowns of the population and labor force by various age buckets in 2010 and 2017. 114 | #' 115 | #' @format A tibble. 12 rows and 4 variables: 116 | #' \describe{ 117 | #' \item{variable}{Chr. Indicates the meaning of the data stored in `value`. "population" or "laborforce".} 118 | #' \item{year}{Factor. 2010 or 2017.} 119 | #' \item{age}{Chr. The age bucket. Either 16-24, 25-54, or 55+.} 120 | #' \item{value}{Double. The value indicated by the other variables.} 121 | #' } 122 | #' @source CMAP traded clusters report 123 | #' 124 | #' @examples 125 | #' # a grouped and stacked bar chart (via `interaction()`) 126 | #' ggplot(pop_and_laborforce_by_age, aes(x = interaction(year, variable), y = value, fill = age)) + 127 | #' geom_col(position = position_stack(reverse = TRUE)) 128 | #' 129 | "pop_and_laborforce_by_age" 130 | 131 | 132 | #' Traded employment by race 133 | #' 134 | #' A test dataset containing the percentage breakdowns of the working population employed in traded clusters, by race. 135 | #' 136 | #' 137 | #' @format A tibble. 12 rows and 4 variables: 138 | #' \describe{ 139 | #' \item{Race}{Chr. White, Asian, Hispanic, Other, Black, or Regional Average.} 140 | #' \item{variable}{Chr. SpecializedTraded, UnspecializedTraded, or Total. 141 | #' Total is a sum of SpecializedTraded and UnspecializedTraded. The invisible remainder (e.g. `1-Total` or 142 | #' `1-(SpecializedTraded+UnspecializedTraded)`) is the percentage employed in local clusters.} 143 | #' \item{value}{Double. The value indicated by the other variables.} 144 | #' } 145 | #' @source CMAP traded clusters report 146 | #' 147 | #' @examples 148 | #' # a stacked bar chart 149 | #' \dontshow{library(dplyr)} 150 | #' df <- dplyr::filter( 151 | #' traded_emp_by_race, 152 | #' variable %in% c("SpecializedTraded", "UnspecializedTraded") 153 | #' ) 154 | #' ggplot(df, aes(x = reorder(Race, -value), y = value, fill = variable)) + 155 | #' geom_col(position = position_stack(reverse = TRUE)) + 156 | #' scale_y_continuous(labels = scales::percent) 157 | #' 158 | "traded_emp_by_race" 159 | 160 | 161 | 162 | #' Transit ridership in the Chicago region, 1980-2024 163 | #' 164 | #' A test dataset containing 1980-2019 transit ridership for the three service 165 | #' boards that provide transit in Northeastern Illinois. 166 | #' 167 | #' @format A tibble. 225 rows and 3 variables 168 | #' \describe{ 169 | #' \item{year}{Double. Year of data} 170 | #' \item{system}{Char. Name of system (includes CTA bus, CTA rail, Metra, Pace, and Pace ADA)} 171 | #' \item{ridership}{Double. Annual unlinked passenger trips in millions} 172 | #' } 173 | #' @source Regional Transportation Authority \url{http://www.rtams.org/rtams/systemRidership.jsp} 174 | #' 175 | #' @examples 176 | #' # A line graph 177 | #' ggplot(transit_ridership,aes(x = year,y=ridership,group=system,color=system)) + 178 | #' geom_line(na.rm=TRUE) 179 | #' 180 | #' 181 | "transit_ridership" 182 | 183 | 184 | 185 | 186 | #' Vehicle ownership in the CMAP seven county region 187 | #' 188 | #' A test dataset containing vehicle ownership rates in the seven county region 189 | #' of northeastern Illinois. 190 | #' 191 | #' @format A tibble. 40 rows and 3 variables 192 | #' \describe{ 193 | #' \item{county}{Char. Name of county} 194 | #' \item{number_of_veh}{Char. Number of vehicles owned by household} 195 | #' \item{pct}{Numeric. Share of households with the given number of vehicles (values between 0 and 1)} 196 | #' } 197 | #' @source CMAP Travel Inventory Survey Data Summary \url{https://www.cmap.illinois.gov/documents/10180/77659/Travel+Inventory+Survey+Data+Summary_weighted_V2.pdf/d4b33cdd-1c44-4322-b32f-2f54b85207cb} 198 | #' 199 | #' @examples 200 | #' # A stacked bar chart 201 | #' ggplot(vehicle_ownership, 202 | #' aes(x = county, y = pct, fill = number_of_veh)) + 203 | #' geom_bar(position = position_stack(), stat = "identity") 204 | #' 205 | "vehicle_ownership" 206 | 207 | -------------------------------------------------------------------------------- /vignettes/colors.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Applying CMAP color schemes" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Applying CMAP color schemes} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r setup, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>", 14 | fig.width = 7, 15 | fig.asp = 400/670, 16 | fig.retina = 4, 17 | fig.align = "center", 18 | dev = "ragg_png" 19 | ) 20 | 21 | library(tidyverse) 22 | library(cmapplot) 23 | ``` 24 | 25 | There are `r nrow(get_cmapplot_global("palettes"))` color palettes in the cmapplot package, across three categories: discrete, sequential, and divergent. Mirroring the underlying functionality of ggplot2, cmapplot can apply these color palettes as either `discrete` or `continuous` scales to either the `color` (outline) or `fill` attributes of a ggplot. 26 | 27 | This table lists all palettes currently available in the package: 28 | 29 | ```{r palettes, echo = FALSE, results = 'asis'} 30 | 31 | pals <- get_cmapplot_global("palettes") 32 | discrete <- filter(pals, type == "discrete")[["name"]] 33 | sequential1 <- filter(pals, type == "sequential", !stringr::str_detect(name, "_"))[["name"]] 34 | sequential2 <- filter(pals, type == "sequential", stringr::str_detect(name, "_"))[["name"]] 35 | divergent <- filter(pals, type == "divergent")[["name"]] 36 | 37 | maxlength <- max(length(discrete), length(sequential1), length(sequential2), length(divergent)) 38 | length(discrete) <- length(sequential1) <- length(sequential2) <- length(divergent) <- maxlength 39 | 40 | out <- tibble(Discrete = discrete, 41 | Sequential = sequential1, 42 | `Sequential (multi-hue)` = sequential2, 43 | Divergent = divergent) 44 | out[is.na(out)] <- "" 45 | 46 | knitr::kable(out) 47 | ``` 48 | 49 | ## Using CMAP color palettes in plots 50 | 51 | ### Discrete scales 52 | 53 | Discrete color scales are used to assign colors to discrete bins, such as the lines in a time series line chart. Discrete scale functions work with any palette -- discrete, sequential, or divergent. Add a discrete color scale by calling either the `cmap_fill_discrete()` or `cmap_color_discrete()` function in your plot construction. 54 | 55 | Note that discrete scales will automatically interpolate additional colors if the dataset has more colors than the underlying palette. This can be helpful but is not ideal for finished graphics. 56 | 57 | ```{r color_discrete, message = FALSE} 58 | df <- dplyr::filter(grp_over_time, category == "Goods-Producing") 59 | 60 | ggplot(data = df) + 61 | geom_line(mapping = aes(x = year, y = realgrp, color = cluster), 62 | size = 1.25) + 63 | scale_x_continuous(breaks = seq(from = 2007, to = 2017, by = 2)) + 64 | cmap_color_discrete(palette = "community", reverse = TRUE) + 65 | theme_cmap() + 66 | ggtitle("Real GRP of goods-producing clusters over time") 67 | ``` 68 | 69 | ### Race/ethnicity scale 70 | 71 | In order to maintain a consistent data language, CMAP uses specific colors for displaying data based on race and ethnicity. To map these colors to each demographic group, use `cmap_fill_race()` or `cmap_color_race()`. 72 | 73 | In the arguments, specify the case-sensitive name of each group as it appears in your data. The functions can be used even if your dataset does not contain every race/ethnicity category — simply omit the parameters for the missing categories. Likewise, if your dataset already contains the default race/ethnicity categories (case-sensitive) specified in the race palette, no arguments have to be provided. 74 | 75 | ```{r color_race, message = FALSE} 76 | df <- dplyr::filter( 77 | traded_emp_by_race, 78 | Race != "Regional average" & variable == "SpecializedTraded" 79 | ) 80 | 81 | ggplot(data = df) + 82 | geom_col(mapping = aes(x = Race, y = value, fill = Race)) + 83 | scale_y_continuous(labels = scales::label_percent(accuracy = 1)) + 84 | cmap_fill_race(white = "White", 85 | black = "Black", 86 | hispanic = "Hispanic", 87 | asian = "Asian", 88 | other = "Other") + 89 | theme_cmap(show.legend = FALSE) + # Legend is redundant in this example 90 | ggtitle("Percent employed in specialized traded clusters, by race") 91 | ``` 92 | 93 | ### Continuous scales 94 | Continuous color scales work with sequential and divergent palettes. Add a continuous scale by calling either the `cmap_fill_continuous()` or `cmap_color_continuous()` function in your plot construction. If you're using a divergent palette, you can specify the midpoint where the divergence begins (default is zero). 95 | 96 | ```{r color_continuous, message = FALSE} 97 | df <- dplyr::filter( 98 | percentile_wages, 99 | cluster %in% c("Biopharmaceuticals", "Hospitality and Tourism", "Paper and Packaging") 100 | ) 101 | 102 | ggplot(data = df) + 103 | geom_point(mapping = aes(x = cluster, y = wage, color = percentile), 104 | size = 5) + 105 | scale_y_continuous(labels = scales::dollar) + 106 | scale_x_discrete(labels = scales::label_wrap(18)) + 107 | coord_flip() + 108 | cmap_color_continuous(palette = "red_teal", middle = 50) + 109 | theme_cmap() + 110 | ggtitle("Wage percentiles for key clusters") 111 | ``` 112 | 113 | ### Highlighting categories 114 | 115 | If you want to draw attention to a specific group in your graph, use `cmap_fill_highlight()` or `cmap_color_highlight()`. This will make your highlighted group one color and all other groups identical in another color. Specify the vector in your data that determines the groups, and then the value of the group to be singled out. Note that this *must* be the same vector specified in the `fill`/`color` aesthetic. The highlight and non-highlight colors have defaults, but can be changed with the `color_value` and `color_other` parameters, respectively. 116 | 117 | More than one category can be highlighted as well, by feeding a vector of values into the the `value` argument. See `?cmap_fill_highlight` for details. 118 | 119 | ```{r color_highlight, message = FALSE} 120 | df <- dplyr::filter(transit_ridership, year == 2019) 121 | 122 | ggplot(data = df) + 123 | geom_col(mapping = aes(x = system, y = ridership, fill = system)) + 124 | cmap_fill_highlight(field = transit_ridership$system, 125 | value = "metra") + 126 | theme_cmap(show.legend = FALSE) + # Legend is redundant in this example 127 | ggtitle("Annual passenger trips by service, in millions") 128 | ``` 129 | 130 | 131 | ## Available color palettes 132 | Palettes are stored in a tibble in the `cmapplot_globals` environment. This tibble can be accessed with `get_cmapplot_global("palettes")`, with specific details about any specific palette most easily accessed with `fetch_pal()`. Palettes can be visualized in the plot window with `viz_palette()` and `viz_gradient()`. 133 | 134 | ### Discrete palettes 135 | Use these palettes with `cmap_fill_discrete()` or `cmap_color_discrete()`: 136 | ```{r show_discrete, echo = FALSE, fig.asp = 300/670} 137 | nms <- dplyr::filter(get_cmapplot_global("palettes"), type == "discrete", name != "race")$name 138 | purrr::walk(nms, viz_palette) 139 | ``` 140 | 141 | The race palette is a special discrete palette, which should be used with the specific functions `cmap_fill_race()` or `cmap_color_race()`: 142 | ```{r show_discrete_palettes, echo = FALSE, fig.asp = 300/670} 143 | viz_palette("race") 144 | ``` 145 | 146 | ### Sequential palettes 147 | Use these palettes with `cmap_fill_continuous()`/`cmap_color_continuous()` or `cmap_fill_discrete()`/`cmap_color_discrete()`. 148 | ```{r show_sequential, echo = FALSE, fig.asp = 300/670} 149 | nms <- dplyr::filter(get_cmapplot_global("palettes"), type == "sequential")$name 150 | purrr::walk(nms, viz_gradient) 151 | ``` 152 | 153 | If you use a sequential palette for a discrete scale, the package will automatically choose colors from across the selected gradient and interpolate additional ones if needed. For example, the `blues` palette is shown below, interpolating nine colors from the initial palette list of seven. 154 | 155 | ```{r show_sequential_discrete, echo = FALSE, fig.asp = 300/670} 156 | viz_palette("blues", num = 9) 157 | ``` 158 | 159 | ### Divergent palettes 160 | Use these palettes with `cmap_fill_continuous()`/`cmap_color_continuous()` or `cmap_fill_discrete()`/`cmap_color_discrete()`. Using these palettes for a discrete scale will behave similarly to the discrete use of `blues` shown above. 161 | ```{r show_diverent, echo = FALSE, fig.asp = 300/670} 162 | nms <- dplyr::filter(get_cmapplot_global("palettes"), type == "divergent")$name 163 | purrr::walk(nms, viz_gradient) 164 | ``` 165 | 166 | -------------------------------------------------------------------------------- /R/utilities.R: -------------------------------------------------------------------------------- 1 | #' Font visualization test 2 | #' 3 | #' This internal function uses base R graphics to display the five text variants 4 | #' that should show up on a CMAP themed graphic - and what fonts the package is 5 | #' planning to use to display them. 6 | #' 7 | #' @noRd 8 | display_cmap_fonts <- function() { 9 | graphics::plot(c(0,2), c(0,6), type="n", xlab="", ylab="") 10 | 11 | draw.me <- function(name, font, size, placement){ 12 | thisfont <- cmapplot_globals$font[[font]] 13 | thissize <- cmapplot_globals$fsize[[size]] 14 | 15 | graphics::par(family=thisfont$family, 16 | font=ifelse(thisfont$face == "bold", 2, 1)) 17 | graphics::text(1, placement, 18 | paste(name, 19 | paste(paste("font:", font), paste("size:", size), sep = ", "), 20 | paste(thisfont$family, thisfont$face, thissize, sep = ", "), 21 | sep = " | "), 22 | cex=thissize/12, ps=12) 23 | } 24 | 25 | draw.me(name = "Title", font = "strong", size = "L", 5) 26 | draw.me(name = "Main", font = "regular", size = "M", 4) 27 | draw.me(name = "Axis", font = "light", size = "M", 3) 28 | draw.me(name = "Label", font = "strong", size = "M", 2) 29 | draw.me(name = "Note", font = "light", size = "S", 1) 30 | } 31 | 32 | 33 | 34 | # Plot sizes and colors --------------------------------------------------- 35 | 36 | #' Line width conversion 37 | #' 38 | #' The factor \code{.lwd} is used to calculate correct output sizes for line 39 | #' widths. For line widths in \code{ggplot2}, the size in mm must be divided 40 | #' by this factor for correct output. Because the user is likely to prefer 41 | #' other units besides for mm, \code{gg_lwd_convert()} is provided as a 42 | #' convenience function, converting from any unit all the way to ggplot units. 43 | #' 44 | #' \code{.lwd} is equal to \code{ggplot2::.stroke / ggplot2::.pt}. In 45 | #' \code{ggplot2}, the size in mm is divided by \code{.lwd} to achieve the 46 | #' correct output. In the \code{grid} package, however, the size in points 47 | #' (\code{pts} (or maybe \code{bigpts}? Unclear.) must be divided by 48 | #' \code{.lwd}. The user is unlikely to interact directly with \code{grid}, 49 | #' but this is how \code{finalize_plot()} does its work. 50 | #' 51 | #' This is closely related to \code{ggplot::.pt}, which is the factor that 52 | #' font sizes (in \code{pts}) must be divided by for text geoms within 53 | #' \code{ggplot2}. Confusingly, \code{.pt} is not required for \code{ggplot2} 54 | #' font sizes outside the plot area: e.g. axis titles, etc. 55 | #' 56 | #' @seealso grid's \code{\link[grid]{unit}}, ggplot2's 57 | #' \code{\link[ggplot2]{.pt}}, and 58 | #' \url{https://stackoverflow.com/questions/17311917/ggplot2-the-unit-of-size} 59 | #' 60 | #' @examples 61 | #' ggplot() + coord_cartesian(xlim = c(-3, 3), ylim = c(-3, 3)) + 62 | #' 63 | #' # a green line 3 points wide 64 | #' geom_hline(yintercept = 1, color = "green", size = gg_lwd_convert(3)) + 65 | #' 66 | #' # black text of size 24 points 67 | #' annotate("text", -2, 0, label = "text", size = 24/ggplot2::.pt) 68 | #' 69 | #' 70 | #' # a blue line 6 points wide, drawn over the plot with the `grid` package 71 | #' grid::grid.lines(y = 0.4, gp = grid::gpar(col = "blue", lwd = 6 / .lwd)) 72 | #' 73 | #' @export 74 | .lwd <- ggplot2::.pt / ggplot2::.stroke 75 | 76 | 77 | #' Helper function to calculate correct size for ggplot line widths. 78 | #' 79 | #' @param value Numeric, the value to be converted. 80 | #' @param unit Char, the unit of the value to be converted. Can be any of the 81 | #' units accepted by \code{grid::unit()}, including "bigpts", "pt", "mm", and 82 | #' "in". Default is \code{bigpts}. 83 | #' 84 | #' @describeIn dot-lwd Function to convert from any unit directly to ggplot2's 85 | #' preferred millimeters. 86 | #' 87 | #' @export 88 | gg_lwd_convert <- function(value, unit = "bigpts") { 89 | 90 | # convert input type to mm 91 | value_out <- grid::convertUnit(grid::unit(value, unit), "mm", valueOnly = TRUE) 92 | 93 | # return with conversion factor 94 | return( 95 | value_out / .lwd 96 | ) 97 | } 98 | 99 | 100 | 101 | #' Identify correct font path based on filename 102 | #' 103 | #' Taking a list of font paths, search for a specific filename. If a perfect 104 | #' match is found, return that path. 105 | #' 106 | #' @param filename the complete file name, less a .otf or .ttf extension. 107 | #' @param path a vector of filepaths 108 | #' 109 | #' @noRd 110 | find_path <- function(filename, paths){ 111 | result <- grep(paste0("(\\\\|/)", filename, ".[ot]tf$"), paths, value = TRUE) 112 | 113 | if(length(result) >= 1){ 114 | return(result[1]) 115 | } else { 116 | stop( 117 | paste0("Font '", filename, "' not found."), 118 | call. = FALSE) 119 | } 120 | } 121 | 122 | 123 | #' Sub-fn to safely intepret grobHeight 124 | #' 125 | #' This returns the height of a grob in any real unit. If the value passed in is 126 | #' null, it returns 0. It is used in various places in `finalize_plot`. 127 | #' 128 | #' @noRd 129 | safe_grobHeight <- function(grob, unitTo = "bigpts", valueOnly = TRUE){ 130 | 131 | if(is.null(grob)){ 132 | if(valueOnly){ 133 | return(0) 134 | } else { 135 | return(unit(0, unitTo)) 136 | } 137 | } 138 | 139 | return(grid::convertHeight(grid::grobHeight(grob), unitTo, valueOnly)) 140 | } 141 | 142 | 143 | #' Palette Fetcher 144 | #' 145 | #' @param which a vector of palette types to consider 146 | #' @param return Value to return. "colors", the default, returns the palette as 147 | #' a vector of colors. "type" returns the palette's type. "Exists" returns 148 | #' TRUE or FALSE based on whether the name is found in the palettes table. 149 | #' 150 | #' @describeIn viz_palette Returns details about a palette 151 | #' 152 | #' @examples 153 | #' # Identify the first two colors of the Prosperity Palette 154 | #' fetch_pal("prosperity")[1:2] 155 | #' 156 | #' # Confirm that "reds" is a sequential palette 157 | #' fetch_pal("reds", which = "sequential", return = "exists") 158 | #' 159 | #' @export 160 | fetch_pal <- function(pal, 161 | which = c("discrete", "sequential", "divergent"), 162 | return = c("colors", "type", "exists")){ 163 | # basics 164 | name <- type <- NULL 165 | return <- match.arg(return) 166 | which <- match.arg(which, unique(cmapplot_globals$palettes$type), several.ok = TRUE) 167 | 168 | # filter palettes 169 | df <- dplyr::filter( 170 | cmapplot_globals$palettes, 171 | name == pal, 172 | type %in% which 173 | ) 174 | 175 | # construct return 176 | if (return == "exists") { 177 | return(nrow(df) == 1) 178 | } else if (nrow(df) == 1) { 179 | return(df[[return]][[1]]) 180 | } else { 181 | return(NULL) 182 | } 183 | } 184 | 185 | 186 | 187 | # ggrepel utility functions ----------------------------------------------- 188 | 189 | ggname <- function(prefix, grob) { 190 | grob$name <- grobName(grob, prefix) 191 | grob 192 | } 193 | 194 | with_seed_null <- function(seed, code) { 195 | if (is.null(seed)) { 196 | code 197 | } else { 198 | withr::with_seed(seed, code) 199 | } 200 | } 201 | 202 | .pt <- 72.27 / 25.4 203 | 204 | "%||%" <- function(a, b) { 205 | if (!is.null(a)) a else b 206 | } 207 | 208 | #' Return a boolean vector of non-empty items. 209 | #' 210 | #' @param xs Vector with a mix of "expression" items, "character" items, 211 | #' and items from other classes. 212 | #' @return Boolean vector indicating which items are not empty. 213 | #' @noRd 214 | not_empty <- function(xs) { 215 | sapply(seq_along(xs), function(i) { 216 | if (is.expression(xs[i])) { 217 | return(length(nchar(xs[i])) > 0) 218 | } else { 219 | return(xs[i] != "") 220 | } 221 | }) 222 | } 223 | 224 | #' Return a unit version of the argument. 225 | #' 226 | #' @param x Number or unit object. 227 | #' @return unit(x, "lines") if number or the unchanged argument if it's already 228 | #' a unit object. 229 | #' @noRd 230 | to_unit <- function(x) { 231 | # don't change arg if already unit 232 | if (is.unit(x)) { 233 | return(x) 234 | } 235 | 236 | # NA used to exclude points from repulsion calculations 237 | if (length(x) == 1 && is.na(x)) { 238 | return(NA) 239 | } 240 | 241 | unit(x, "lines") 242 | } 243 | 244 | #' Parse takes a vector of n lines and returns m expressions. 245 | #' See https://github.com/tidyverse/ggplot2/issues/2864 for discussion. 246 | #' 247 | #' parse(text = c("alpha", "", "gamma")) 248 | #' #> expression(alpha, gamma) 249 | #' 250 | #' parse_safe(text = c("alpha", "", "gamma")) 251 | #' #> expression(alpha, NA, gamma) 252 | #' 253 | #' @noRd 254 | parse_safe <- function(text) { 255 | stopifnot(is.character(text)) 256 | out <- vector("expression", length(text)) 257 | for (i in seq_along(text)) { 258 | expr <- parse(text = text[[i]]) 259 | out[[i]] <- if (length(expr) == 0) NA else expr[[1]] 260 | } 261 | out 262 | } 263 | 264 | #' Exclude data points outside the panel ranges 265 | #' @noRd 266 | exclude_outside <- function(data, panel_scales) { 267 | if ("x.range" %in% names(panel_scales)) { 268 | xr <- panel_scales$x.range 269 | yr <- panel_scales$y.range 270 | ix <- inside(data$x, xr) & inside(data$y, yr) 271 | data <- data[ix,,drop=FALSE] 272 | } else if ("x_range" %in% names(panel_scales)) { 273 | xr <- panel_scales$x_range 274 | yr <- panel_scales$y_range 275 | ix <- inside(data$x, xr) & inside(data$y, yr) 276 | data <- data[ix,,drop=FALSE] 277 | } 278 | data 279 | } 280 | 281 | #' Exclude data points outside the panel ranges 282 | #' @noRd 283 | inside <- function(x, bounds) { 284 | is.infinite(x) | (x <= bounds[2] & x >= bounds[1]) 285 | } 286 | -------------------------------------------------------------------------------- /vignettes/plots.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Creating CMAP-themed plots" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Creating CMAP-themed plots} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r setup, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>", 14 | fig.width = 7, 15 | fig.asp = 400/670, 16 | fig.retina = 4, 17 | fig.align = "center", 18 | dev = "ragg_png" 19 | ) 20 | 21 | library(tidyverse) 22 | library(cmapplot) 23 | unapply_cmap_default_aes() # For testing purposes 24 | ``` 25 | 26 | This vignette summarizes the basic formatting that the cmapplot package can add to a `ggplot()`, including via the functions `theme_cmap()`, `geom_recessions()`, `geom_text_lastonly()`, and `apply_cmap_default_aes()`. The vignette relies on one of the sample CMAP datasets included with the package. 27 | 28 | 29 | ## The initial plot 30 | 31 | Let's start with a basic time series line graph, drawn using ggplot2's default theme (`theme_grey()`). We'll save this as `p` for later, but keep the actual data geom (in this case, `geom_line()`) separate due to how ggplot2 layers geoms. 32 | 33 | ```{r initial, message = FALSE} 34 | # load tidyverse (which includes ggplot2) and cmapplot 35 | library(tidyverse) 36 | library(cmapplot) 37 | 38 | # clean up dataset 39 | df <- transit_ridership %>% 40 | filter(system != "pace_ada") %>% 41 | mutate(system = recode_factor(system, 42 | cta_bus = "CTA bus", 43 | cta_rail = "CTA rail", 44 | metra = "Metra", 45 | pace = "Pace" 46 | )) 47 | 48 | # build plot 49 | p <- ggplot(data = df, 50 | mapping = aes(x = year, y = ridership, color = system)) + 51 | scale_y_continuous(breaks = c(0, 200, 400), 52 | limits = c(-1, 575)) 53 | 54 | p + geom_line() 55 | ``` 56 | 57 | 58 | ## The CMAP theme 59 | 60 | ### Basic implementation 61 | 62 | To add basic CMAP design elements to the plot, add `theme_cmap()` like you would any other ggplot2 theme. 63 | 64 | ```{r basic, message = FALSE} 65 | p + geom_line() + theme_cmap() 66 | ``` 67 | 68 | ### Built-in modifiers 69 | 70 | The CMAP theme function has a variety of easy built-in modifiers. For example, by default the x- and y-axis labels are turned off, but this can be overridden. The help file `?theme_cmap` describes how to make these changes. 71 | 72 | For example, `theme_cmap()` can be used to to add a strong horizontal or vertical line (such as to delineate zero or some other threshold). Note that, on standard-resolution (non-retina) displays, `hline` and `vline` may appear as the same thickness as the plot's gridlines when viewed within R. When exported as a vector file (or raster at high enough resolution) the difference becomes apparent. 73 | 74 | When using `hline` and/or `vline`, it is important to place `theme_cmap()` *before* the primary data geom. This is because the `hline` drawn by `theme_cmap()` is a geom, and ggplot2 layers geoms in the order they are added to the plot. This ordering allows the `CTA rail` line to be drawn on top of the `hline`. 75 | 76 | ```{r mods1, message = FALSE} 77 | p + theme_cmap(ylab = "Annual Ridership (Millions)", 78 | hline = 200, # may not be immediately visible 79 | legend.max.columns = 2) + 80 | geom_line() 81 | ``` 82 | 83 | 84 | `theme_cmap()` also allows users to easily add tick marks on the x- and/or the y-axes, using the argument `axisticks`. Note that here, some additional manipulation of the y-axis scale is necessary for the axis ticks to touch the lowest horizontal gridline (this is to override `ggplot2`'s default behavior, which expands the scale slightly beyond the extent of the data for aesthetic reasons). 85 | ```{r mods2, message = FALSE} 86 | p + geom_line() + 87 | scale_y_continuous(breaks = c(0, 200, 400), 88 | limits = c(-1, 575), 89 | expand = c(0, 0)) + 90 | theme_cmap(ylab = "Annual Ridership (Millions)", 91 | axisticks = "x") 92 | ``` 93 | 94 | ### Overriding plotting defaults 95 | 96 | The package contains a list of default values (`cmapplot_globals$consts`) that control various plotting constants. Most of these impact the function `finalize_plot()`, but a few impact `theme_cmap()`. For a complete description of these constants, see `?cmapplot_globals`. 97 | 98 | The most important at this stage is `margin_panel_r`, which controls the distance between the right side of the plot and the right side of the plotting window. Depending on the length and location of your right-most x axis label, you may need to expand this value beyond it's default. These `consts` can be modified in the `overrides` argument: 99 | 100 | ```{r overrides, message = FALSE} 101 | p + scale_x_continuous(labels = c("1980", "1990", "2000", "2010", "2020", "A very long label")) + 102 | theme_cmap(ylab = "Annual Ridership (Millions)", 103 | hline = 200, 104 | overrides = list(margin_panel_r = 50, # the most likely override in theme_cmap() 105 | lwd_strongline = 3)) + 106 | geom_line() 107 | ``` 108 | 109 | ### Further theme modifications 110 | 111 | In addition, any ggplot2 theme element can also be modified directly in `theme_cmap()` by passing valid arguments from the `ggplot2::theme()` function. In the following example, the addition of `panel.grid.major.y = element_line(color = "light gray")` modifies the default color of a horizontal grid line, changing it from black to light gray. 112 | 113 | ```{r mods3, message = FALSE} 114 | p + theme_cmap(ylab = "Annual Ridership (Millions)", 115 | panel.grid.major.y = element_line(color = "light gray")) + 116 | geom_line() 117 | ``` 118 | 119 | 120 | ## Auxiliary functions 121 | 122 | The cmapplot package also includes two custom geoms that allow the user to easily add common elements used in CMAP plots that would be otherwise difficult to program. 123 | 124 | ### Include US recessions as a backdrop 125 | 126 | The function `geom_recessions()`, allows for the addition of rectangles (and text, if desired) representing US economic recessions. Similar to the `hline` example above, `geom_recessions()` should be added before the plot's primary geom (`geom_line()`) so that lines are drawn on top of recession rectangles. 127 | 128 | `ggplot()` always draws geoms *on top* of base plot elements like gridlines. The default fill and alpha values for `geom_recessions()` are the most transparent way possible to achieve CMAP palette color `#002d49` when drawn on a white background — thus impacting the color of the gridlines as little as possible. 129 | 130 | This function relies on the National Bureau of Economic Research's definitions of recessions. Details on how to update these dates, as well as how to provide your own, can be found in `geom_recessions()` and `update_recessions()`. If the most recent recession has not yet been declared over (as is the case in June 2021), the function will default to displaying this ongoing recession from its beginning through the end of the visualized data. If this is not desired (for example, if the visualization is of a projection far into the future), users can remove this ongoing recession by setting `show_ongoing = FALSE`. 131 | 132 | ```{r recessions, message = FALSE} 133 | q <- ggplot(data = df, 134 | mapping = aes(x = year, y = ridership, color = system)) + 135 | geom_recessions(ymin = 0) + 136 | geom_line() + 137 | theme_cmap(ylab = "Annual Ridership (Millions)") 138 | 139 | q 140 | ``` 141 | 142 | ### Label and highlight final data point 143 | 144 | The function `geom_text_lastonly()` allows the user to label only the final point in a time series. In addition to applying the label, this function can also highlight the final point via an implicit `geom_point()`. Like `geom_text()`, the value of the label can be passed via the `mapping` argument, either in the top-line `ggplot()` or in `geom_text_lastonly()`. 145 | 146 | Due to ggplot2's underlying structure, `geom_text()` labels are clipped by the plot's default extent. Often, the right side of the plot will need to be expanded — or plot clipping turned off — for correct display of these labels. `?geom_text_lastonly` describes a number of methods to account for this. 147 | 148 | ```{r textlast, message = FALSE} 149 | q <- q + geom_text_lastonly(mapping = aes(label = round(ridership, digits = 0)), 150 | add_points = TRUE, 151 | nudge_x = 0.5) + 152 | coord_cartesian(clip = "off") 153 | 154 | q 155 | ``` 156 | 157 | ### Abbreviating years in date axes 158 | 159 | By default, date axes with units in years display the full year for each axis label (e.g., 2000, 2001, 2002, 2003). In some cases, users may want to abbreviate some, but not all, of these axis labels, such as by maintaining the full year for the first label and abbreviating subsequent labels (e.g., 2000, '01, '02, '03). The function `abbr_years` enables users to do so, defaulting to abbreviating all years but the first. 160 | 161 | This function uses syntax similar to those in the `scales::label_*()` family, and can be applied to a plot via the labels parameters of the `ggplot2::scale_x_continuous` or `ggplot2::scale_x_date()` functions (or their y-axis counterparts). Users can specify which years should be abbreviated by either their value (using `full_by_year`) or their position on the axis (using `full_by_pos`). More details are available in `?abbr_years`. 162 | 163 | ```{r abbr_years, message = FALSE} 164 | p + geom_line() + theme_cmap() + 165 | scale_x_continuous(labels = abbr_years(full_by_year = c(2000))) 166 | ``` 167 | 168 | ### Applying CMAP default aesthetics 169 | 170 | Every ggplot2 "geom" has default aesthetics that cannot be modified by a ggplot2 theme (and therefore, cannot be modified by `theme_cmap()`). Notice, for example, that the text drawn in the plot via `geom_text_lastonly()` and `geom_recessions()` still appears in Arial (R's default font), although the legend and axis fonts appear in Whitney. This is automatically corrected in `finalize_plot()`. However, if you'd like to see these impacts at this stage, you can override ggplot2's default aesthetics for line and text geoms with CMAP defaults via `apply_cmap_default_aes()`. 171 | 172 | ```{r aes} 173 | apply_cmap_default_aes() 174 | 175 | q 176 | ``` 177 | 178 | It is important to note that this change is "sticky" for the duration of the session, but you can reverse the change with `unapply_cmap_default_aes()`. This function will revert to whatever the default aesthetics were before cmapplot was loaded. 179 | 180 | 181 | ## Debug mode 182 | 183 | In addition, `theme_cmap()` provides a debug mode in which it draws outlines around the rectangular elements in the plot. This can help the user understand what is being drawn and identify additional modifications that they might want to make via `overrides` or `ggplot2::theme()` arguments: 184 | 185 | ```{r debug, message = FALSE} 186 | ggplot(data = df, 187 | mapping = aes(x = year, y = ridership, color = system)) + 188 | geom_recessions(ymin = 0) + 189 | geom_line() + 190 | theme_cmap(ylab = "Annual Ridership (Millions)", 191 | debug = TRUE) 192 | ``` 193 | -------------------------------------------------------------------------------- /vignettes/finalize.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Finalizing and exporting CMAP-themed plots" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Finalizing and exporting CMAP-themed plots} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r setup, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>", 14 | fig.width = 670/72, 15 | fig.asp = 400/670, 16 | fig.retina = 4, 17 | fig.align = "center", 18 | out.width = "100%", 19 | dev = "ragg_png" 20 | ) 21 | 22 | library(tidyverse) 23 | library(cmapplot) 24 | 25 | # clean up dataset 26 | df <- transit_ridership %>% 27 | filter(system != "pace_ada") %>% 28 | mutate(system = recode_factor(system, 29 | cta_bus = "CTA bus", 30 | cta_rail = "CTA rail", 31 | metra = "Metra", 32 | pace = "Pace" 33 | )) 34 | 35 | # build plot 36 | p <- ggplot(data = df, 37 | mapping = aes(x = year, y = ridership, color = system)) + 38 | geom_recessions(ymin = 0) + 39 | geom_line() + 40 | geom_text_lastonly( 41 | mapping = aes(label = round(ridership, digits = 0)), 42 | add_points = TRUE, 43 | nudge_x = 0.5) + 44 | coord_cartesian(clip = "off") + 45 | theme_cmap() 46 | ``` 47 | 48 | `finalize_plot()` will place a ggplot into a frame defined by CMAP design standards. It will align your title and caption to the left, add a horizontal line on top, and make other adjustments. It can show you the final plot and/or export it as a raster or vector file. This function will not apply CMAP design standards to the plot itself; use `theme_cmap()` for that. 49 | 50 | In this vignette we will use the final version of the line chart developed in `vignette("plots")` to describe standard use of the `finalize_plot()` function. That plot is referenced in the following examples as `p`. The function has numerous additional customization options built in, accepting 16 (or more) arguments. Refer to the object documentation, `?finalize_plot`, for detailed information on all arguments. 51 | 52 | 53 | ## Basic implementation 54 | 55 | ### Finalizing a plot 56 | 57 | After creating a plot and applying `theme_cmap()`, use `finalize_plot()` to complete the implementation of CMAP design standards. You will probably want to set at least the `title` and `caption`, although the function will extract them from the ggplot if they were specified. 58 | 59 | As you are preparing the plot, you will likely want to view it within R. Do this by leaving leaving the default `mode = "plot"` to send the finished plot to the "Plots" tab within RStudio. The plot will show up "actual size" (depending on your screen's resolution) surrounded by a gray canvas. If you want to view the plot in a separate window, you can select the "Zoom" button at the top left of the plot. This may be especially useful for large plots that cannot easily be displayed within RStudio's default plotting window. 60 | 61 | ```{r finalize1, fig.width=11, out.width="100%"} 62 | finalize_plot(plot = p, 63 | title = "Annual unlinked passenger trips (in millions)", 64 | caption = "Source: Chicago Metropolitan Agency for Planning 65 | analysis of Regional Transportation Authority data") 66 | ``` 67 | 68 | ### Exporting a plot 69 | 70 | Once you are happy with your plot, export it using `finalize_plot()` with one or more of the write modes `c("svg", "ps", "pdf", "png", "tiff", "jpeg")` as well as the filename argument. If Communications staff will be modifying your graphic, they will require one of the vector formats (preferably PDF). While many raster formats are available, PNG is *strongly* recommended over the others for the best balance of filesize and visual fidelity. 71 | 72 | You may specify multiple modes simultaneously using the form `mode = c("png", "pdf", "plot")`. That would export the plot as both a PDF and PNG, as well as display it in the plotting window of your R console. 73 | 74 | Some additional notes: 75 | 76 | - If the full file path is not specified, the file will be exported to your current working directory (check with `getwd()`, change with `setwd(dir)`). 77 | 78 | - If a file with the name specified already exists in the specified directory, it will not be overwritten unless the user sets `overwrite = TRUE`. 79 | 80 | - When naming your exports, you may but do not need to include the extension (e.g., `filename = "my_chart"`). The function will automatically add the appropriate extension to each of your exported files (e.g., `"my_chart.pdf"`, `"my_chart.png"`). Leaving off the extension is recommended if you're specifying multiple export modes in the same call. 81 | 82 | 83 | ```{r finalize2-forshow, eval=FALSE, echo=TRUE} 84 | # Finalize and export plot to PNG and PDF 85 | finalize_plot(plot = p, 86 | title = "Annual unlinked passenger trips (in millions)", 87 | caption = "Source: Chicago Metropolitan Agency for Planning 88 | analysis of Regional Transportation Authority data", 89 | mode = c("png", "pdf"), 90 | filename = "finalized_plot") 91 | ``` 92 | 93 | 94 | ```{r finalize2, eval=TRUE, echo=FALSE, message=FALSE} 95 | finalize_plot(plot = p, 96 | title = "Annual unlinked passenger trips (in millions)", 97 | caption = "Source: Chicago Metropolitan Agency for Planning 98 | analysis of Regional Transportation Authority data") 99 | ``` 100 | 101 | 102 | ## Basic customization 103 | 104 | While `finalize_plot()` can be run successfully with very few arguments, the function allows many further customizations, a number of which are described here. 105 | 106 | ### Top-level position adjustments 107 | 108 | In addition to setting the plot's `height` and `width`, `finalize_plot()` has a number of top-level arguments that impact the final layout. For example, you can change the width of the sidebar, alter caption alignment, or change the graphic's background color. Changing the background color can be helpful if you know that the graphic will eventually be placed on an inked page. 109 | 110 | ```{r finalize3, message=FALSE} 111 | # A modified finalized plot 112 | finalize_plot(plot = p, 113 | title = "Annual unlinked passenger trips (in millions)", 114 | caption = "Source: Chicago Metropolitan Agency for Planning 115 | analysis of Regional Transportation Authority data", 116 | sidebar_width = 1.8, 117 | caption_align = 1, 118 | fill_bg = "cornsilk") 119 | ``` 120 | 121 | ### Finalizing a plot with no sidebar 122 | 123 | One special case of a top-level position adjustment is removing the sidebar altogether by setting `sidebar_width = 0`. This is generally preferred when creating graphics to embed in a Word/PDF report (as opposed to a web page or presentation slide). Titles and captions, if specified, are moved to above and below the plot element, respectively. Note that this has an impact on what margins are drawn where. See the section [Many, many margins] for more on this. 124 | 125 | ```{r finalize4, message=FALSE} 126 | # A finalized line graph, with no sidebar 127 | finalize_plot(plot = p, 128 | title = "Annual unlinked passenger trips (in millions)", 129 | caption = "Source: Chicago Metropolitan Agency for Planning 130 | analysis of Regional Transportation Authority data", 131 | sidebar_width = 0) 132 | ``` 133 | 134 | ### Title and caption formatting 135 | 136 | The title and caption blocks take HTML formatting tags, which you can use to manually set line breaks and apply other font formatting. 137 | 138 | ```{r finalize5, message=FALSE} 139 | # A finalized line graph, with text tweaks 140 | finalize_plot(plot = p, 141 | title = "Annual unlinked passenger trips
(in millions)", 142 | caption = 'Source: Chicago 143 | Metropolitan Agency for Planning analysis of 144 | RTA1 data. 145 |

146 | 1 Regional Transportation Authority') 147 | ``` 148 | 149 | ## Advanced customization 150 | 151 | ### Using debug mode to fine-tune your plot 152 | 153 | `finalize_plot()` has a built-in visual debugging tool that helps the user identify the positions of various elements of the finished graphic and how they relate to one another. Setting `debug = TRUE` will draw outlines around every rectangular "grob" in the graphic: 154 | 155 | ```{r finalize6, message=FALSE} 156 | # A debugged finalized plot 157 | finalize_plot(plot = p, 158 | title = "Annual unlinked passenger trips (in millions)", 159 | caption = "Source: Chicago Metropolitan Agency for Planning 160 | analysis of Regional Transportation Authority data", 161 | debug = TRUE) 162 | ``` 163 | 164 | ### Overriding plotting constants 165 | 166 | Default values in `finalize_plot()` attempt to reflect CMAP design standards using constants. The list of preset values can be accessed by calling `get_cmapplot_globals()`, while individual presets can be accessed using `get_cmapplot_global()`. Users can manually adjust these constants by passing a named list to the `overrides` argument. For example, the chart below uses `overrides` to modify the margin below the title (`margin_title_b`), the margin to the left of the sidebar (`margin_sidebar_l`), and the margin above the legend (`margin_legend_t`). 167 | 168 | The [Many, many margins] section of this article describes most of these `consts` visually. To learn more about all possible overrides, see `?set_cmapplot_global`. 169 | 170 | ```{r finalize7, message=FALSE} 171 | # A finalized plot with some formatting overrides 172 | finalize_plot(plot = p, 173 | title = "Annual unlinked passenger trips (in millions)", 174 | caption = "Source: Chicago Metropolitan Agency for Planning 175 | analysis of Regional Transportation Authority data", 176 | debug = TRUE, 177 | overrides = list(margin_title_b = 30, 178 | margin_sidebar_l = 10, 179 | margin_legend_t = 15)) 180 | ``` 181 | 182 | ### Unshifting the legend 183 | 184 | By default, `finalize_plot()` will attempt to extract legend from the ggplot and draw it separately. This allows for justifying the legend to the left edge of the plotting area (`ggplot2` only allows the legend to left-justify to the y axis). If this attempted shift is unwanted or results in an error, the legend alignment of the original plot can be kept by setting `legend_shift = FALSE`. Note that in this situation, the debug boxes indicate that `finalize_plot()` has not separated the legend from the plot. 185 | 186 | ```{r finalize8, message=FALSE} 187 | # A finalized plot with an un-modified legend 188 | finalize_plot(plot = p, 189 | title = "Annual unlinked passenger trips (in millions)", 190 | caption = "Source: Chicago Metropolitan Agency for Planning 191 | analysis of Regional Transportation Authority data", 192 | debug = TRUE, 193 | legend_shift = FALSE) 194 | ``` 195 | 196 | ### Many, many margins 197 | 198 | There is a fairly long list of possible margins that can be customized using the `overrides` argument of `finalize_plot()`. You can read more about the available options for customization in `?set_cmapplot_global`. 199 | 200 | Here, the margins are visualized as they impact a finalized plot that has a sidebar: 201 | 202 | 203 | 204 | Here, the same margins are visualized as they impact a finalized plot with no sidebar: 205 | 206 | 207 | --------------------------------------------------------------------------------