├── .github ├── .gitignore ├── workflows │ ├── R-CMD-check.yaml │ ├── test-coverage.yaml │ └── pkgdown.yaml ├── CODE_OF_CONDUCT.md └── CONTRIBUTING.md ├── LICENSE ├── data └── qr_mod.rda ├── tests ├── testthat │ ├── Rplots.pdf │ ├── test-git_info.R │ ├── files │ │ ├── ggtrack-logo.jpg │ │ ├── ggtrack-logo.png │ │ └── ggtrack-logo.svg │ ├── test-caption.R │ ├── test-position.R │ ├── test-logo.R │ ├── test-theme.R │ ├── test-qr.R │ ├── test-banner.R │ ├── test-download.R │ └── test-grid.R └── testthat.R ├── man ├── figures │ ├── README-hide-1.png │ ├── README-logo-1.png │ ├── ggtrack-logo.jpg │ ├── ggtrack-logo.png │ ├── ggtrack_chart_.png │ ├── README-caption-1.png │ ├── README-example-1.png │ ├── README-pressure-1.png │ ├── ggtrack-logo_sten.png │ ├── README-interactive-1.png │ ├── ggtrack_steg_chart_.png │ ├── ggtrack-favicon.svg │ ├── qr.svg │ └── ggtrack-logo.svg ├── print.tracker.Rd ├── plot.tracker.Rd ├── qr_mod.Rd ├── qr_size.Rd ├── get_git_info.Rd ├── pipe.Rd ├── make_qr.Rd ├── add_theme.Rd ├── add_banner.Rd ├── get_positions.Rd ├── obj_tracker.Rd ├── make_tracker.Rd ├── caption.Rd ├── logo.Rd ├── make_download.Rd ├── qr.Rd └── ggtrack.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 ├── .gitignore ├── .Rbuildignore ├── codecov.yml ├── R ├── utils-pipe.R ├── theme.R ├── calibration.R ├── banner.R ├── git_info.R ├── position.R ├── caption.R ├── logo.R ├── qr.R ├── download.R └── grid.R ├── project.Rproj ├── _pkgdown.yml ├── NAMESPACE ├── LICENSE.md ├── NEWS.md ├── DESCRIPTION ├── inst └── working │ └── plotly.R ├── vignettes ├── qr_calibrate.Rmd └── ggtrack.Rmd ├── README.Rmd └── README.md /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2021 2 | COPYRIGHT HOLDER: ggtrack authors 3 | -------------------------------------------------------------------------------- /data/qr_mod.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/data/qr_mod.rda -------------------------------------------------------------------------------- /tests/testthat/Rplots.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/tests/testthat/Rplots.pdf -------------------------------------------------------------------------------- /man/figures/README-hide-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/man/figures/README-hide-1.png -------------------------------------------------------------------------------- /man/figures/README-logo-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/man/figures/README-logo-1.png -------------------------------------------------------------------------------- /man/figures/ggtrack-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/man/figures/ggtrack-logo.jpg -------------------------------------------------------------------------------- /man/figures/ggtrack-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/man/figures/ggtrack-logo.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /tests/testthat/test-git_info.R: -------------------------------------------------------------------------------- 1 | test_that("multiplication works", { 2 | expect_equal(2 * 2, 4) 3 | }) 4 | -------------------------------------------------------------------------------- /man/figures/ggtrack_chart_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/man/figures/ggtrack_chart_.png -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(ggplot2) 3 | library(ggtrack) 4 | 5 | test_check("ggtrack") 6 | -------------------------------------------------------------------------------- /man/figures/README-caption-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/man/figures/README-caption-1.png -------------------------------------------------------------------------------- /man/figures/README-example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/man/figures/README-example-1.png -------------------------------------------------------------------------------- /man/figures/README-pressure-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/man/figures/README-pressure-1.png -------------------------------------------------------------------------------- /man/figures/ggtrack-logo_sten.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/man/figures/ggtrack-logo_sten.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /man/figures/README-interactive-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/man/figures/README-interactive-1.png -------------------------------------------------------------------------------- /man/figures/ggtrack_steg_chart_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/man/figures/ggtrack_steg_chart_.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /tests/testthat/files/ggtrack-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/tests/testthat/files/ggtrack-logo.jpg -------------------------------------------------------------------------------- /tests/testthat/files/ggtrack-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/tests/testthat/files/ggtrack-logo.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjoh3/ggtrack/HEAD/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .Rdata 4 | .httr-oauth 5 | .DS_Store 6 | ggtrack.Rcheck/ 7 | ggtrack*.tar.gz 8 | ggtrack*.tgz 9 | docs 10 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^project\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^README\.Rmd$ 4 | ^ggtrack\.Rcheck$ 5 | ^ggtrack.*\.tar\.gz$ 6 | ^ggtrack.*\.tgz$ 7 | ^LICENSE\.md$ 8 | ^_pkgdown\.yml$ 9 | ^docs$ 10 | ^pkgdown$ 11 | ^\.github$ 12 | ^codecov\.yml$ 13 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /R/utils-pipe.R: -------------------------------------------------------------------------------- 1 | #' Pipe operator 2 | #' 3 | #' See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details. 4 | #' 5 | #' @name %>% 6 | #' @rdname pipe 7 | #' @keywords internal 8 | #' @export 9 | #' @importFrom magrittr %>% 10 | #' @usage lhs \%>\% rhs 11 | #' @param lhs A value or the magrittr placeholder. 12 | #' @param rhs A function call using the magrittr semantics. 13 | #' @return The result of calling `rhs(lhs)`. 14 | NULL 15 | -------------------------------------------------------------------------------- /man/print.tracker.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/grid.R 3 | \name{print.tracker} 4 | \alias{print.tracker} 5 | \title{Print Tracker} 6 | \usage{ 7 | \method{print}{tracker}(x, ...) 8 | } 9 | \arguments{ 10 | \item{x}{ggtrack tracker object} 11 | 12 | \item{...}{print options} 13 | } 14 | \description{ 15 | Print Tracker 16 | } 17 | \examples{ 18 | \dontrun{ 19 | print(make_tracker()) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /man/plot.tracker.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/grid.R 3 | \name{plot.tracker} 4 | \alias{plot.tracker} 5 | \title{Plot Tracker Object} 6 | \usage{ 7 | \method{plot}{tracker}(x, ...) 8 | } 9 | \arguments{ 10 | \item{x}{ggtrack tracker object} 11 | 12 | \item{...}{plot options} 13 | } 14 | \description{ 15 | Plot Tracker Object 16 | } 17 | \examples{ 18 | \dontrun{ 19 | plot(make_tracker()) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /man/qr_mod.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calibration.R 3 | \docType{data} 4 | \name{qr_mod} 5 | \alias{qr_mod} 6 | \title{Estimate QR Size.} 7 | \format{ 8 | A linear model 9 | } 10 | \usage{ 11 | qr_mod 12 | } 13 | \description{ 14 | A model predicting the required QR size based 15 | on the number of bytes to be encoded. The model is generated in the 16 | package vignette qr_calibrate 17 | } 18 | \keyword{datasets} 19 | -------------------------------------------------------------------------------- /man/qr_size.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calibration.R 3 | \name{qr_size} 4 | \alias{qr_size} 5 | \title{Get QR Size} 6 | \usage{ 7 | qr_size(txt) 8 | } 9 | \arguments{ 10 | \item{txt}{character to be encoded into the QR} 11 | } 12 | \value{ 13 | numeric size of QR in cm. 14 | } 15 | \description{ 16 | Given a character string, calculate the minimum size 17 | of a QR that will reliably scan off a computer monitor 18 | } 19 | -------------------------------------------------------------------------------- /man/get_git_info.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/git_info.R 3 | \name{get_git_info} 4 | \alias{get_git_info} 5 | \title{Get Local Git Information} 6 | \usage{ 7 | get_git_info() 8 | } 9 | \value{ 10 | character 11 | } 12 | \description{ 13 | Uses \link[base]{system} to determine if a git repository exists 14 | and if so returns the upstream remote URL and current commit SHA 15 | } 16 | \examples{ 17 | \dontrun{ 18 | 19 | get_git_info() 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /project.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | LineEndingConversion: Posix 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /man/pipe.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils-pipe.R 3 | \name{\%>\%} 4 | \alias{\%>\%} 5 | \title{Pipe operator} 6 | \usage{ 7 | lhs \%>\% rhs 8 | } 9 | \arguments{ 10 | \item{lhs}{A value or the magrittr placeholder.} 11 | 12 | \item{rhs}{A function call using the magrittr semantics.} 13 | } 14 | \value{ 15 | The result of calling \code{rhs(lhs)}. 16 | } 17 | \description{ 18 | See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details. 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/make_qr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/qr.R 3 | \name{make_qr} 4 | \alias{make_qr} 5 | \title{Make QR} 6 | \usage{ 7 | make_qr(qr_content, color = "black", color_bg = "white") 8 | } 9 | \arguments{ 10 | \item{qr_content}{\code{character} content passed to \link[qrencoder]{qrencode}. A time stamp is automatically added plus the current 11 | git commit where available.} 12 | 13 | \item{color}{character color of QR code} 14 | 15 | \item{color_bg}{character background color of QR code} 16 | } 17 | \value{ 18 | matrix 19 | } 20 | \description{ 21 | Make QR 22 | } 23 | -------------------------------------------------------------------------------- /man/add_theme.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/theme.R 3 | \name{add_theme} 4 | \alias{add_theme} 5 | \title{Modify Tracking Banner Theme} 6 | \usage{ 7 | add_theme(tracker, ...) 8 | } 9 | \arguments{ 10 | \item{tracker}{ggtrack tracker object} 11 | 12 | \item{...}{options passed to \link[ggplot2]{theme} in order to style the tracker banner} 13 | } 14 | \value{ 15 | tracker 16 | } 17 | \description{ 18 | Modify Tracking Banner Theme 19 | } 20 | \examples{ 21 | \dontrun{ 22 | make_tracker() \%>\% add_theme(plot.background = ggplot2::element_rect(fill = "red", size = 0)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/testthat/test-caption.R: -------------------------------------------------------------------------------- 1 | 2 | pos <- get_positions("CLQ", c(10, 50, 40)) 3 | 4 | test_that("we can add a Caption to a plot", { 5 | 6 | # this should only ever occur in ggtrack function 7 | cap <- ggplot(mapping = aes(x = 0:1, y = 1)) %>% 8 | add_caption('add to qr', position = pos) 9 | 10 | expect_type(cap, 'list') 11 | expect_s3_class(cap, 'gg') 12 | expect_s3_class(cap, 'ggplot') 13 | 14 | }) 15 | 16 | test_that("we can add a Caption to a tracker", { 17 | cap <- make_tracker(add_git = FALSE) %>% 18 | add_caption('add to qr') 19 | 20 | expect_type(cap, 'list') 21 | expect_s3_class(cap, 'tracker') 22 | 23 | }) 24 | -------------------------------------------------------------------------------- /man/add_banner.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/banner.R 3 | \name{add_banner} 4 | \alias{add_banner} 5 | \title{Add Tracking Banner to Plot} 6 | \usage{ 7 | add_banner(gg, tracker, height_plot = 7) 8 | } 9 | \arguments{ 10 | \item{gg}{ggplot object to track} 11 | 12 | \item{tracker}{ggtrack tracker object} 13 | 14 | \item{height_plot}{numeric tracker height in cm.} 15 | } 16 | \value{ 17 | tracker 18 | } 19 | \description{ 20 | Add Tracking Banner to Plot 21 | } 22 | \examples{ 23 | 24 | \dontrun{ 25 | 26 | track <- make_tracker() 27 | 28 | ggplot() \%>\% 29 | add_banner(track) 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/testthat/test-position.R: -------------------------------------------------------------------------------- 1 | 2 | test_that("position inputs are correct", { 3 | expect_error(get_positions("ABC", 100)) 4 | expect_error(get_positions("ABC", c(10, 50, 40))) 5 | expect_error(get_positions("CLQ", 100)) 6 | expect_error(get_positions("CLQ", 50)) 7 | }) 8 | 9 | test_that("position data.frame is accurate", { 10 | pos <- get_positions("CLQ", c(10, 50, 40)) 11 | expect_equal(nrow(pos), 3) 12 | expect_equal(sum(pos$position), 100) 13 | expect_true(all(pos$order %in% c('C','L','Q'))) 14 | }) 15 | 16 | test_that('position input can be lower or mixed case',{ 17 | expect_s3_class(get_positions("qLC", c(10, 50, 40)), 'data.frame') 18 | }) 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/testthat/test-logo.R: -------------------------------------------------------------------------------- 1 | 2 | pos <- get_positions("CLQ", c(10, 50, 40)) 3 | 4 | test_that("we can add a Logo to a plot", { 5 | 6 | # this should only ever occur in ggtrack function 7 | logo <- ggplot(mapping = aes(x = 0:1, y = 1)) %>% 8 | add_logo('files/ggtrack-logo.png', position = pos, height_tracker = 2, justification = 1) 9 | 10 | expect_type(logo, 'list') 11 | expect_s3_class(logo, 'gg') 12 | expect_s3_class(logo, 'ggplot') 13 | 14 | }) 15 | 16 | test_that("we can add a Logo to a tracker", { 17 | logo <- make_tracker(add_git = FALSE) %>% 18 | add_logo('files/ggtrack-logo.png', justification = 1) 19 | 20 | expect_type(logo, 'list') 21 | expect_s3_class(logo, 'tracker') 22 | 23 | }) 24 | -------------------------------------------------------------------------------- /R/theme.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #' @title Modify Tracking Banner Theme 7 | #' 8 | #' @param tracker ggtrack tracker object 9 | #' @param ... options passed to \link[ggplot2]{theme} in order to style the tracker banner 10 | #' 11 | #' @return tracker 12 | #' @export 13 | #' 14 | #' @examples 15 | #' \dontrun{ 16 | #' make_tracker() %>% add_theme(plot.background = ggplot2::element_rect(fill = "red", size = 0)) 17 | #' } 18 | add_theme <- function(tracker, ...) { 19 | 20 | height_tracker <- tracker$height 21 | position <- tracker$pos 22 | banner <- tracker$track 23 | git <- tracker$git 24 | ts <- tracker$ts 25 | 26 | tracker$track <- banner + theme(...) 27 | 28 | mtrack <- obj_tracker(tracker, 'theme') 29 | 30 | return(mtrack) 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /tests/testthat/test-theme.R: -------------------------------------------------------------------------------- 1 | 2 | gg <- ggplot(mapping = aes(x = 1:10, y = rnorm(10))) + 3 | geom_bar(stat = 'identity') 4 | 5 | test_that('tracker theme can be modified', { 6 | thm <- ggtrack(gg, 7 | qr_content = 'some text', 8 | logo = 'files/ggtrack-logo.svg', 9 | caption = 'some text', 10 | plot.background = element_rect(fill = "#f0f0f0", size = 0), 11 | add_git = FALSE) 12 | 13 | expect_s3_class(thm, "gtable") 14 | expect_s3_class(thm, "gTree") 15 | expect_s3_class(thm, "grob") 16 | expect_s3_class(thm, "gDesc") 17 | 18 | expect_length(thm$heights, 2) 19 | expect_equal(as.numeric(thm$heights[1]), 7) 20 | expect_equal(as.numeric(thm$heights[2]), 3.3) 21 | 22 | }) 23 | -------------------------------------------------------------------------------- /R/calibration.R: -------------------------------------------------------------------------------- 1 | 2 | #' @title Estimate QR Size. 3 | #' 4 | #' @description A model predicting the required QR size based 5 | #' on the number of bytes to be encoded. The model is generated in the 6 | #' package vignette qr_calibrate 7 | #' 8 | #' @format A linear model 9 | #' 10 | "qr_mod" 11 | 12 | 13 | #' @title Get QR Size 14 | #' 15 | #' @description Given a character string, calculate the minimum size 16 | #' of a QR that will reliably scan off a computer monitor 17 | #' 18 | #' @param txt character to be encoded into the QR 19 | #' 20 | #' @return numeric size of QR in cm. 21 | #' @export 22 | #' 23 | #' @examples 24 | qr_size <- function(txt) { 25 | 26 | bts <- nchar(txt, 'bytes') 27 | pred <- predict(qr_mod, data.frame(bytes = bts)) 28 | pred <- round(as.numeric(pred), 1) 29 | 30 | return(max(1.8, pred)) 31 | } 32 | -------------------------------------------------------------------------------- /R/banner.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #' @title Add Tracking Banner to Plot 4 | #' 5 | #' @param gg ggplot object to track 6 | #' @param tracker ggtrack tracker object 7 | #' @param height_plot numeric tracker height in cm. 8 | #' 9 | #' @return tracker 10 | #' @export 11 | #' 12 | #' @examples 13 | #' 14 | #' \dontrun{ 15 | #' 16 | #' track <- make_tracker() 17 | #' 18 | #' ggplot() %>% 19 | #' add_banner(track) 20 | #' 21 | #' } 22 | add_banner <- function(gg, tracker, height_plot = 7) { 23 | 24 | if (missing(gg)) { 25 | gg <- ggplot() + theme_void() 26 | height_plot <- 0 27 | } 28 | 29 | height_tracker <- tracker$height 30 | 31 | tracker$track <- tracker$track + 32 | theme(plot.margin=unit(c(.5, 0, .3, 0),"cm")) 33 | 34 | gridExtra::grid.arrange(gg, tracker$track, heights = unit(c(height_plot, height_tracker + 1.5 ), "cm")) 35 | 36 | } 37 | 38 | 39 | -------------------------------------------------------------------------------- /man/get_positions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/position.R 3 | \name{get_positions} 4 | \alias{get_positions} 5 | \title{Define Tracker Container Positions} 6 | \usage{ 7 | get_positions(order, positions) 8 | } 9 | \arguments{ 10 | \item{order}{\code{character} of \code{length} 3 defining placement 11 | order for "caption" (C), "logo" (L) and "QR" (Q) code. Must be one of: 12 | \itemize{ 13 | \item{CLQ} 14 | \item{LCQ} 15 | \item{QLC} 16 | \item{CQL} 17 | }} 18 | 19 | \item{positions}{\code{numeric} \code{vector} of \code{length} 3, 20 | defining the horizontal proportion of each container. The 3 numbers 21 | must add to 100.} 22 | } 23 | \value{ 24 | data.frame 25 | } 26 | \description{ 27 | Define Tracker Container Positions 28 | } 29 | \examples{ 30 | 31 | get_positions(order = 'CLQ', positions = c(55, 25, 20)) 32 | 33 | } 34 | -------------------------------------------------------------------------------- /man/obj_tracker.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/grid.R 3 | \name{obj_tracker} 4 | \alias{obj_tracker} 5 | \title{Create Tracker Object} 6 | \usage{ 7 | obj_tracker( 8 | tracker, 9 | contains, 10 | pos = NULL, 11 | height_tracker = NULL, 12 | git = NULL, 13 | timestamp = NULL 14 | ) 15 | } 16 | \arguments{ 17 | \item{tracker}{ggtrack tracker object} 18 | 19 | \item{contains}{character vector tracks what elements have been added to tracker} 20 | 21 | \item{pos}{\code{numeric} \code{vector} of \code{length} 3, 22 | defining the horizontal proportion of each container. The 3 numbers 23 | must add to 100.} 24 | 25 | \item{height_tracker}{numeric tracker height in cm.} 26 | 27 | \item{git}{character git information derived by \link[ggtrack]{get_git_info}} 28 | } 29 | \value{ 30 | tracker 31 | } 32 | \description{ 33 | Create Tracker Object 34 | } 35 | -------------------------------------------------------------------------------- /tests/testthat/test-qr.R: -------------------------------------------------------------------------------- 1 | 2 | pos <- get_positions("CLQ", c(10, 50, 40)) 3 | 4 | test_that("we can create a QR matrix", { 5 | qr <- ggtrack:::make_qr('minimal test of QR') 6 | 7 | expect_type(qr, 'character') 8 | expect_equal(unique(qr[1,]), c('black','white')) 9 | expect_equal(dim(qr), c(25,25)) 10 | 11 | }) 12 | 13 | 14 | test_that("we can add a QR to a plot", { 15 | 16 | # this should only ever occur in ggtrack function 17 | qr <- ggplot(mapping = aes(x = 0:1, y = 1)) %>% 18 | add_qr('add to qr', justification = 1, height_tracker = 2, position = pos) 19 | 20 | expect_type(qr, 'list') 21 | expect_s3_class(qr, 'gg') 22 | expect_s3_class(qr, 'ggplot') 23 | 24 | }) 25 | 26 | 27 | 28 | 29 | test_that("we can add a QR to a tracker", { 30 | qr <- make_tracker(add_git = FALSE) %>% 31 | add_qr('add to qr', justification = 1) 32 | 33 | expect_type(qr, 'list') 34 | expect_s3_class(qr, 'tracker') 35 | 36 | }) 37 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://mrjoh3.github.io/ggtrack 2 | 3 | home: 4 | title: ggtrack, add a tracking banner to your plots. 5 | description: The `ggtrack` package will embed enough metadata in a plot image to later identify the source and the exact time it was produced. 6 | 7 | navbar: 8 | type: default 9 | structure: 10 | left: [home, intro, articles, reference, news] 11 | right: [github] 12 | 13 | repo: 14 | url: 15 | home: https://github.com/mrjoh3/ggtrack/ 16 | source: https://github.com/mrjoh3/ggtrack/blob/master/ 17 | issue: https://github.com/mrjoh3/ggtrack/issues/ 18 | user: https://github.com/mrjoh3/ 19 | 20 | development: 21 | mode: auto 22 | 23 | template: 24 | params: 25 | bootswatch: flatly 26 | opengraph: 27 | image: 28 | src: man/figures/ggtrack-logo.png 29 | alt: "Use ggrack to add metadata to your ggplot" 30 | twitter: 31 | creator: "@mrjoh3" 32 | card: summary 33 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag. 2 | # https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request: 9 | branches: 10 | - main 11 | - master 12 | 13 | name: R-CMD-check 14 | 15 | jobs: 16 | R-CMD-check: 17 | runs-on: macOS-latest 18 | env: 19 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: r-lib/actions/setup-r@v1 23 | - name: Install dependencies 24 | run: | 25 | install.packages(c("remotes", "rcmdcheck")) 26 | remotes::install_deps(dependencies = TRUE) 27 | shell: Rscript {0} 28 | - name: Check 29 | run: | 30 | options(crayon.enabled = TRUE) 31 | rcmdcheck::rcmdcheck(args = "--no-manual", error_on = "error") 32 | shell: Rscript {0} 33 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(add_caption,gg) 4 | S3method(add_caption,tracker) 5 | S3method(add_logo,gg) 6 | S3method(add_logo,tracker) 7 | S3method(add_qr,gg) 8 | S3method(add_qr,tracker) 9 | S3method(plot,tracker) 10 | S3method(print,tracker) 11 | export("%>%") 12 | export(add_banner) 13 | export(add_caption) 14 | export(add_logo) 15 | export(add_qr) 16 | export(add_theme) 17 | export(caption) 18 | export(get_git_info) 19 | export(get_positions) 20 | export(ggtrack) 21 | export(make_download) 22 | export(make_tracker) 23 | export(qr_size) 24 | import(RCurl) 25 | import(ggplot2) 26 | import(grid) 27 | import(gridExtra) 28 | import(jpeg) 29 | import(png) 30 | import(qrencoder) 31 | import(rsvg) 32 | importFrom(ggplot2,annotation_custom) 33 | importFrom(glue,glue) 34 | importFrom(grid,rasterGrob) 35 | importFrom(gridtext,richtext_grob) 36 | importFrom(htmltools,a) 37 | importFrom(htmltools,css) 38 | importFrom(magick,image_read_svg) 39 | importFrom(magrittr,"%>%") 40 | importFrom(stegasaur,lsb_encode) 41 | importFrom(tools,file_ext) 42 | -------------------------------------------------------------------------------- /man/make_tracker.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/grid.R 3 | \name{make_tracker} 4 | \alias{make_tracker} 5 | \title{Define Tracker Base} 6 | \usage{ 7 | make_tracker( 8 | order = "CLQ", 9 | positions = c(55, 25, 20), 10 | height_tracker = 1.8, 11 | add_git = TRUE, 12 | add_ts = TRUE 13 | ) 14 | } 15 | \arguments{ 16 | \item{order}{\code{character} of \code{length} 3 defining placement 17 | order for "caption" (C), "logo" (L) and "QR" (Q) code. Must be one of: 18 | \itemize{ 19 | \item{CLQ} 20 | \item{LCQ} 21 | \item{QLC} 22 | \item{CQL} 23 | }} 24 | 25 | \item{positions}{\code{numeric} \code{vector} of \code{length} 3, 26 | defining the horizontal proportion of each container. The 3 numbers 27 | must add to 100.} 28 | 29 | \item{height_tracker}{numeric tracker height in cm.} 30 | 31 | \item{add_git}{logical include git info in encoding} 32 | 33 | \item{add_ts}{logical include timestamp info in encoding} 34 | } 35 | \value{ 36 | tracker 37 | } 38 | \description{ 39 | Define Tracker Base 40 | } 41 | \examples{ 42 | \dontrun{ 43 | make_tracker() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2021 ggtrack authors 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 | -------------------------------------------------------------------------------- /tests/testthat/test-banner.R: -------------------------------------------------------------------------------- 1 | 2 | track <- make_tracker(add_git = FALSE) %>% 3 | add_logo('files/ggtrack-logo.svg', 1) %>% 4 | add_qr('for_QR', justification = 1) %>% 5 | add_caption('for_Caption') %>% 6 | add_theme(plot.background = element_rect(fill = "#ff9955", size = 0)) 7 | 8 | gg <- ggplot(mapping = aes(x = 1:10, y = rnorm(10))) + 9 | geom_bar(stat = 'identity') + 10 | theme_minimal() 11 | 12 | 13 | test_that("we can add a tracking banner", { 14 | tbn <- gg %>% 15 | add_banner(track) 16 | 17 | expect_s3_class(tbn, "gtable") 18 | expect_s3_class(tbn, "gTree") 19 | expect_s3_class(tbn, "grob") 20 | expect_s3_class(tbn, "gDesc") 21 | 22 | expect_length(tbn$heights, 2) 23 | expect_equal(as.numeric(tbn$heights[1]), 7) 24 | expect_equal(as.numeric(tbn$heights[2]), 3.3) 25 | }) 26 | 27 | 28 | test_that("we can add a tracking banner without a plot", { 29 | tbn <- add_banner(tracker = track) 30 | 31 | expect_s3_class(tbn, "gtable") 32 | expect_s3_class(tbn, "gTree") 33 | expect_s3_class(tbn, "grob") 34 | expect_s3_class(tbn, "gDesc") 35 | 36 | expect_length(tbn$heights, 2) 37 | expect_equal(as.numeric(tbn$heights[1]), 0) 38 | expect_equal(as.numeric(tbn$heights[2]), 3.3) 39 | }) 40 | -------------------------------------------------------------------------------- /R/git_info.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #' @title Get Local Git Information 5 | #' @description Uses \link[base]{system} to determine if a git repository exists 6 | #' and if so returns the upstream remote URL and current commit SHA 7 | #' 8 | #' @return character 9 | #' @export 10 | #' 11 | #' @examples 12 | #' \dontrun{ 13 | #' 14 | #' get_git_info() 15 | #' 16 | #' } 17 | get_git_info <- function() { 18 | 19 | g <- Sys.which('git') 20 | 21 | if (as.character(g) != '') { 22 | # we might have git but not be in a git repo 23 | in_git_repo <- invisible(system("git rev-parse --git-dir", 24 | ignore.stderr = TRUE, 25 | ignore.stdout = TRUE)) 26 | 27 | if (in_git_repo == 0) { 28 | 29 | git_sha <- strtrim(system("git rev-parse HEAD", intern=TRUE), 8) 30 | git_url <- system("git config --get remote.origin.url", intern=TRUE) 31 | git_url <- gsub(':', '/', git_url) # change remote to web URL 32 | git_url <- gsub('git@', 'http://', git_url) 33 | git_url <- gsub('\\.git', '', git_url) 34 | 35 | git <- paste0(git_url, ' ', git_sha) 36 | 37 | } else { 38 | git <- "" 39 | } 40 | } 41 | 42 | return(git) 43 | 44 | } 45 | -------------------------------------------------------------------------------- /R/position.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #' @title Define Tracker Container Positions 4 | #' 5 | #' @param order \code{character} of \code{length} 3 defining placement 6 | #' order for "caption" (C), "logo" (L) and "QR" (Q) code. Must be one of: 7 | #' \itemize{ 8 | #' \item{CLQ} 9 | #' \item{LCQ} 10 | #' \item{QLC} 11 | #' \item{CQL} 12 | #' } 13 | #' @param positions \code{numeric} \code{vector} of \code{length} 3, 14 | #' defining the horizontal proportion of each container. The 3 numbers 15 | #' must add to 100. 16 | #' 17 | #' @return data.frame 18 | #' @export 19 | #' 20 | #' @examples 21 | #' 22 | #' get_positions(order = 'CLQ', positions = c(55, 25, 20)) 23 | #' 24 | get_positions <- function(order, positions) { 25 | 26 | order <- toupper(order) 27 | stopifnot(order %in% c('CLQ','LCQ','QLC','CQL')) 28 | stopifnot(sum(positions) == 100) 29 | stopifnot(length(positions) == 3) 30 | 31 | pos <- data.frame(order = unlist(strsplit(order, split = "")), 32 | position = positions, 33 | xmin = 0, 34 | xmax = 1, 35 | stringsAsFactors = FALSE) 36 | 37 | pos[1, 'xmax'] <- pos[2, 'xmin'] <- (positions[1] / 100) 38 | pos[2, 'xmax'] <- pos[3, 'xmin'] <- ((positions[1] + positions[2]) / 100) 39 | 40 | return(pos) 41 | 42 | } 43 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # ggtrack 0.1.3 2 | 3 | * QR now resizes automatically based on bytes to encode. This ensures QR code readability 4 | 5 | # ggtrack 0.1.2 6 | 7 | * `add_banner` now works without a `ggplot` input. This is useful where you just want to create the banner. Like this the banner could be placed above or below an interactive chart or map, or any other content. 8 | 9 | # ggtrack 0.1.1 10 | 11 | * move git and timestamp options higher up in API and make them optional 12 | * using `stegasaur` package add encoded text or R object to saved PNG (https://github.com/richfitz/stegasaur) 13 | 14 | # ggtrack 0.1.0 15 | 16 | * complete minimum tests 17 | * complete minimum documentation 18 | * add `codecov` badge 19 | * update lifecycle badge to stable 20 | * R-CMD check passing 21 | 22 | # ggtrack 0.0.3 23 | 24 | * add additional API for incremental banner build 25 | * ability to add banner to multiple plots 26 | * generic print and plot functions for `tracker` object 27 | 28 | # ggtrack 0.0.2 29 | 30 | * add logo caption and QR code to tracking banner 31 | * rearrange order of banner elements 32 | * style banner area 33 | * add JPEG, PNG or SVG logos from URL or local file 34 | * add button or link to download plot with tracking banner 35 | * documentation in `pkgdown` site 36 | * fancy SGV hex logo 37 | 38 | # ggtrack 0.0.1 39 | 40 | * Currently a work in progress 41 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: ggtrack 2 | Title: Build tracking banners using QR codes for addition to plots and reports 3 | Version: 0.1.3 4 | Authors@R: 5 | person(given = "Matt", 6 | family = "Johnson", 7 | role = c("aut", "cre"), 8 | email = "mrjoh3@gmail.com") 9 | Description: Sometimes in a workplace it is hard to know exactly where or when a 10 | specific chart has been produced. This is especially true when someone has copied 11 | a chart out of a report to use somewhere else. The `ggtrack` package aims to 12 | solve this problem by embedding enough metadata in the charts image to identify 13 | the source and the exact time it was produced. 14 | License: MIT + file LICENSE 15 | Encoding: UTF-8 16 | LazyData: true 17 | Date: 2021-07-04 18 | Roxygen: list(markdown = TRUE) 19 | RoxygenNote: 7.1.1 20 | BugReports: https://github.com/mrjoh3/ggtrack/issues 21 | Suggests: 22 | covr, 23 | testthat (>= 3.0.0) 24 | Config/testthat/edition: 3 25 | Remotes: 26 | hrbrmstr/qrencoder, 27 | richfitz/stegasaur 28 | Imports: 29 | grid, 30 | ggplot2, 31 | RCurl, 32 | gridExtra, 33 | jpeg, 34 | png, 35 | rsvg, 36 | magick, 37 | gridtext, 38 | qrencoder, 39 | magrittr, 40 | glue, 41 | htmltools, 42 | stegasaur 43 | URL: https://mrjoh3.github.io/ggtrack 44 | Depends: 45 | R (>= 2.10) 46 | -------------------------------------------------------------------------------- /inst/working/plotly.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | p1 = plotly_empty(x = 1, y = 1) %>% 4 | layout( 5 | images = list(list( 6 | source = raster2uri(q, interpolate = FALSE), 7 | xref = "paper", 8 | yref = "paper", 9 | x = .5, y = .5, 10 | sizex = 1, sizey = 1, 11 | xanchor = "left", yanchor = "bottom" 12 | )) 13 | ) %>% layout(margin=list(l=10, r=10, b=0, t=0), 14 | xaxis=list(showticklabels=FALSE, ticks=""), 15 | yaxis=list(showticklabels=FALSE, ticks="")) 16 | 17 | 18 | 19 | p2 <- plotly_empty(x = 1, y = 1) %>% 20 | layout( 21 | images = list(list( 22 | source = raster2uri(logo_imported), 23 | xref = "paper", 24 | yref = "paper", 25 | x = .5, y = .5, 26 | sizex = 1, sizey = 1, 27 | xanchor = "left", yanchor = "bottom" 28 | )) 29 | ) %>% layout(margin=list(l=10, r=10, b=0, t=0), 30 | xaxis=list(showticklabels=FALSE, ticks=""), 31 | yaxis=list(showticklabels=FALSE, ticks="")) 32 | 33 | p3 <- plotly_empty(x = 1, y = 1) %>% 34 | add_text(text = 'some text here', x = .5, y = .5) %>% 35 | layout(margin=list(l=10, r=10, b=0, t=0), 36 | xaxis=list(showticklabels=FALSE, ticks=""), 37 | yaxis=list(showticklabels=FALSE, ticks="")) 38 | 39 | 40 | bn <- subplot(p3, p2, p1, nrows = 1, widths = c(.6, .2, .2)) 41 | 42 | subplot(ggplotly(tracker), bn, nrows = 2, heights = c(.7,.3)) 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /man/caption.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/caption.R 3 | \name{caption} 4 | \alias{caption} 5 | \alias{add_caption} 6 | \alias{add_caption.gg} 7 | \alias{add_caption.tracker} 8 | \title{Add Caption to Tracker} 9 | \usage{ 10 | caption(tracker, caption, position, ...) 11 | 12 | add_caption(tracker, ...) 13 | 14 | \method{add_caption}{gg}(tracker, caption, position, ...) 15 | 16 | \method{add_caption}{tracker}(tracker, caption, ...) 17 | } 18 | \arguments{ 19 | \item{tracker}{ggtrack tracker object} 20 | 21 | \item{caption}{\code{character} or \code{grob} to add to footer. Text can be 22 | \code{html} or \code{md} and is passed directly to \link[gridtext]{richtext_grob}} 23 | 24 | \item{position}{data.frame generated by get \link[ggtrack]{get_positions}} 25 | 26 | \item{...}{additional options passed through to \link[gridtext]{richtext_grob}} 27 | } 28 | \value{ 29 | tracker 30 | } 31 | \description{ 32 | Add Caption to Tracker 33 | } 34 | \examples{ 35 | \dontrun{ 36 | make_tracker() \%>\% add_caption('your caption') 37 | } 38 | } 39 | \seealso{ 40 | Other gg: 41 | \code{\link{logo}()}, 42 | \code{\link{qr}()} 43 | 44 | Other tracker: 45 | \code{\link{logo}()}, 46 | \code{\link{qr}()} 47 | 48 | Other gg: 49 | \code{\link{logo}()}, 50 | \code{\link{qr}()} 51 | 52 | Other tracker: 53 | \code{\link{logo}()}, 54 | \code{\link{qr}()} 55 | } 56 | \concept{add_caption} 57 | \concept{gg} 58 | \concept{tracker} 59 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who 4 | contribute through reporting issues, posting feature requests, updating documentation, 5 | submitting pull requests or patches, and other activities. 6 | 7 | We are committed to making participation in this project a harassment-free experience for 8 | everyone, regardless of level of experience, gender, gender identity and expression, 9 | sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 10 | 11 | Examples of unacceptable behavior by participants include the use of sexual language or 12 | imagery, derogatory comments or personal attacks, trolling, public or private harassment, 13 | insults, or other unprofessional conduct. 14 | 15 | Project maintainers have the right and responsibility to remove, edit, or reject comments, 16 | commits, code, wiki edits, issues, and other contributions that are not aligned to this 17 | Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed 18 | from the project team. 19 | 20 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by 21 | opening an issue or contacting one or more of the project maintainers. 22 | 23 | This Code of Conduct is adapted from the Contributor Covenant 24 | (http://contributor-covenant.org), version 1.0.0, available at 25 | http://contributor-covenant.org/version/1/0/0/ 26 | -------------------------------------------------------------------------------- /vignettes/qr_calibrate.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "QR Size Calibration" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{QR Size Calibration} 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 | ) 15 | 16 | library(dplyr) 17 | library(ggplot2) 18 | library(ggtrack) 19 | ``` 20 | 21 | 22 | 23 | 24 | 25 | ```{r} 26 | df <- tribble(~ bytes, ~ cm, 27 | 47 , 1.8, 28 | 109 , 2.0, 29 | 218 , 2.6, 30 | 327 , 3.1, 31 | 436 , 3.3, 32 | 545 , 3.5, 33 | 654 , 4.0) 34 | ``` 35 | 36 | 37 | ```{r, fig.width=8, fig.height=5, out.width="100%", fig.align='center', fig.retina=2} 38 | gg <- ggplot(df, aes(x = bytes, y = cm)) + 39 | geom_point() + 40 | geom_smooth(method = 'lm') + 41 | labs(x = 'bytes encoded into QR', 42 | y = 'height of QR in cm', 43 | title = 'QR size calibration') + 44 | theme_minimal() 45 | 46 | ggtrack(gg, 47 | caption = 'calibration based on QR code scanned
from computer monitor', 48 | qr_content = 'https://mrjoh3.github.io/ggtrack/articles/qr_calibrate.html', 49 | logo = '../man/figures/ggtrack-logo.svg') 50 | ``` 51 | 52 | 53 | 54 | ```{r} 55 | 56 | qr_mod <- lm(cm ~ bytes, data = df) 57 | 58 | summary(qr_mod) 59 | ``` 60 | 61 | 62 | ```{r, eval=FALSE} 63 | usethis::use_data(qr_mod, internal = FALSE, overwrite = TRUE) 64 | ``` 65 | 66 | -------------------------------------------------------------------------------- /man/logo.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/logo.R 3 | \name{logo} 4 | \alias{logo} 5 | \alias{add_logo} 6 | \alias{add_logo.gg} 7 | \alias{add_logo.tracker} 8 | \title{Add Logo to Tracker} 9 | \usage{ 10 | logo(tracker, logo, height_tracker, position, justification, ...) 11 | 12 | add_logo(tracker, ...) 13 | 14 | \method{add_logo}{gg}(tracker, logo, height_tracker, position, justification, ...) 15 | 16 | \method{add_logo}{tracker}(tracker, logo, justification, ...) 17 | } 18 | \arguments{ 19 | \item{tracker}{ggtrack tracker object} 20 | 21 | \item{logo}{character file path or image url. Image extension can be any of: 22 | \itemize{ 23 | \item{jpeg} 24 | \item{jpg} 25 | \item{png} 26 | \item{svg} 27 | }} 28 | 29 | \item{height_tracker}{numeric tracker height in cm.} 30 | 31 | \item{position}{data.frame generated by get \link[ggtrack]{get_positions}} 32 | 33 | \item{justification}{numeric between 0 and 1 passed to \link[grid]{rasterGrob}.} 34 | 35 | \item{...}{additional options passed to \link[grid]{rasterGrob}} 36 | } 37 | \value{ 38 | tracker 39 | } 40 | \description{ 41 | Add Logo to Tracker 42 | } 43 | \examples{ 44 | \dontrun{ 45 | make_tracker() \%>\% add_logo('https://www.r-project.org/logo/Rlogo.png', justification = 1) 46 | } 47 | } 48 | \seealso{ 49 | Other gg: 50 | \code{\link{caption}()}, 51 | \code{\link{qr}()} 52 | 53 | Other tracker: 54 | \code{\link{caption}()}, 55 | \code{\link{qr}()} 56 | 57 | Other gg: 58 | \code{\link{caption}()}, 59 | \code{\link{qr}()} 60 | 61 | Other tracker: 62 | \code{\link{caption}()}, 63 | \code{\link{qr}()} 64 | } 65 | \concept{add_logo} 66 | \concept{gg} 67 | \concept{tracker} 68 | -------------------------------------------------------------------------------- /man/make_download.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/download.R 3 | \name{make_download} 4 | \alias{make_download} 5 | \title{Tracker Download Link} 6 | \usage{ 7 | make_download( 8 | tracker, 9 | save_file, 10 | download_file, 11 | ext = "png", 12 | link_text = "download", 13 | type = c("link", "button"), 14 | height = 13, 15 | height_units = c("cm", "mm", "in"), 16 | button_style = htmltools::css(background.color = "DodgerBlue", border = "none", color 17 | = "white", padding = "12px 30px", cursor = "pointer", font.size = "16px", width = 18 | "150px"), 19 | date = format(Sys.time(), "\%Y\%m\%d-\%H\%M\%S"), 20 | render = TRUE, 21 | stenograph = FALSE 22 | ) 23 | } 24 | \arguments{ 25 | \item{tracker}{tracker object to be saved} 26 | 27 | \item{save_file}{character vector, where length > 1, path is generated using \link[base]{file.path}} 28 | 29 | \item{download_file}{character vector, where length > 1, path is generated using \link[base]{file.path}} 30 | 31 | \item{ext}{character device type passed to \link[ggplot2]{ggsave}} 32 | 33 | \item{link_text}{character text to appear on the page} 34 | 35 | \item{type}{character must be one of "link" or "button".} 36 | 37 | \item{height}{integer height value passed to \link[ggplot2]{ggsave}} 38 | 39 | \item{height_units}{character units value passed to \link[ggplot2]{ggsave}} 40 | 41 | \item{button_style}{css object generated by \link[htmltools]{css} or a css character} 42 | 43 | \item{render}{Boolean, render in-place or save for later} 44 | 45 | \item{stenograph}{use \link[stegasaur]{lsb_encode} to enbed content within the png output.} 46 | 47 | \item{stenograph_text}{character text or R object to encode using \link[stegasaur]{lsb_encode}, must be less than 2^16 bytes} 48 | } 49 | \value{ 50 | html of the link object 51 | } 52 | \description{ 53 | Save tracker object to file and render link or button for user download 54 | } 55 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | - master 6 | pull_request: 7 | branches: 8 | - main 9 | - master 10 | 11 | name: test-coverage 12 | 13 | jobs: 14 | test-coverage: 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - uses: r-lib/actions/setup-r@v1 22 | 23 | - uses: r-lib/actions/setup-pandoc@v1 24 | 25 | - name: Install curl 26 | run: sudo apt-get install libcurl4-openssl-dev curl 27 | 28 | - name: Install pkgdown dep 29 | run: sudo apt-get install libharfbuzz-dev libfribidi-dev 30 | 31 | - name: Install libmagick png and jpeg 32 | run: sudo apt-get install libmagick++-dev libpng-dev libjpeg-dev 33 | 34 | - name: Install gdal 35 | run: sudo apt-get install gdal-bin proj-bin libgdal-dev libproj-dev 36 | 37 | - name: Query dependencies 38 | run: | 39 | install.packages('remotes') 40 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 41 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 42 | shell: Rscript {0} 43 | 44 | - name: Restore R package cache 45 | uses: actions/cache@v2 46 | with: 47 | path: ${{ env.R_LIBS_USER }} 48 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 49 | restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- 50 | 51 | - name: Install dependencies 52 | run: | 53 | install.packages(c("remotes")) 54 | remotes::install_deps(dependencies = TRUE) 55 | remotes::install_cran("covr") 56 | shell: Rscript {0} 57 | 58 | - name: Test coverage 59 | run: covr::codecov(token = "${{ secrets.CODECOV_TOKEN }}") 60 | shell: Rscript {0} 61 | -------------------------------------------------------------------------------- /man/qr.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/qr.R 3 | \name{qr} 4 | \alias{qr} 5 | \alias{add_qr} 6 | \alias{add_qr.gg} 7 | \alias{add_qr.tracker} 8 | \title{Add QR Code to Tracker} 9 | \usage{ 10 | qr( 11 | tracker, 12 | qr_content, 13 | color = "black", 14 | color_bg = "white", 15 | height_tracker, 16 | position, 17 | justification, 18 | ... 19 | ) 20 | 21 | add_qr(tracker, ...) 22 | 23 | \method{add_qr}{gg}( 24 | tracker, 25 | qr_content, 26 | color = "black", 27 | color_bg = "white", 28 | height_tracker, 29 | position, 30 | justification, 31 | ... 32 | ) 33 | 34 | \method{add_qr}{tracker}( 35 | tracker, 36 | qr_content, 37 | color = "black", 38 | color_bg = "white", 39 | justification, 40 | ... 41 | ) 42 | } 43 | \arguments{ 44 | \item{tracker}{ggtrack tracker object} 45 | 46 | \item{qr_content}{\code{character} content passed to \link[qrencoder]{qrencode}. A time stamp is automatically added plus the current 47 | git commit where available.} 48 | 49 | \item{color}{character color of QR code} 50 | 51 | \item{color_bg}{character background color of QR code} 52 | 53 | \item{height_tracker}{numeric tracker height in cm.} 54 | 55 | \item{position}{data.frame generated by get \link[ggtrack]{get_positions}} 56 | 57 | \item{justification}{numeric between 0 and 1 passed to \link[grid]{rasterGrob}.} 58 | 59 | \item{...}{additional options passed to \link[grid]{rasterGrob}} 60 | } 61 | \value{ 62 | tracker 63 | } 64 | \description{ 65 | Add QR Code to Tracker 66 | } 67 | \examples{ 68 | \dontrun{ 69 | make_tracker() \%>\% add_qr('Report ID: 2c9075a5-4d7e-47a5-8616-55dd88af3dc5', justification = 1) 70 | } 71 | } 72 | \seealso{ 73 | Other gg: 74 | \code{\link{caption}()}, 75 | \code{\link{logo}()} 76 | 77 | Other tracker: 78 | \code{\link{caption}()}, 79 | \code{\link{logo}()} 80 | 81 | Other gg: 82 | \code{\link{caption}()}, 83 | \code{\link{logo}()} 84 | 85 | Other tracker: 86 | \code{\link{caption}()}, 87 | \code{\link{logo}()} 88 | } 89 | \concept{add_qr} 90 | \concept{gg} 91 | \concept{tracker} 92 | -------------------------------------------------------------------------------- /R/caption.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #' @title Add Caption to Tracker 4 | #' 5 | #' @param tracker ggtrack tracker object 6 | #' @param caption \code{character} or \code{grob} to add to footer. Text can be 7 | #' \code{html} or \code{md} and is passed directly to \link[gridtext]{richtext_grob} 8 | #' @param position data.frame generated by get \link[ggtrack]{get_positions} 9 | #' @param ... additional options passed through to \link[gridtext]{richtext_grob} 10 | #' 11 | #' @importFrom gridtext richtext_grob 12 | #' @importFrom ggplot2 annotation_custom 13 | #' 14 | #' @return tracker 15 | #' @export 16 | #' 17 | #' @examples 18 | #' \dontrun{ 19 | #' make_tracker() %>% add_caption('your caption') 20 | #' } 21 | caption <- function(tracker, caption, position, ...) { 22 | 23 | if (is.character(caption)) { 24 | tg <- richtext_grob(caption, x = 0, hjust = 0, name = 'caption', ...) 25 | } else if ('grob' %in% class(caption)) { 26 | tg <- caption 27 | } else { 28 | stop('A caption needs to be either text or a "grob"') 29 | } 30 | 31 | # define position 32 | p <- as.list(position[position$order == 'C', ]) 33 | 34 | tracker + 35 | annotation_custom(tg, xmin = p$xmin, xmax = p$xmax) 36 | 37 | } 38 | 39 | 40 | 41 | #' @rdname caption 42 | #' @family add_caption 43 | #' @family gg 44 | #' @family tracker 45 | #' @export 46 | add_caption <- function (tracker, ...) { 47 | UseMethod("add_caption", tracker) 48 | } 49 | 50 | #' @rdname caption 51 | #' @family add_caption 52 | #' @family gg 53 | #' @export 54 | add_caption.gg <- function(tracker, caption, position, ...) { 55 | 56 | caption(tracker, caption, position, ...) 57 | 58 | } 59 | 60 | #' @rdname caption 61 | #' @family add_caption 62 | #' @family tracker 63 | #' @export 64 | add_caption.tracker <- function(tracker, caption, ...) { 65 | 66 | height_tracker <- tracker$height 67 | position <- tracker$pos 68 | banner <- tracker$track 69 | git <- tracker$git 70 | ts <- tracker$ts 71 | 72 | tracker$track <- caption(banner, caption, position, ...) 73 | 74 | mtrack <- obj_tracker(tracker, 'caption') 75 | 76 | return(mtrack) 77 | 78 | } 79 | 80 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | - master 6 | 7 | name: pkgdown 8 | 9 | jobs: 10 | pkgdown: 11 | runs-on: ubuntu-latest 12 | env: 13 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - uses: r-lib/actions/setup-r@v1 18 | 19 | - uses: r-lib/actions/setup-pandoc@v1 20 | 21 | - name: Install curl 22 | run: sudo apt-get install libcurl4-openssl-dev curl 23 | 24 | - name: Install pkgdown dep 25 | run: sudo apt-get install libharfbuzz-dev libfribidi-dev 26 | 27 | - name: Install libmagick 28 | run: sudo apt-get install libmagick++-dev 29 | 30 | - name: Install gdal 31 | run: sudo apt-get install gdal-bin proj-bin libgdal-dev libproj-dev 32 | 33 | - name: Query dependencies 34 | run: | 35 | install.packages('remotes') 36 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 37 | writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") 38 | shell: Rscript {0} 39 | 40 | - name: Restore R package cache 41 | uses: actions/cache@v2 42 | with: 43 | path: ${{ env.R_LIBS_USER }} 44 | key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} 45 | restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- 46 | 47 | - name: Install dependencies 48 | run: | 49 | remotes::install_deps(dependencies = TRUE) 50 | install.packages("pkgdown") 51 | install.packages("ggthemes") 52 | remotes::install_github("ropensci/rWBclimate") 53 | remotes::install_github("brianwdavis/quadrangle") 54 | shell: Rscript {0} 55 | 56 | - name: Install package 57 | run: R CMD INSTALL . 58 | 59 | - name: Deploy package 60 | run: | 61 | git config --local user.email "actions@github.com" 62 | git config --local user.name "GitHub Actions" 63 | Rscript -e 'pkgdown::deploy_to_branch(new_process = FALSE)' 64 | -------------------------------------------------------------------------------- /tests/testthat/test-download.R: -------------------------------------------------------------------------------- 1 | 2 | gg <- ggplot(mapping = aes(x = 1:10, y = rnorm(10))) + 3 | geom_bar(stat = 'identity') 4 | 5 | test_that("Download generates a file and link with basic API", { 6 | 7 | api_basic <- ggtrack(gg, 8 | qr_content = 'text content here') 9 | 10 | dl_basic <- make_download(api_basic, 11 | save_file = c('files', 'basic_ggtrack'), 12 | download_file = c('files', 'basic_ggtrack'), 13 | type = 'button', 14 | date = 'delete', 15 | render = FALSE) 16 | 17 | expect_true(file.exists('files/basic_ggtrack_delete.png')) 18 | expect_type(dl_basic, 'character') 19 | 20 | # does the link contain the correct URL and type 21 | expect_equal(as.character(strcapture('href="(.*?)"', dl_basic, proto = 'c')), 22 | "files/basic_ggtrack_delete.png") 23 | expect_equal(as.character(strcapture('button type="(.*?)"', dl_basic, proto = 'c')), 24 | 'submit') 25 | 26 | expect_true(file.remove("files/basic_ggtrack_delete.png")) 27 | }) 28 | 29 | 30 | test_that("Download generates a file and link with advanced API", { 31 | 32 | adv <- make_tracker() %>% 33 | add_logo('files/ggtrack-logo.svg', 1) %>% 34 | add_qr('some text', justification = 1) %>% 35 | add_caption('some text') %>% 36 | add_theme(plot.background = element_rect(fill = "#ff9955", size = 0)) 37 | 38 | api_adv <- gg %>% 39 | add_banner(adv) 40 | 41 | dl_adv <- make_download(api_adv, 42 | save_file = c('files', 'adv_ggtrack'), 43 | download_file = c('files', 'adv_ggtrack'), 44 | type = 'link', 45 | date = 'delete', 46 | render = FALSE) 47 | 48 | expect_true(file.exists('files/adv_ggtrack_delete.png')) 49 | expect_s3_class(dl_adv, 'shiny.tag') 50 | 51 | # does the link contain the correct URL and type 52 | expect_equal(as.character(strcapture('href="(.*?)"', as.character(dl_adv), proto = 'c')), 53 | "files/adv_ggtrack_delete.png") 54 | expect_true(is.na(strcapture('button type="(.*?)"', as.character(dl_adv), proto = 'c'))) 55 | 56 | expect_true(file.remove("files/adv_ggtrack_delete.png")) 57 | }) 58 | 59 | 60 | -------------------------------------------------------------------------------- /man/ggtrack.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/grid.R 3 | \name{ggtrack} 4 | \alias{ggtrack} 5 | \title{Add Tracking Footer} 6 | \usage{ 7 | ggtrack( 8 | gg, 9 | qr_content = NULL, 10 | color = "black", 11 | color_bg = "white", 12 | caption = NULL, 13 | logo = NULL, 14 | order = "CLQ", 15 | positions = c(55, 25, 20), 16 | logo_justification = 1, 17 | qr_justification = 1, 18 | height_plot = 7, 19 | height_tracker = 1.8, 20 | add_git = TRUE, 21 | add_ts = TRUE, 22 | interactive = FALSE, 23 | plotly_heights = c(0.8, 0.2), 24 | ... 25 | ) 26 | } 27 | \arguments{ 28 | \item{gg}{ggplot object to track} 29 | 30 | \item{qr_content}{\code{character} content passed to \link[qrencoder]{qrencode}. A time stamp is automatically added plus the current 31 | git commit where available.} 32 | 33 | \item{color}{character color of the QR code} 34 | 35 | \item{color_bg}{character background color of QR code} 36 | 37 | \item{caption}{\code{character} or \code{grob} to add to footer. Text can be 38 | \code{html} or \code{md} and is passed directly to \link[gridtext]{richtext_grob}} 39 | 40 | \item{logo}{file to add to footer as a logo} 41 | 42 | \item{order}{\code{character} of \code{length} 3 defining placement 43 | order for "caption" (C), "logo" (L) and "QR" (Q) code. Must be one of: 44 | \itemize{ 45 | \item{CLQ} 46 | \item{LCQ} 47 | \item{QLC} 48 | \item{CQL} 49 | }} 50 | 51 | \item{positions}{\code{numeric} \code{vector} of \code{length} 3, 52 | defining the horizontal proportion of each container. The 3 numbers 53 | must add to 100.} 54 | 55 | \item{logo_justification}{numeric between 0 and 1 passed to \link[grid]{rasterGrob}. See note below.} 56 | 57 | \item{qr_justification}{numeric between 0 and 1 passed to \link[grid]{rasterGrob}. See note below.} 58 | 59 | \item{height_plot}{numeric plot height in cm.} 60 | 61 | \item{height_tracker}{numeric tracker height in cm.} 62 | 63 | \item{add_git}{logical include git info in encoding} 64 | 65 | \item{add_ts}{logical include timestamp info in encoding} 66 | 67 | \item{interactive}{logical use plotly for interactivity} 68 | 69 | \item{plotly_heights}{numeric vector of length 2 to fix relative height of plot and tracking banner} 70 | 71 | \item{...}{options passed to \link[ggplot2]{theme} in order to style the tracker banner.} 72 | } 73 | \value{ 74 | grid 75 | } 76 | \description{ 77 | Add Tracking Footer 78 | } 79 | \note{ 80 | For Justification you need to imagine each element in its own rectangle with a bottom 81 | dimension of 0 to 1. If you want the logo or QR code on the right of the rectangle set justification 82 | to 1, or 0 for left. 83 | } 84 | -------------------------------------------------------------------------------- /R/logo.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #' @title Add Logo to Tracker 4 | #' 5 | #' @param tracker ggtrack tracker object 6 | #' @param logo character file path or image url. Image extension can be any of: 7 | #' \itemize{ 8 | #' \item{jpeg} 9 | #' \item{jpg} 10 | #' \item{png} 11 | #' \item{svg} 12 | #' } 13 | #' @param height_tracker numeric tracker height in cm. 14 | #' @param position data.frame generated by get \link[ggtrack]{get_positions} 15 | #' @param justification numeric between 0 and 1 passed to \link[grid]{rasterGrob}. 16 | #' @param ... additional options passed to \link[grid]{rasterGrob} 17 | #' 18 | #' @importFrom tools file_ext 19 | #' @import RCurl 20 | #' @import png 21 | #' @import jpeg 22 | #' @import rsvg 23 | #' @importFrom magick image_read_svg 24 | #' 25 | #' @return tracker 26 | #' 27 | #' @examples 28 | #' \dontrun{ 29 | #' make_tracker() %>% add_logo('https://www.r-project.org/logo/Rlogo.png', justification = 1) 30 | #' } 31 | logo <- function(tracker, logo, height_tracker, position, justification, ...) { 32 | 33 | # check if url or file 34 | typ <- ifelse(grepl('http', logo), 'url', 'file') 35 | 36 | # check type of logo 37 | ext <- tolower(file_ext(logo)) 38 | 39 | if (typ == 'url') { 40 | logo <- RCurl::getURLContent(logo) 41 | } 42 | 43 | if (ext == 'png') { 44 | logo_imported <- png::readPNG(logo) 45 | } else if (ext %in% c('jpg', 'jpeg')) { 46 | logo_imported <- jpeg::readJPEG(logo) 47 | } else if (ext == 'svg') { 48 | logo_imported <- magick::image_read_svg(logo, height = 300) 49 | } else { 50 | stop(paste0('Unable to Add filetype: ', ext)) 51 | } 52 | 53 | lg <- grid::rasterGrob(logo_imported, x = justification, just = justification, height = unit(height_tracker, 'cm'), name = 'logo', ...) 54 | 55 | # define position 56 | p <- as.list(position[position$order == 'L', ]) 57 | 58 | tracker + 59 | annotation_custom(lg, xmin = p$xmin, xmax = p$xmax) 60 | 61 | } 62 | 63 | #' @rdname logo 64 | #' @family add_logo 65 | #' @family gg 66 | #' @family tracker 67 | #' @export 68 | add_logo <- function (tracker, ...) { 69 | UseMethod("add_logo", tracker) 70 | } 71 | 72 | #' @rdname logo 73 | #' @family add_logo 74 | #' @family gg 75 | #' @export 76 | add_logo.gg <- function(tracker, logo, height_tracker, position, justification, ...) { 77 | 78 | logo(tracker, logo, height_tracker, position, justification, ...) 79 | 80 | } 81 | 82 | #' @rdname logo 83 | #' @family add_logo 84 | #' @family tracker 85 | #' @export 86 | add_logo.tracker <- function(tracker, logo, justification, ...) { 87 | 88 | height_tracker <- tracker$height 89 | position <- tracker$pos 90 | banner <- tracker$track 91 | git <- tracker$git 92 | ts <- tracker$ts 93 | 94 | tracker$track <- logo(banner, logo, height_tracker, position, justification, ...) 95 | 96 | mtrack <- obj_tracker(tracker, 'logo') 97 | 98 | return(mtrack) 99 | 100 | } 101 | -------------------------------------------------------------------------------- /R/qr.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #' @title Add QR Code to Tracker 4 | #' 5 | #' @param tracker ggtrack tracker object 6 | #' @param qr_content \code{character} content passed to \link[qrencoder]{qrencode}. A time stamp is automatically added plus the current 7 | #' git commit where available. 8 | #' @param color character color of QR code 9 | #' @param color_bg character background color of QR code 10 | #' @param height_tracker numeric tracker height in cm. 11 | #' @param position data.frame generated by get \link[ggtrack]{get_positions} 12 | #' @param justification numeric between 0 and 1 passed to \link[grid]{rasterGrob}. 13 | #' @param ... additional options passed to \link[grid]{rasterGrob} 14 | #' 15 | #' @import qrencoder 16 | #' @importFrom grid rasterGrob 17 | #' 18 | #' @return tracker 19 | #' 20 | #' @examples 21 | #' \dontrun{ 22 | #' make_tracker() %>% add_qr('Report ID: 2c9075a5-4d7e-47a5-8616-55dd88af3dc5', justification = 1) 23 | #' } 24 | qr <- function(tracker, qr_content, color = 'black', color_bg = 'white', height_tracker, position, justification, ...) { 25 | 26 | # setup QR code 27 | qr_matrix <- make_qr(qr_content, color, color_bg) 28 | 29 | qr <- grid::rasterGrob(qr_matrix, interpolate = FALSE, x = justification, just = justification, height = unit(height_tracker, 'cm'), name = 'qrcode', ...) 30 | 31 | # define position 32 | p <- as.list(position[position$order == 'Q', ]) 33 | 34 | tracker + 35 | annotation_custom(qr, xmin = p$xmin, xmax = p$xmax) 36 | 37 | } 38 | 39 | 40 | #' @rdname qr 41 | #' @family add_qr 42 | #' @family gg 43 | #' @family tracker 44 | #' @export 45 | add_qr <- function (tracker, ...) { 46 | UseMethod("add_qr", tracker) 47 | } 48 | 49 | 50 | #' @rdname qr 51 | #' @family add_qr 52 | #' @family gg 53 | #' @export 54 | add_qr.gg <- function(tracker, qr_content, color = 'black', color_bg = 'white', height_tracker, position, justification, ...) { 55 | qr(tracker, qr_content, color, color_bg, height_tracker, position, justification, ...) 56 | } 57 | 58 | 59 | #' @rdname qr 60 | #' 61 | #' @importFrom glue glue 62 | #' @family add_qr 63 | #' @family tracker 64 | #' @export 65 | add_qr.tracker <- function(tracker, qr_content, color = 'black', color_bg = 'white', justification, ...) { 66 | 67 | height_tracker <- tracker$height 68 | position <- tracker$pos 69 | banner <- tracker$track 70 | git <- tracker$git 71 | ts <- tracker$ts 72 | 73 | qr_content <- paste(qr_content, git, ts, sep = ' ') 74 | 75 | qr_cm <- qr_size(qr_content) 76 | 77 | if (height_tracker < qr_cm) { 78 | height_tracker <- tracker$height <- qr_cm 79 | message(glue('to encode this much text into QR making QR height {qr_cm}cm')) 80 | } 81 | 82 | tracker$track <- qr(banner, qr_content, color, color_bg, height_tracker, position, justification, ...) 83 | 84 | mtrack <- obj_tracker(tracker, 'qr') 85 | 86 | return(mtrack) 87 | 88 | } 89 | 90 | 91 | #' @title Make QR 92 | #' 93 | #' @param qr_content \code{character} content passed to \link[qrencoder]{qrencode}. A time stamp is automatically added plus the current 94 | #' git commit where available. 95 | #' @param color character color of QR code 96 | #' @param color_bg character background color of QR code 97 | #' 98 | #' @return matrix 99 | #' 100 | make_qr <- function(qr_content, color = 'black', color_bg = 'white') { 101 | 102 | qr_matrix <- qrencoder::qrencode(qr_content) 103 | 104 | qr_matrix[qr_matrix == 1] <- color 105 | qr_matrix[qr_matrix == 0] <- color_bg 106 | 107 | return(qr_matrix) 108 | } 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /R/download.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #' @title Tracker Download Link 4 | #' 5 | #' @description Save tracker object to file and render link or button for user download 6 | #' 7 | #' @param tracker tracker object to be saved 8 | #' @param save_file character vector, where length > 1, path is generated using \link[base]{file.path} 9 | #' @param download_file character vector, where length > 1, path is generated using \link[base]{file.path} 10 | #' @param ext character device type passed to \link[ggplot2]{ggsave} 11 | #' @param link_text character text to appear on the page 12 | #' @param type character must be one of "link" or "button". 13 | #' @param height integer height value passed to \link[ggplot2]{ggsave} 14 | #' @param height_units character units value passed to \link[ggplot2]{ggsave} 15 | #' @param button_style css object generated by \link[htmltools]{css} or a css character 16 | #' @param date character date add to filename. Default is \code{format(Sys.time(), "%Y%m%d-%H%M%S")} 17 | #' @param render Boolean, render in-place or save for later 18 | #' @param stenograph use \link[stegasaur]{lsb_encode} to enbed content within the png output. 19 | #' @param stenograph_text character text or R object to encode using \link[stegasaur]{lsb_encode}, must be less than 2^16 bytes 20 | #' 21 | #' @importFrom htmltools a css 22 | #' @importFrom glue glue 23 | #' @importFrom stegasaur lsb_encode 24 | #' 25 | #' @return html of the link object 26 | #' @export 27 | #' 28 | make_download <- function(tracker, 29 | save_file, 30 | download_file, 31 | ext = 'png', 32 | link_text = 'download', 33 | type = c('link','button'), 34 | height = 13, 35 | height_units = c("cm","mm", "in"), 36 | button_style = htmltools::css(background.color = 'DodgerBlue', 37 | border = 'none', 38 | color = 'white', 39 | padding = '12px 30px', 40 | cursor = 'pointer', 41 | font.size = '16px', 42 | width = '150px'), 43 | date = format(Sys.time(), '%Y%m%d-%H%M%S'), 44 | render = TRUE, 45 | stenograph = FALSE) { 46 | 47 | if (missing(save_file)) { 48 | save_file <- paste0('chart_', date, '.', ext) 49 | } else { 50 | save_file <- paste0(paste(save_file, collapse = .Platform$file.sep), 51 | '_', date, 52 | '.', ext) 53 | } 54 | 55 | if (missing(download_file)) { 56 | download_file <- save_file 57 | } else { 58 | download_file <- paste0(paste(download_file, collapse = .Platform$file.sep), 59 | '_', date, 60 | '.', ext) 61 | } 62 | 63 | type <- match.arg(type, choices = type) 64 | height_units <- match.arg(height_units, choices = height_units) 65 | 66 | # save to file 67 | ggsave(save_file, tracker, device = ext, height = height, units = height_units) 68 | 69 | if (!isFALSE(stenograph)) { 70 | img <- png::readPNG(save_file) 71 | png::writePNG(lsb_encode(stenograph, img), save_file) # need method to pass content lsb_encode 72 | } 73 | 74 | if (type == 'link') { 75 | link <- htmltools::a(href = download_file, 76 | download = basename(download_file), 77 | link_text) 78 | } else if (type == 'button'){ 79 | link <- glue::glue('', 80 | '', 84 | '') 85 | } 86 | 87 | if (render){ 88 | cat(as.character(link)) 89 | } 90 | 91 | return(link) 92 | 93 | } 94 | 95 | -------------------------------------------------------------------------------- /man/figures/ggtrack-favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | nttnttnt 31 | 52 | 55 | 56 | 57 | 58 | nt 62 | 68 | 69 | 70 | n 75 | 80 | 81 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: 3 | md_document: 4 | variant: markdown_github 5 | --- 6 | 7 | 8 | 9 | ```{r, include = FALSE} 10 | knitr::opts_chunk$set( 11 | collapse = TRUE, 12 | comment = "#>", 13 | fig.path = "man/figures/README-", 14 | out.width = "100%" 15 | ) 16 | ``` 17 | 18 | # ggtrack 19 | 20 | 21 | [![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) 22 | [![R-CMD-check](https://github.com/mrjoh3/ggtrack/workflows/R-CMD-check/badge.svg)](https://github.com/mrjoh3/ggtrack/actions) 23 | [![Codecov test coverage](https://codecov.io/gh/mrjoh3/ggtrack/branch/master/graph/badge.svg)](https://codecov.io/gh/mrjoh3/ggtrack?branch=master) 24 | 25 | 26 |
27 | 28 | Sometimes in a workplace it is hard to know exactly where or when a specific chart has been produced. This is especially true when someone has copied a chart out of a report to use somewhere else. The `ggtrack` package aims to solve this problem by embedding enough metadata in the charts image to identify the source and the exact time it was produced. 29 | 30 | The metadata is added by way of a QR code embedded in a chart "tracking" footer. The QR code can encode any arbitrary text and will append a time stamp. The text is intended to be a URL or a unique id for the original document or chart source. 31 | 32 | QR codes can then be scanned using a phone scanner or the [quadrangle package](https://github.com/brianwdavis/quadrangle). 33 | 34 | The `ggtrack` footer can also display a corporate logo and additional text. 35 | 36 | 37 | 38 | ## Installation 39 | 40 | You can install the latest version of project from [Github](https://github.com) with: 41 | 42 | ``` r 43 | devtools::install_github("mrjoh3/ggtrack") 44 | ``` 45 | On a linux system you may also need to install the png and jpeg libs. 46 | 47 | 48 | ```console 49 | sudo apt install libpng-dev libjpeg-dev 50 | ``` 51 | 52 | ## Development 53 | 54 | This package is still quite young but now contains most of the desired features. It now has a full testing suite and documentation. For future updates all efforts will be made to maintain a stable API. If you have any suggestions, or feature requests please submit an [issue](https://github.com/mrjoh3/ggtrack/issues). All feedback is welcome. 55 | 56 | 57 | ## A Minimum Example 58 | 59 | To start you just need a `ggplot` and some text you wish to encode into the QR. The QR is intended to contain enough information to uniquely identify the report, so a URL, file name or other unique identifier. The QR encode process automatically appends a time stamp. But try to keep the content of the QR code minimal. The for information it is the more pixels its requires and the larger it needs to be. The examples here need a QR code size of 1.8cm to be reliably scanned using a phone off the screen. QR code are encoded using the [qrencoder](https://github.com/hrbrmstr/qrencoder) package. 60 | 61 | ```{r example, fig.width=8, fig.height=5, out.width="100%", fig.align='center', fig.retina=2} 62 | library(ggtrack) 63 | library(ggplot2) 64 | library(grid) 65 | library(rWBclimate) 66 | library(ggthemes) 67 | 68 | temp <- get_historical_temp('aus', "year") 69 | 70 | tp <- ggplot(temp, aes(x = year, y = data)) + 71 | geom_path(color = 'blue') + geom_point(color = 'darkblue') + 72 | labs(title = 'Average Annual Temperature for Australia', 73 | y = 'degrees celcius') + 74 | stat_smooth(se = TRUE, colour = "darkred") + 75 | theme_fivethirtyeight() 76 | 77 | ggtrack(tp, 78 | qr_content = paste0('Data accessed using R package: ', 79 | 'https://github.com/ropensci/rWBclimate / ', 80 | 'https://docs.ropensci.org/rWBclimate/'), 81 | logo = 'man/figures/ggtrack-logo.svg', 82 | caption = paste0('data accessed from the World Bank
', 83 | 'Climate Portal via the R package
', 84 | 'rWBclimate.'), 85 | plot.background = element_rect(fill = "#f0f0f0", size = 0)) 86 | 87 | ``` 88 | 89 | 90 | 91 | ## Related and Enabling Packages 92 | 93 | The `ggrack` package makes use of many [R packages](https://github.com/mrjoh3/ggtrack/blob/master/DESCRIPTION#L20). But I want to include a special thank-you to some packages without which the `ggtrack` package would not be possible. 94 | 95 | * The [qrencoder](https://github.com/hrbrmstr/qrencoder) package generate to QR codes that form the basis for much of `ggtrack`. `qrencoder` is both fast and easy to use, and provides a variety of outputs that make it easy to incorporate QR codes into a project. 96 | * Without [ggplot2](https://github.com/tidyverse/ggplot2/) there would be little point to a package like `ggtrack`. The entire banner object is a `ggplot` with `theme_void` and `annotation_custom` used to place the three tracking elements. 97 | * The `rasterGrob` elements from the [grid](https://github.com/cran/grid) package make it possible to add both the QR code and arbitrary images such as logos. 98 | * `grid.arrange` from [gridExtra](https://cran.r-project.org/package=gridExtra) makes it possible to stack the `ggplot` object on top of the tracking banner. 99 | * The [stegasaur](https://github.com/richfitz/stegasaur) is used the encode arbitrary text or R objects into the plot `PNG`. This is very cool and [stegasaur](https://github.com/richfitz/stegasaur) is a great package that makes it really easy to encode and decode images. 100 | 101 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to ggtrack 2 | 3 | 4 | 5 | First of all, thanks for considering contributing to ggtrack! 👍 It's people like you that make it rewarding for us - the project maintainers - to work on ggtrack. 😊 6 | 7 | ggtrack is an open source project, maintained by people who care. We are not directly funded to do so. 8 | 9 | [repo]: https://github.com/mrjoh3/ggtrack 10 | [issues]: https://github.com/mrjoh3/ggtrack/issues 11 | [new_issue]: https://github.com/mrjoh3/ggtrack/issues/new 12 | [website]: https://mrjoh3.github.io/ggtrack 13 | [citation]: https://mrjoh3.github.io/ggtrack/authors.html 14 | [email]: mailto:mrjoh3@gmail.com 15 | 16 | ## Code of conduct 17 | 18 | Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. 19 | 20 | ## How you can contribute 21 | 22 | There are several ways you can contribute to this project. If you want to know more about why and how to contribute to open source projects like this one, see this [Open Source Guide](https://opensource.guide/how-to-contribute/). 23 | 24 | ### Share the love ❤️ 25 | 26 | Think ggtrack is useful? Let others discover it, by telling them in person, via Twitter or a blog post. 27 | 28 | Using ggtrack for a paper you are writing? Consider citing it, you can generate a citation in R using `citation('ggtrack')`. 29 | 30 | ### Ask a question ⁉️ 31 | 32 | Using ggtrack and got stuck? Browse the [documentation][https://mrjoh3.github.io/ggtrack] to see if you can find a solution. Still stuck? Post your question as an [issue on GitHub][https://github.com/mrjoh3/ggtrack/issues/new]. While we cannot offer user support, we'll try to do our best to address it, as questions often lead to better documentation or the discovery of bugs. 33 | 34 | Want to ask a question in private? Contact the package maintainer by [email][mailto:mrjoh3@gmail.com]. 35 | 36 | ### Propose an idea 💡 37 | 38 | Have an idea for a new ggtrack feature? Take a look at the [documentation][https://mrjoh3.github.io/ggtrack] and [issue list][https://github.com/mrjoh3/ggtrack/issues] to see if it isn't included or suggested yet. If not, suggest your idea as an [issue on GitHub][https://github.com/mrjoh3/ggtrack/issues/new]. While we can't promise to implement your idea, it helps to: 39 | 40 | * Explain in detail how it would work. 41 | * Keep the scope as narrow as possible. 42 | 43 | See below if you want to contribute code for your idea as well. 44 | 45 | ### Report a bug 🐛 46 | 47 | Using ggtrack and discovered a bug? That's annoying! Don't let others have the same experience and report it as an [issue on GitHub][https://github.com/mrjoh3/ggtrack/issues/new] so we can fix it. A good bug report makes it easier for us to do so, so please include: 48 | 49 | * Your operating system name and version (e.g. Mac OS 10.13.6). 50 | * Any details about your local setup that might be helpful in troubleshooting. 51 | * Detailed steps to reproduce the bug. 52 | 53 | ### Improve the documentation 📖 54 | 55 | Noticed a typo on the website? Think a function could use a better example? Good documentation makes all the difference, so your help to improve it is very welcome! 56 | 57 | #### The website 58 | 59 | [This website][https://mrjoh3.github.io/ggtrack] is generated with [`pkgdown`](http://pkgdown.r-lib.org/). That means we don't have to write any html: content is pulled together from documentation in the code, vignettes, [Markdown](https://guides.github.com/features/mastering-markdown/) files, the package `DESCRIPTION` and `_pkgdown.yml` settings. If you know your way around `pkgdown`, you can [propose a file change](https://help.github.com/articles/editing-files-in-another-user-s-repository/) to improve documentation. If not, [report an issue][https://github.com/mrjoh3/ggtrack/issues/new] and we can point you in the right direction. 60 | 61 | #### Function documentation 62 | 63 | Functions are described as comments near their code and translated to documentation using [`roxygen2`](https://klutometis.github.io/roxygen/). If you want to improve a function description: 64 | 65 | 1. Go to `R/` directory in the [code repository][https://github.com/mrjoh3/ggtrack]. 66 | 2. Look for the file with the name of the function. 67 | 3. [Propose a file change](https://help.github.com/articles/editing-files-in-another-user-s-repository/) to update the function documentation in the roxygen comments (starting with `#'`). 68 | 69 | ### Contribute code 📝 70 | 71 | Care to fix bugs or implement new functionality for ggtrack? Awesome! 👏 Have a look at the [issue list][https://github.com/mrjoh3/ggtrack/issues] and leave a comment on the things you want to work on. See also the development guidelines below. 72 | 73 | ## Development guidelines 74 | 75 | We try to follow the [GitHub flow](https://guides.github.com/introduction/flow/) for development. 76 | 77 | 1. Fork [this repo][repo] and clone it to your computer. To learn more about this process, see [this guide](https://guides.github.com/activities/forking/). 78 | 2. If you have forked and cloned the project before and it has been a while since you worked on it, [pull changes from the original repo](https://help.github.com/articles/merging-an-upstream-repository-into-your-fork/) to your clone by using `git pull upstream master`. 79 | 3. Open the RStudio project file (`.Rproj`). 80 | 4. Make your changes: 81 | * Write your code. 82 | * Test your code (bonus points for adding unit tests) we use the `testthat` package. 83 | * Document your code (see function documentation above). 84 | * Check your code with `devtools::check()` and aim for 0 errors and warnings. 85 | 5. Commit and push your changes. 86 | 6. Submit a [pull request](https://guides.github.com/activities/forking/#making-a-pull-request). 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # ggtrack 4 | 5 | 6 | 7 | [![Lifecycle: 8 | stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) 9 | [![R-CMD-check](https://github.com/mrjoh3/ggtrack/workflows/R-CMD-check/badge.svg)](https://github.com/mrjoh3/ggtrack/actions) 10 | [![Codecov test 11 | coverage](https://codecov.io/gh/mrjoh3/ggtrack/branch/master/graph/badge.svg)](https://codecov.io/gh/mrjoh3/ggtrack?branch=master) 12 | 13 | 14 |
15 | 16 | 17 | 18 |
19 | 20 | Sometimes in a workplace it is hard to know exactly where or when a 21 | specific chart has been produced. This is especially true when someone 22 | has copied a chart out of a report to use somewhere else. The `ggtrack` 23 | package aims to solve this problem by embedding enough metadata in the 24 | charts image to identify the source and the exact time it was produced. 25 | 26 | The metadata is added by way of a QR code embedded in a chart “tracking” 27 | footer. The QR code can encode any arbitrary text and will append a time 28 | stamp. The text is intended to be a URL or a unique id for the original 29 | document or chart source. 30 | 31 | QR codes can then be scanned using a phone scanner or the [quadrangle 32 | package](https://github.com/brianwdavis/quadrangle). 33 | 34 | The `ggtrack` footer can also display a corporate logo and additional 35 | text. 36 | 37 | ## Installation 38 | 39 | You can install the latest version of project from 40 | [Github](https://github.com) with: 41 | 42 | ``` r 43 | devtools::install_github("mrjoh3/ggtrack") 44 | ``` 45 | 46 | On a linux system you may also need to install the png and jpeg libs. 47 | 48 | ``` console 49 | sudo apt install libpng-dev libjpeg-dev 50 | ``` 51 | 52 | ## Development 53 | 54 | This package is still quite young but now contains most of the desired 55 | features. It now has a full testing suite and documentation. For future 56 | updates all efforts will be made to maintain a stable API. If you have 57 | any suggestions, or feature requests please submit an 58 | [issue](https://github.com/mrjoh3/ggtrack/issues). All feedback is 59 | welcome. 60 | 61 | ## A Minimum Example 62 | 63 | To start you just need a `ggplot` and some text you wish to encode into 64 | the QR. The QR is intended to contain enough information to uniquely 65 | identify the report, so a URL, file name or other unique identifier. The 66 | QR encode process automatically appends a time stamp. But try to keep 67 | the content of the QR code minimal. The for information it is the more 68 | pixels its requires and the larger it needs to be. The examples here 69 | need a QR code size of 1.8cm to be reliably scanned using a phone off 70 | the screen. QR code are encoded using the 71 | [qrencoder](https://github.com/hrbrmstr/qrencoder) package. 72 | 73 | ``` r 74 | library(ggtrack) 75 | library(ggplot2) 76 | library(grid) 77 | library(rWBclimate) 78 | library(ggthemes) 79 | 80 | temp <- get_historical_temp('aus', "year") 81 | #> No encoding supplied: defaulting to UTF-8. 82 | 83 | tp <- ggplot(temp, aes(x = year, y = data)) + 84 | geom_path(color = 'blue') + geom_point(color = 'darkblue') + 85 | labs(title = 'Average Annual Temperature for Australia', 86 | y = 'degrees celcius') + 87 | stat_smooth(se = TRUE, colour = "darkred") + 88 | theme_fivethirtyeight() 89 | 90 | ggtrack(tp, 91 | qr_content = paste0('Data accessed using R package: ', 92 | 'https://github.com/ropensci/rWBclimate / ', 93 | 'https://docs.ropensci.org/rWBclimate/'), 94 | logo = 'man/figures/ggtrack-logo.svg', 95 | caption = paste0('data accessed from the World Bank
', 96 | 'Climate Portal via the R package
', 97 | 'rWBclimate.'), 98 | plot.background = element_rect(fill = "#f0f0f0", size = 0)) 99 | #> `geom_smooth()` using method = 'loess' and formula 'y ~ x' 100 | ``` 101 | 102 | 103 | 104 | ## Related and Enabling Packages 105 | 106 | The `ggrack` package makes use of many [R 107 | packages](https://github.com/mrjoh3/ggtrack/blob/master/DESCRIPTION#L20). 108 | But I want to include a special thank-you to some packages without which 109 | the `ggtrack` package would not be possible. 110 | 111 | - The [qrencoder](https://github.com/hrbrmstr/qrencoder) package 112 | generate to QR codes that form the basis for much of `ggtrack`. 113 | `qrencoder` is both fast and easy to use, and provides a variety of 114 | outputs that make it easy to incorporate QR codes into a project. 115 | - Without [ggplot2](https://github.com/tidyverse/ggplot2/) there would 116 | be little point to a package like `ggtrack`. The entire banner 117 | object is a `ggplot` with `theme_void` and `annotation_custom` used 118 | to place the three tracking elements. 119 | - The `rasterGrob` elements from the 120 | [grid](https://github.com/cran/grid) package make it possible to add 121 | both the QR code and arbitrary images such as logos. 122 | - `grid.arrange` from 123 | [gridExtra](https://cran.r-project.org/package=gridExtra) makes it 124 | possible to stack the `ggplot` object on top of the tracking banner. 125 | - The [stegasaur](https://github.com/richfitz/stegasaur) is used the 126 | encode arbitrary text or R objects into the plot `PNG`. This is very 127 | cool and [stegasaur](https://github.com/richfitz/stegasaur) is a 128 | great package that makes it really easy to encode and decode images. 129 | -------------------------------------------------------------------------------- /tests/testthat/test-grid.R: -------------------------------------------------------------------------------- 1 | 2 | gg <- ggplot(mapping = aes(x = 1:10, y = rnorm(10))) + 3 | geom_bar(stat = 'identity') 4 | 5 | test_that("We can add a QR code", { 6 | qr_test <- ggtrack(gg, 7 | qr_content = 'text content here', 8 | add_git = FALSE) 9 | expect_s3_class(qr_test, "gtable") 10 | expect_s3_class(qr_test, "gTree") 11 | expect_s3_class(qr_test, "grob") 12 | expect_s3_class(qr_test, "gDesc") 13 | 14 | expect_length(qr_test$heights, 2) 15 | expect_equal(as.numeric(qr_test$heights[1]), 7) 16 | expect_equal(as.numeric(qr_test$heights[2]), 3.3) 17 | }) 18 | 19 | 20 | test_that("We can add a Logo from URL", { 21 | qr_logo <- ggtrack(gg, 22 | logo = 'https://www.r-project.org/logo/Rlogo.png', 23 | add_git = FALSE) 24 | expect_s3_class(qr_logo, "gtable") 25 | expect_s3_class(qr_logo, "gTree") 26 | expect_s3_class(qr_logo, "grob") 27 | expect_s3_class(qr_logo, "gDesc") 28 | 29 | expect_length(qr_logo$heights, 2) 30 | expect_equal(as.numeric(qr_logo$heights[1]), 7) 31 | expect_equal(as.numeric(qr_logo$heights[2]), 3.3) 32 | }) 33 | 34 | 35 | test_that("We can add a PNG Logo from file", { 36 | qr_logo <- ggtrack(gg, 37 | logo = 'files/ggtrack-logo.png', 38 | add_git = FALSE) 39 | expect_s3_class(qr_logo, "gtable") 40 | expect_s3_class(qr_logo, "gTree") 41 | expect_s3_class(qr_logo, "grob") 42 | expect_s3_class(qr_logo, "gDesc") 43 | 44 | expect_length(qr_logo$heights, 2) 45 | expect_equal(as.numeric(qr_logo$heights[1]), 7) 46 | expect_equal(as.numeric(qr_logo$heights[2]), 3.3) 47 | }) 48 | 49 | 50 | 51 | test_that("We can add a JPEG Logo from file", { 52 | qr_logo <- ggtrack(gg, 53 | logo = 'files/ggtrack-logo.jpg', 54 | add_git = FALSE) 55 | expect_s3_class(qr_logo, "gtable") 56 | expect_s3_class(qr_logo, "gTree") 57 | expect_s3_class(qr_logo, "grob") 58 | expect_s3_class(qr_logo, "gDesc") 59 | 60 | expect_length(qr_logo$heights, 2) 61 | expect_equal(as.numeric(qr_logo$heights[1]), 7) 62 | expect_equal(as.numeric(qr_logo$heights[2]), 3.3) 63 | }) 64 | 65 | 66 | 67 | 68 | test_that("We can add a SVG Logo from file", { 69 | qr_logo <- ggtrack(gg, 70 | logo = 'files/ggtrack-logo.svg', 71 | add_git = FALSE) 72 | expect_s3_class(qr_logo, "gtable") 73 | expect_s3_class(qr_logo, "gTree") 74 | expect_s3_class(qr_logo, "grob") 75 | expect_s3_class(qr_logo, "gDesc") 76 | 77 | expect_length(qr_logo$heights, 2) 78 | expect_equal(as.numeric(qr_logo$heights[1]), 7) 79 | expect_equal(as.numeric(qr_logo$heights[2]), 3.3) 80 | }) 81 | 82 | 83 | 84 | test_that("We can add a Logo and a QR", { 85 | qr_logo <- ggtrack(gg, 86 | qr_content = 'text content here', 87 | logo = 'files/ggtrack-logo.svg', 88 | add_git = FALSE) 89 | expect_s3_class(qr_logo, "gtable") 90 | expect_s3_class(qr_logo, "gTree") 91 | expect_s3_class(qr_logo, "grob") 92 | expect_s3_class(qr_logo, "gDesc") 93 | 94 | expect_length(qr_logo$heights, 2) 95 | expect_equal(as.numeric(qr_logo$heights[1]), 7) 96 | expect_equal(as.numeric(qr_logo$heights[2]), 3.3) 97 | }) 98 | 99 | 100 | test_that("We can add a Caption", { 101 | qr_logo <- ggtrack(gg, 102 | caption = 'text to display', 103 | add_git = FALSE) 104 | expect_s3_class(qr_logo, "gtable") 105 | expect_s3_class(qr_logo, "gTree") 106 | expect_s3_class(qr_logo, "grob") 107 | expect_s3_class(qr_logo, "gDesc") 108 | 109 | expect_length(qr_logo$heights, 2) 110 | expect_equal(as.numeric(qr_logo$heights[1]), 7) 111 | expect_equal(as.numeric(qr_logo$heights[2]), 3.3) 112 | }) 113 | 114 | 115 | test_that("We can add a Caption and a QR", { 116 | qr_logo <- ggtrack(gg, 117 | qr_content = 'text content here', 118 | caption = 'text to display', 119 | add_git = FALSE) 120 | expect_s3_class(qr_logo, "gtable") 121 | expect_s3_class(qr_logo, "gTree") 122 | expect_s3_class(qr_logo, "grob") 123 | expect_s3_class(qr_logo, "gDesc") 124 | 125 | expect_length(qr_logo$heights, 2) 126 | expect_equal(as.numeric(qr_logo$heights[1]), 7) 127 | expect_equal(as.numeric(qr_logo$heights[2]), 3.3) 128 | }) 129 | 130 | 131 | 132 | test_that("We can add a Caption, logo and a QR", { 133 | 134 | qr_logo <- ggtrack(gg, 135 | qr_content = 'text content here', 136 | logo = 'files/ggtrack-logo.svg', 137 | caption = 'text to display', 138 | add_git = FALSE) 139 | expect_s3_class(qr_logo, "gtable") 140 | expect_s3_class(qr_logo, "gTree") 141 | expect_s3_class(qr_logo, "grob") 142 | expect_s3_class(qr_logo, "gDesc") 143 | 144 | expect_length(qr_logo$heights, 2) 145 | expect_equal(as.numeric(qr_logo$heights[1]), 7) 146 | expect_equal(as.numeric(qr_logo$heights[2]), 3.3) 147 | }) 148 | 149 | 150 | test_that('element order can be rearranged', { 151 | pos <- ggtrack(gg, 152 | order = 'LCQ', 153 | positions = c(25, 55, 20), 154 | qr_content = 'For all your image tracking needs: https://github.com/mrjoh3/ggtrack', 155 | logo = 'https://www.r-project.org/logo/Rlogo.png', 156 | caption = 'Lots of extra info, or a fancy "grob".', 157 | logo_justification = 0, 158 | add_git = FALSE) 159 | 160 | expect_s3_class(pos, "gtable") 161 | expect_s3_class(pos, "gTree") 162 | expect_s3_class(pos, "grob") 163 | expect_s3_class(pos, "gDesc") 164 | 165 | expect_length(pos$heights, 2) 166 | expect_equal(as.numeric(pos$heights[1]), 7) 167 | expect_equal(as.numeric(pos$heights[2]), 3.5) 168 | 169 | }) 170 | 171 | 172 | test_that('element size can be modified', { 173 | pos <- ggtrack(gg, 174 | order = 'LCQ', 175 | positions = c(60, 10, 30), 176 | qr_content = 'For all your image tracking needs: https://github.com/mrjoh3/ggtrack', 177 | logo = 'https://www.r-project.org/logo/Rlogo.png', 178 | caption = 'Lots of extra info, or a fancy "grob".', 179 | logo_justification = 0, 180 | add_git = FALSE) 181 | 182 | expect_s3_class(pos, "gtable") 183 | expect_s3_class(pos, "gTree") 184 | expect_s3_class(pos, "grob") 185 | expect_s3_class(pos, "gDesc") 186 | 187 | expect_length(pos$heights, 2) 188 | expect_equal(as.numeric(pos$heights[1]), 7) 189 | expect_equal(as.numeric(pos$heights[2]), 3.5) 190 | 191 | }) 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /R/grid.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #' @title Add Tracking Footer 4 | #' 5 | #' @param gg ggplot object to track 6 | #' @param qr_content \code{character} content passed to \link[qrencoder]{qrencode}. A time stamp is automatically added plus the current 7 | #' git commit where available. 8 | #' @param color character color of the QR code 9 | #' @param color_bg character background color of QR code 10 | #' @param caption \code{character} or \code{grob} to add to footer. Text can be 11 | #' \code{html} or \code{md} and is passed directly to \link[gridtext]{richtext_grob} 12 | #' @param logo file to add to footer as a logo 13 | #' @param order \code{character} of \code{length} 3 defining placement 14 | #' order for "caption" (C), "logo" (L) and "QR" (Q) code. Must be one of: 15 | #' \itemize{ 16 | #' \item{CLQ} 17 | #' \item{LCQ} 18 | #' \item{QLC} 19 | #' \item{CQL} 20 | #' } 21 | #' @param positions \code{numeric} \code{vector} of \code{length} 3, 22 | #' defining the horizontal proportion of each container. The 3 numbers 23 | #' must add to 100. 24 | #' @param logo_justification numeric between 0 and 1 passed to \link[grid]{rasterGrob}. See note below. 25 | #' @param qr_justification numeric between 0 and 1 passed to \link[grid]{rasterGrob}. See note below. 26 | #' @param height_plot numeric plot height in cm. 27 | #' @param height_tracker numeric tracker height in cm. 28 | #' @param add_git logical include git info in encoding 29 | #' @param add_ts logical include timestamp info in encoding 30 | #' @param interactive logical use plotly for interactivity 31 | #' @param plotly_heights numeric vector of length 2 to fix relative height of plot and tracking banner 32 | #' @param ... options passed to \link[ggplot2]{theme} in order to style the tracker banner. 33 | #' 34 | #' @note For Justification you need to imagine each element in its own rectangle with a bottom 35 | #' dimension of 0 to 1. If you want the logo or QR code on the right of the rectangle set justification 36 | #' to 1, or 0 for left. 37 | #' 38 | #' @import ggplot2 39 | #' @import grid 40 | #' @import gridExtra 41 | #' 42 | #' @return grid 43 | #' @export 44 | #' 45 | ggtrack <- function(gg, 46 | qr_content = NULL, 47 | color = 'black', 48 | color_bg = 'white', 49 | caption = NULL, 50 | logo = NULL, 51 | order = 'CLQ', 52 | positions = c(55, 25, 20), 53 | logo_justification = 1, 54 | qr_justification = 1, 55 | height_plot = 7, 56 | height_tracker = 1.8, 57 | add_git = TRUE, 58 | add_ts = TRUE, 59 | interactive = FALSE, 60 | plotly_heights = c(.8,.2), 61 | ...) { 62 | 63 | 64 | # define size and order of 3 containers 65 | pos <- get_positions(order, positions) 66 | 67 | # build tracker as plot, this is the tracker object 68 | tracker <- ggplot(mapping = aes(x = 0:1, y = 1)) + 69 | theme_void() + 70 | theme(...) 71 | 72 | # setup qr 73 | if (!is.null(qr_content)) { 74 | 75 | # build QR content 76 | if (add_git) { 77 | git <- get_git_info() 78 | qr_content <- paste(qr_content, git, sep = ' ') 79 | } 80 | 81 | if (add_ts) { 82 | qr_content <- paste(qr_content, format(Sys.time(), '%Y%m%d-%H%M%S'), sep = ' ') 83 | } 84 | 85 | qr_cm <- qr_size(qr_content) 86 | 87 | if (height_tracker < qr_cm) { 88 | height_tracker <- qr_cm 89 | message(glue('to encode this much text into QR making QR height {qr_cm}cm')) 90 | } 91 | 92 | tracker <- add_qr(tracker, qr_content, color, color_bg, height_tracker, pos, qr_justification) 93 | } 94 | 95 | # setup logo 96 | if (!is.null(logo)) { 97 | tracker <- add_logo(tracker, logo, height_tracker, pos, logo_justification) 98 | } 99 | 100 | # setup caption 101 | if (!is.null(caption)) { 102 | tracker <- add_caption(tracker, caption, pos) 103 | } 104 | 105 | # style tracker 106 | tracker <- tracker + 107 | theme(plot.margin=unit(c(.5, 0, .3, 0),"cm")) 108 | 109 | if (interactive) { 110 | #plotly::subplot(gg, tracker, nrows = 2, heights = plotly_heights) 111 | warning('interactive option currently unavailable') 112 | } else { 113 | gridExtra::grid.arrange(gg, tracker, heights = unit(c(height_plot, height_tracker + 1.5 ), "cm")) 114 | } 115 | 116 | 117 | } 118 | 119 | 120 | 121 | 122 | 123 | 124 | #' @title Define Tracker Base 125 | #' 126 | #' @param order \code{character} of \code{length} 3 defining placement 127 | #' order for "caption" (C), "logo" (L) and "QR" (Q) code. Must be one of: 128 | #' \itemize{ 129 | #' \item{CLQ} 130 | #' \item{LCQ} 131 | #' \item{QLC} 132 | #' \item{CQL} 133 | #' } 134 | #' @param positions \code{numeric} \code{vector} of \code{length} 3, 135 | #' defining the horizontal proportion of each container. The 3 numbers 136 | #' must add to 100. 137 | #' @param height_tracker numeric tracker height in cm. 138 | #' @param add_git logical include git info in encoding 139 | #' @param add_ts logical include timestamp info in encoding 140 | #' 141 | #' @return tracker 142 | #' @export 143 | #' 144 | #' @examples 145 | #' \dontrun{ 146 | #' make_tracker() 147 | #' } 148 | make_tracker <- function(order = 'CLQ', 149 | positions = c(55, 25, 20), 150 | height_tracker = 1.8, 151 | add_git = TRUE, 152 | add_ts = TRUE) { 153 | 154 | # define size and order of 3 containers 155 | pos <- get_positions(order, positions) 156 | 157 | # build QR content 158 | if (add_git) { 159 | git <- get_git_info() 160 | } else { 161 | git <- '' 162 | } 163 | 164 | if (add_ts) { 165 | ts <- format(Sys.time(), '%Y%m%d-%H%M%S') 166 | } else { 167 | ts <- '' 168 | } 169 | 170 | # build tracker as plot, this is the tracker object 171 | tracker <- ggplot(mapping = aes(x = 0:1, y = 1)) + 172 | theme_void() 173 | 174 | mtrack <- obj_tracker(tracker, 'background', pos, height_tracker, git, ts) 175 | 176 | return(mtrack) 177 | 178 | } 179 | 180 | 181 | #' @title Create Tracker Object 182 | #' 183 | #' @param tracker ggtrack tracker object 184 | #' @param contains character vector tracks what elements have been added to tracker 185 | #' @param pos \code{numeric} \code{vector} of \code{length} 3, 186 | #' defining the horizontal proportion of each container. The 3 numbers 187 | #' must add to 100. 188 | #' @param height_tracker numeric tracker height in cm. 189 | #' @param git character git information derived by \link[ggtrack]{get_git_info} 190 | #' @param timestamp character timestamp information obtained from \code{format(Sys.time(), '%Y%m%d-%H%M%S')} 191 | #' 192 | #' 193 | #' 194 | #' @return tracker 195 | #' 196 | obj_tracker <- function(tracker, contains, pos = NULL, height_tracker = NULL, git = NULL, timestamp = NULL) { 197 | 198 | if (is.null(tracker$contains)) { 199 | tracker <- list(track = tracker, 200 | pos = pos, 201 | height = height_tracker, 202 | contains = contains, 203 | git = git, 204 | ts = timestamp) 205 | class(tracker) <- 'tracker' 206 | } else { 207 | tracker$contains <- c(tracker$contains, contains) 208 | } 209 | 210 | return(tracker) 211 | 212 | } 213 | 214 | 215 | 216 | 217 | 218 | #' @title Print Tracker 219 | #' 220 | #' @param x ggtrack tracker object 221 | #' @param ... print options 222 | #' 223 | #' @export 224 | #' @examples 225 | #' \dontrun{ 226 | #' print(make_tracker()) 227 | #' } 228 | print.tracker <- function(x, ...) { 229 | 230 | cat("ggtrack tracking banner\n") 231 | cat("=======================\n\n") 232 | cat(glue("banner height: {x$height} cm \n")) 233 | cat("\n\nincluded elements:\n - ") 234 | cat(x$contains, sep = '\n - ') 235 | cat('\nelement positions:\n\n') 236 | x$pos 237 | 238 | } 239 | 240 | #' @title Plot Tracker Object 241 | #' 242 | #' @param x ggtrack tracker object 243 | #' @param ... plot options 244 | #' 245 | #' @export 246 | #' @examples 247 | #' \dontrun{ 248 | #' plot(make_tracker()) 249 | #' } 250 | plot.tracker <- function(x, ...) { 251 | 252 | x$track + 253 | labs(title = 'ggtrack tracker banner', 254 | subtitle = "add to a ggplot using add_banner(plot, tracker)") 255 | } 256 | -------------------------------------------------------------------------------- /vignettes/ggtrack.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "ggtrack" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{ggtrack} 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 | warning = FALSE, 15 | message = FALSE 16 | ) 17 | ``` 18 | 19 | 20 | 21 | ## Using ggtrack 22 | 23 | To start you just need a `ggplot` and some text you wish to encode into the QR. The QR is intended to contain enough information to uniquely identify the report, so a URL, file name or other unique identifier. The QR encode process automatically appends a time stamp. But try to keep the content of the QR code minimal. The for information it is the more pixels its requires and the larger it needs to be. The examples here need a QR code size of 1.8cm to be reliably scanned using a phone off the screen. QR code are encoded using the [qrencoder](https://github.com/hrbrmstr/qrencoder) package. 24 | 25 | ```{r example, fig.width=6, fig.height=5, out.width="80%", fig.align='center', fig.retina=2} 26 | library(ggtrack) 27 | library(ggplot2) 28 | library(grid) 29 | 30 | gg <- ggplot(mapping = aes(x = 1:10, y = rnorm(10))) + 31 | geom_bar(stat = 'identity') + 32 | theme_minimal() 33 | 34 | ggtrack(gg, 35 | qr_content = 'Report ID: 2c9075a5-4d7e-47a5-8616-55dd88af3dc5') 36 | 37 | ``` 38 | 39 | ### Add a Logo 40 | 41 | A logo can be added either from a local or remote source. For now only `png` and `jpeg/jpg` are supported. 42 | 43 | ```{r logo, fig.width=6, fig.height=5, out.width="80%", fig.align='center', fig.retina=2} 44 | ggtrack(gg, 45 | qr_content = 'text content here', 46 | logo = 'https://www.r-project.org/logo/Rlogo.png') 47 | 48 | ``` 49 | 50 | 51 | 52 | 53 | ### Add a Caption 54 | 55 | Captions use the [gridtext](https://github.com/wilkelab/gridtext) package. So you can use both `HTML` and `markdown` to style. Or if you prefer, create your own `grob` and pass that through instead. 56 | 57 | ```{r caption, fig.width=6, fig.height=5, out.width="80%", fig.align='center', fig.retina=2} 58 | g <- ggtrack(gg, 59 | qr_content = 'For all your image tracking needs: https://github.com/mrjoh3/ggtrack', 60 | logo = 'https://www.r-project.org/logo/Rlogo.png', 61 | caption = 'Lots of extra info, or a fancy "grob".') 62 | 63 | grid.draw(g) 64 | ``` 65 | 66 | 67 | ### Rearrange Banner Elements 68 | 69 | The order and size of the banner elements can be easily rearranged via the `order` and `positions` variables. Each element is defined via a single letter code: 70 | 71 | * Caption is C 72 | * Logo is L 73 | * QR code is Q 74 | 75 | And the element order is defined by the order of these codes. For example, the default order code is `CLQ`. 76 | 77 | Depending on the size of the content in each element and its relative order, it becomes necessary to adjust the position or size of the elements. Position defines in order the proportional size of each element. You need to provide three numbers indicating the percent size of the element. These proportions are then converted into `xmin` and `xmax` values in the banner. 78 | 79 | ```{r order, fig.width=6, fig.height=5, out.width="80%", fig.align='center', fig.retina=2} 80 | 81 | ggtrack(gg, 82 | order = 'LCQ', 83 | positions = c(25, 55, 20), 84 | qr_content = 'For all your image tracking needs: https://github.com/mrjoh3/ggtrack', 85 | logo = 'https://www.r-project.org/logo/Rlogo.png', 86 | caption = 'Lots of extra info, or a fancy "grob".', 87 | logo_justification = 0) 88 | 89 | ``` 90 | 91 | When rearranging the order of banner elements it may be necessary to also change the justification of the logo and QR elements. By default these are justified to the right, so if your logo moves to the left it will appear out of place. 92 | 93 | Justification is set via `logo_justification` and `qr_justification`. Justification here is positional. You need to imagine each element in its own rectangle with a bottom dimension of 0 to 1. If you want the logo or QR code on the right of the rectangle set justification to 1, or 0 for left. These are the main options, but you can use any number between 0 and 1 for more granular control. 94 | 95 | 96 | 97 | 98 | ### Add a download link 99 | 100 | The tracking chart can also be saved and made available for download. You can do this manually with `gplotly::ggsave()` and a simple markdown link as below. 101 | 102 | ```r 103 | ggsave(g, filename = 'ggrack_simple_download.png') 104 | ``` 105 | 106 | ```md 107 | [download png](ggrack_simple_download.png) 108 | ``` 109 | 110 | However, `ggtracker` also has a helper function to facilitate. Using `make_download` you can create either a download link or button. Unlike the markdown link which simply opens the file in the browser; `make_download` creates a proper download link. Note, in the example below it is necessary to specify both location to save the file and the download location. This is only necessary in this example due to the way `pkgdown` handles file locations. In most instances you only needs specify the save location. The button can also be styled by `CSS`. 111 | 112 | ```{r save link, results='asis'} 113 | dl <- make_download(g, 114 | save_file = c('../man', 'figures', 'ggtrack_chart'), 115 | download_file = c('../reference', 'figures', 'ggtrack_chart'), 116 | type = 'button', 117 | date = '', 118 | render = FALSE) 119 | ``` 120 | 121 | `r dl` 122 | 123 | Something to consider at this point, with a down load button it is possible to display a plot without the additional metadata. Then pass the tracker object to the download link or button for users. This is a simple way to maintain the aesthetics of your report but still include vital metadata. 124 | 125 | 126 | ### Encode Additional Content Within Image 127 | 128 | It is also possible to encode arbitrary text or an R object within `knitr` chunks of a report or within a downloaded file when using `make_download`. Using the [stegasaur](https://github.com/richfitz/stegasaur) package `knitr` hook is the easiest way to include the code used to generate a plot (see [stegasaur docs](https://richfitz.github.io/stegasaur/reference/hook_figasaur.html)). 129 | 130 | The [stegasaur](https://github.com/richfitz/stegasaur) `stenograph` method is included here as a download option. Simply pass the text or R object you want encoded to the `stenograph` variable. Then to decode the content of the image use `stegasaur::decode()`. For this method to work the image must be saved in `PNG` format, this is the default for `make_download`. 131 | 132 | ```{r steg link, results='asis'} 133 | dl_steg <- make_download(g, 134 | link_text = 'download file with stenograph encoded', 135 | save_file = c('../man', 'figures', 'ggtrack_steg_chart'), 136 | download_file = c('../reference', 'figures', 'ggtrack_steg_chart'), 137 | type = 'link', 138 | date = '', 139 | render = FALSE, 140 | stenograph = rnorm(10)) 141 | 142 | ``` 143 | 144 | `r dl_steg` 145 | 146 | 147 | 148 | ## Tracking Banner Style 149 | 150 | The tracking banner is a simple `ggplot` object. The tracking elements are `grob` objects added via `annotation_custom`. As a `ggplot` object the banner can also be styled in the same way. Any `ggplot` theme options are passed directly to the tracking banner theme. This enables you to match the tracker to your page theme or ggplot theme. For all available theme options see https://ggplot2.tidyverse.org/reference/theme.html. 151 | 152 | ```{r style, fig.width=8, fig.height=5, out.width="100%", fig.align='center', fig.retina=2} 153 | 154 | library(rWBclimate) 155 | library(ggthemes) 156 | 157 | temperature <- get_historical_temp('aus', "year") 158 | 159 | temperature_plot <- ggplot(temperature, aes(x = year, y = data)) + 160 | geom_path(color = 'blue') + geom_point(color = 'darkblue') + 161 | labs(title = 'Average Annual Temperature for Australia', 162 | y = 'degrees celcius') + 163 | stat_smooth(se = TRUE, colour = "darkred") + 164 | theme_fivethirtyeight() 165 | 166 | for_QR <- paste0('Data accessed using R package: ', 167 | 'https://github.com/ropensci/rWBclimate / ', 168 | 'https://docs.ropensci.org/rWBclimate/') 169 | for_Caption <- paste0('data accessed from the World Bank
', 170 | 'Climate Portal via the R package
', 171 | 'rWBclimate.') 172 | 173 | ggtrack(temperature_plot, 174 | qr_content = for_QR, 175 | logo = '../man/figures/ggtrack-logo.svg', 176 | caption = for_Caption, 177 | plot.background = element_rect(fill = "#f0f0f0", size = 0)) 178 | ``` 179 | 180 | 181 | ## One Tracker Per Document 182 | 183 | In some cases you may wish to create a single tracking banner and add it to multiple plots. `ggtrack` also has a more granular API that allows the user to iteratively build the banner. This banner can then be added to any existing plot. 184 | 185 | ```{r build, fig.width=8, fig.height=5, out.width="100%", fig.align='center', fig.retina=2} 186 | 187 | track <- make_tracker() %>% 188 | add_logo('../man/figures/ggtrack-logo.svg', 1) %>% 189 | add_qr(for_QR, justification = 1) %>% 190 | add_caption(for_Caption) %>% 191 | add_theme(plot.background = element_rect(fill = "#ff9955", size = 0)) 192 | 193 | temperature_plot %>% 194 | add_banner(track) 195 | 196 | ``` 197 | 198 | 199 | ## Retrieving Metadata 200 | 201 | The metadata from the QR code can be read using the scanner on your phone. If you need to programatically extract this data use the [quadrangle package](https://github.com/brianwdavis/quadrangle). Given the way the QR code are generated, you will need to ensure you specify `flop = TRUE`. This essentially flips the image to replicate what a scanner looking at a screen would capture. 202 | 203 | With `quadrangle` you can pass in the entire file or just the QR code. The package will detect the location of the QR code before decoding. 204 | 205 | ```{r scan} 206 | library(quadrangle) 207 | 208 | qr <- qr_scan("../man/figures/ggtrack_chart_.png", flop = TRUE, plot = FALSE) 209 | 210 | qr$values$value 211 | ``` 212 | 213 | And for a [stegasaur](https://github.com/richfitz/stegasaur) encoded file, you can simply use `stegasaur::decode`. 214 | 215 | ```{r} 216 | library(stegasaur) 217 | 218 | stegasaur::decode('../man/figures/ggtrack_steg_chart_.png') 219 | ``` 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | ## Related and Enabling Packages 251 | 252 | The `ggrack` package makes use of many [R packages](https://github.com/mrjoh3/ggtrack/blob/master/DESCRIPTION#L20). But I want to include a special thank-you to some packages without which the `ggtrack` package would not be possible. 253 | 254 | * The [qrencoder](https://github.com/hrbrmstr/qrencoder) package generate to QR codes that form the basis for much of `ggtrack`. `qrencoder` is both fast and easy to use, and provides a variety of outputs that make it easy to incorporate QR codes into a project. 255 | * Without [ggplot2](https://github.com/tidyverse/ggplot2/) there would be little point to a package like `ggtrack`. The entire banner object is a `ggplot` with `theme_void` and `annotation_custom` used to place the three tracking elements. 256 | * The `rasterGrob` elements from the [grid](https://github.com/cran/grid) package make it possible to add both the QR code and arbitrary images such as logos. 257 | * `grid.arrange` from [gridExtra](https://cran.r-project.org/package=gridExtra) makes it possible to stack the `ggplot` object on top of the tracking banner. 258 | * The [stegasaur](https://github.com/richfitz/stegasaur) is used the encode arbitrary text or R objects into the plot `PNG`. This is very cool and [stegasaur](https://github.com/richfitz/stegasaur) is a great package that makes it really easy to encode and decode images. 259 | -------------------------------------------------------------------------------- /man/figures/qr.svg: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | nttnttnt 30 | 50 | nt 53 | 57 | 63 | 66 | 73 | 76 | 83 | 86 | 93 | 100 | 107 | 114 | 121 | 128 | 135 | 142 | 149 | 156 | ntt 157 | 158 | 159 | 160 | 164 | 169 | 173 | 177 | 181 | 185 | 189 | 193 | 197 | 198 | 199 | n 200 | -------------------------------------------------------------------------------- /man/figures/ggtrack-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | nttnttnt 31 | 52 | 55 | 56 | 57 | nt 61 | 67 | 72 | 76 | 80 | 84 | 88 | 92 | 96 | 100 | 101 | 102 | n 107 | 112 | 115 | 122 | 125 | 132 | 135 | 142 | 149 | 156 | 163 | 170 | 177 | 184 | 191 | 198 | 205 | 206 | ntt 207 | 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /tests/testthat/files/ggtrack-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | nttnttnt 31 | 52 | 55 | 56 | 57 | nt 61 | 67 | 72 | 76 | 80 | 84 | 88 | 92 | 96 | 100 | 101 | 102 | n 107 | 112 | 115 | 122 | 125 | 132 | 135 | 142 | 149 | 156 | 163 | 170 | 177 | 184 | 191 | 198 | 205 | 206 | ntt 207 | 208 | 209 | 210 | 211 | --------------------------------------------------------------------------------