├── .github ├── .gitignore ├── FUNDING.yml └── workflows │ ├── test-ggplot2-versions.yml │ └── R-CMD-check.yaml ├── LICENSE ├── R ├── zzz.R ├── ggExtra-package.R ├── rotateTextX.R ├── runExample.R ├── removeGrid.R ├── plotCount.R ├── ggMarginal-helpers.R ├── ggMarginal.R ├── ggMarginal-MarginalPlot.R └── ggMarginalGadget.R ├── .gitattributes ├── inst ├── img │ └── ggmarginal-gadget.png ├── gadgets │ └── ggmarginal │ │ ├── img │ │ └── ajax-loader.gif │ │ └── css │ │ └── app.css ├── examples-shiny │ └── ggMarginal │ │ ├── www │ │ ├── background.jpg │ │ ├── github-orange-right.png │ │ └── style.css │ │ ├── global.R │ │ ├── ui.R │ │ └── server.R ├── rstudio │ └── addins.dcf └── vignette_files │ └── ggExtra_files │ └── figure-markdown_strict │ ├── init-plot-1.png │ ├── removeGrid-1.png │ ├── plotCount-df-1.png │ ├── rotateTextX-1.png │ ├── ggmarginal-hist-1.png │ ├── ggmarginal-save-1.png │ ├── plotCount-table-1.png │ ├── unnamed-chunk-2-1.png │ ├── ggmarginal-basic-1.png │ ├── ggmarginal-large-1.png │ ├── ggmarginal-manual-1.png │ ├── ggmarginal-params-1.png │ ├── ggmarginal-grouping-1.png │ └── ggmarginal-extraparams-1.png ├── .Rbuildignore ├── tests ├── testthat.R └── testthat │ ├── test-ggMarginal.R │ ├── test-ggMarginal-errors.R │ ├── test-ggplot2-internals.R │ ├── helper-funs.R │ └── _snaps │ ├── 3.5.0 │ └── ggMarginal │ │ ├── basic-boxplot.svg │ │ ├── center-and-boundary-set.svg │ │ ├── widths-of-boxplots-are-the-same-within-a-marginal.svg │ │ └── x-axis-limits-for-histograms.svg │ └── 4.0.0 │ └── ggMarginal │ ├── basic-boxplot.svg │ ├── center-and-boundary-set.svg │ └── widths-of-boxplots-are-the-same-within-a-marginal.svg ├── .gitignore ├── .dockerignore ├── vignettes ├── ggExtra_files │ └── figure-markdown_strict │ │ ├── init-plot-1.png │ │ ├── removeGrid-1.png │ │ ├── plotCount-df-1.png │ │ ├── rotateTextX-1.png │ │ ├── ggmarginal-hist-1.png │ │ ├── ggmarginal-save-1.png │ │ ├── plotCount-table-1.png │ │ ├── unnamed-chunk-2-1.png │ │ ├── ggmarginal-basic-1.png │ │ ├── ggmarginal-large-1.png │ │ ├── ggmarginal-manual-1.png │ │ ├── ggmarginal-params-1.png │ │ ├── ggmarginal-grouping-1.png │ │ └── ggmarginal-extraparams-1.png └── ggExtra.Rmd ├── NAMESPACE ├── man ├── print.ggExtraPlot.Rd ├── runExample.Rd ├── ggExtra-package.Rd ├── rotateTextX.Rd ├── ggMarginalGadget.Rd ├── removeGrid.Rd ├── plotCount.Rd └── ggMarginal.Rd ├── dockerfile ├── DESCRIPTION ├── Makefile ├── NEWS.md └── README.md /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2015 2 | COPYRIGHT HOLDER: Dean Attali 3 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | utils::globalVariables(c(".data", "density")) 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | tests/figs/* linguist-generated=true 2 | 3 | -------------------------------------------------------------------------------- /R/ggExtra-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | "_PACKAGE" 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: daattali 4 | -------------------------------------------------------------------------------- /inst/img/ggmarginal-gadget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/img/ggmarginal-gadget.png -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^Makefile$ 4 | ^dockerfile$ 5 | ^\.github$ 6 | ^\.dockerignore$ 7 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | Sys.unsetenv('R_TESTS') 2 | 3 | library(testthat) 4 | library(ggExtra) 5 | 6 | test_check("ggExtra") 7 | -------------------------------------------------------------------------------- /inst/gadgets/ggmarginal/img/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/gadgets/ggmarginal/img/ajax-loader.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | *.Rproj 5 | inst/doc 6 | *.Rcheck 7 | *.tar.gz 8 | Rplots.pdf 9 | .Renviron 10 | -------------------------------------------------------------------------------- /inst/examples-shiny/ggMarginal/www/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/examples-shiny/ggMarginal/www/background.jpg -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | .github/ 4 | 5 | dockerfile 6 | .dockerignore 7 | 8 | .Rproj.user 9 | *.Rproj 10 | .RData 11 | .Rhistory 12 | -------------------------------------------------------------------------------- /inst/rstudio/addins.dcf: -------------------------------------------------------------------------------- 1 | Name: ggplot2 Marginal Plots 2 | Description: Add marginal plots to ggplot2 3 | Binding: ggMarginalGadgetAddin 4 | Interactive: true 5 | -------------------------------------------------------------------------------- /inst/examples-shiny/ggMarginal/www/github-orange-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/examples-shiny/ggMarginal/www/github-orange-right.png -------------------------------------------------------------------------------- /vignettes/ggExtra_files/figure-markdown_strict/init-plot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/vignettes/ggExtra_files/figure-markdown_strict/init-plot-1.png -------------------------------------------------------------------------------- /vignettes/ggExtra_files/figure-markdown_strict/removeGrid-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/vignettes/ggExtra_files/figure-markdown_strict/removeGrid-1.png -------------------------------------------------------------------------------- /vignettes/ggExtra_files/figure-markdown_strict/plotCount-df-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/vignettes/ggExtra_files/figure-markdown_strict/plotCount-df-1.png -------------------------------------------------------------------------------- /vignettes/ggExtra_files/figure-markdown_strict/rotateTextX-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/vignettes/ggExtra_files/figure-markdown_strict/rotateTextX-1.png -------------------------------------------------------------------------------- /vignettes/ggExtra_files/figure-markdown_strict/ggmarginal-hist-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/vignettes/ggExtra_files/figure-markdown_strict/ggmarginal-hist-1.png -------------------------------------------------------------------------------- /vignettes/ggExtra_files/figure-markdown_strict/ggmarginal-save-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/vignettes/ggExtra_files/figure-markdown_strict/ggmarginal-save-1.png -------------------------------------------------------------------------------- /vignettes/ggExtra_files/figure-markdown_strict/plotCount-table-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/vignettes/ggExtra_files/figure-markdown_strict/plotCount-table-1.png -------------------------------------------------------------------------------- /vignettes/ggExtra_files/figure-markdown_strict/unnamed-chunk-2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/vignettes/ggExtra_files/figure-markdown_strict/unnamed-chunk-2-1.png -------------------------------------------------------------------------------- /vignettes/ggExtra_files/figure-markdown_strict/ggmarginal-basic-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/vignettes/ggExtra_files/figure-markdown_strict/ggmarginal-basic-1.png -------------------------------------------------------------------------------- /vignettes/ggExtra_files/figure-markdown_strict/ggmarginal-large-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/vignettes/ggExtra_files/figure-markdown_strict/ggmarginal-large-1.png -------------------------------------------------------------------------------- /vignettes/ggExtra_files/figure-markdown_strict/ggmarginal-manual-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/vignettes/ggExtra_files/figure-markdown_strict/ggmarginal-manual-1.png -------------------------------------------------------------------------------- /vignettes/ggExtra_files/figure-markdown_strict/ggmarginal-params-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/vignettes/ggExtra_files/figure-markdown_strict/ggmarginal-params-1.png -------------------------------------------------------------------------------- /inst/vignette_files/ggExtra_files/figure-markdown_strict/init-plot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/vignette_files/ggExtra_files/figure-markdown_strict/init-plot-1.png -------------------------------------------------------------------------------- /inst/vignette_files/ggExtra_files/figure-markdown_strict/removeGrid-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/vignette_files/ggExtra_files/figure-markdown_strict/removeGrid-1.png -------------------------------------------------------------------------------- /vignettes/ggExtra_files/figure-markdown_strict/ggmarginal-grouping-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/vignettes/ggExtra_files/figure-markdown_strict/ggmarginal-grouping-1.png -------------------------------------------------------------------------------- /inst/vignette_files/ggExtra_files/figure-markdown_strict/plotCount-df-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/vignette_files/ggExtra_files/figure-markdown_strict/plotCount-df-1.png -------------------------------------------------------------------------------- /inst/vignette_files/ggExtra_files/figure-markdown_strict/rotateTextX-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/vignette_files/ggExtra_files/figure-markdown_strict/rotateTextX-1.png -------------------------------------------------------------------------------- /vignettes/ggExtra_files/figure-markdown_strict/ggmarginal-extraparams-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/vignettes/ggExtra_files/figure-markdown_strict/ggmarginal-extraparams-1.png -------------------------------------------------------------------------------- /inst/vignette_files/ggExtra_files/figure-markdown_strict/ggmarginal-hist-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/vignette_files/ggExtra_files/figure-markdown_strict/ggmarginal-hist-1.png -------------------------------------------------------------------------------- /inst/vignette_files/ggExtra_files/figure-markdown_strict/ggmarginal-save-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/vignette_files/ggExtra_files/figure-markdown_strict/ggmarginal-save-1.png -------------------------------------------------------------------------------- /inst/vignette_files/ggExtra_files/figure-markdown_strict/plotCount-table-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/vignette_files/ggExtra_files/figure-markdown_strict/plotCount-table-1.png -------------------------------------------------------------------------------- /inst/vignette_files/ggExtra_files/figure-markdown_strict/unnamed-chunk-2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/vignette_files/ggExtra_files/figure-markdown_strict/unnamed-chunk-2-1.png -------------------------------------------------------------------------------- /inst/vignette_files/ggExtra_files/figure-markdown_strict/ggmarginal-basic-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/vignette_files/ggExtra_files/figure-markdown_strict/ggmarginal-basic-1.png -------------------------------------------------------------------------------- /inst/vignette_files/ggExtra_files/figure-markdown_strict/ggmarginal-large-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/vignette_files/ggExtra_files/figure-markdown_strict/ggmarginal-large-1.png -------------------------------------------------------------------------------- /inst/vignette_files/ggExtra_files/figure-markdown_strict/ggmarginal-manual-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/vignette_files/ggExtra_files/figure-markdown_strict/ggmarginal-manual-1.png -------------------------------------------------------------------------------- /inst/vignette_files/ggExtra_files/figure-markdown_strict/ggmarginal-params-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/vignette_files/ggExtra_files/figure-markdown_strict/ggmarginal-params-1.png -------------------------------------------------------------------------------- /inst/vignette_files/ggExtra_files/figure-markdown_strict/ggmarginal-grouping-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/vignette_files/ggExtra_files/figure-markdown_strict/ggmarginal-grouping-1.png -------------------------------------------------------------------------------- /inst/vignette_files/ggExtra_files/figure-markdown_strict/ggmarginal-extraparams-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daattali/ggExtra/HEAD/inst/vignette_files/ggExtra_files/figure-markdown_strict/ggmarginal-extraparams-1.png -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(print,ggExtraPlot) 4 | export(ggMarginal) 5 | export(ggMarginalGadget) 6 | export(plotCount) 7 | export(removeGrid) 8 | export(removeGridX) 9 | export(removeGridY) 10 | export(rotateTextX) 11 | export(runExample) 12 | import(miniUI) 13 | import(shiny) 14 | -------------------------------------------------------------------------------- /inst/gadgets/ggmarginal/css/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: white; 3 | } 4 | #plot { 5 | position: absolute; 6 | left: 40%; 7 | z-index: 2; 8 | top: 45px; 9 | } 10 | #plot.recalculating { 11 | z-index: -1; 12 | } 13 | .left-panel-area { 14 | padding: 15px; 15 | border-right: 1px solid #eee; 16 | background: #fcfcfc; 17 | } 18 | #code { 19 | background: none; 20 | border: 0 none; 21 | } 22 | #error { 23 | position: absolute; 24 | z-index: 3; 25 | color: #FF0000; 26 | font-size: 1.2em; 27 | left: 40%; 28 | background: rgba(0,0,0,0.8); 29 | padding: 10px; 30 | } 31 | #plot-spinner { 32 | position: absolute; 33 | top: 50%; 34 | left: 70%; 35 | margin-top: -33px; 36 | margin-left: -33px; 37 | } 38 | -------------------------------------------------------------------------------- /man/print.ggExtraPlot.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ggMarginal.R 3 | \name{print.ggExtraPlot} 4 | \alias{print.ggExtraPlot} 5 | \title{Print a ggExtraPlot object} 6 | \usage{ 7 | \method{print}{ggExtraPlot}(x, newpage = grDevices::dev.interactive(), ...) 8 | } 9 | \arguments{ 10 | \item{x}{ggExtraPlot object.} 11 | 12 | \item{newpage}{Should a new page (i.e., an empty page) be drawn before the 13 | ggExtraPlot is drawn?} 14 | 15 | \item{...}{ignored} 16 | } 17 | \description{ 18 | \code{ggExtraPlot} objects are created from \code{ggMarginal}. This is the S3 19 | generic print method to print the result of the scatterplot with its marginal 20 | plots. 21 | } 22 | \seealso{ 23 | \code{\link{ggMarginal}} 24 | } 25 | \keyword{internal} 26 | -------------------------------------------------------------------------------- /man/runExample.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/runExample.R 3 | \name{runExample} 4 | \alias{runExample} 5 | \title{Run ggExtra example [DEPRECATED]} 6 | \usage{ 7 | runExample() 8 | } 9 | \description{ 10 | Launch a Shiny app that shows a demo of what can be done with 11 | \code{ggExtra::ggMarginal}. 12 | } 13 | \details{ 14 | This example is also 15 | \href{https://daattali.com/shiny/ggExtra-ggMarginal-demo/}{available online}.\cr\cr 16 | \strong{Deprecation Notice:} This function is no longer required since Shiny version 17 | 1.8.1 (March 2024). This function will be removed in a future release of \{ggExtra\}. 18 | You can use \code{shiny::runExample("ggMarginal", package = "ggExtra")} instead of 19 | \code{ggExtra::runExample()}. 20 | } 21 | \examples{ 22 | ## Only run this example in interactive R sessions 23 | if (interactive()) { 24 | runExample() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /inst/examples-shiny/ggMarginal/global.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(ggplot2) 3 | library(shinyjs) 4 | 5 | randoms <- data.frame( 6 | "Normal" = 7 | rnorm(1000, 150, 20), 8 | "Bimodal" = 9 | c(rnorm(500, 200, 50), rnorm(500, 400, 50)), 10 | "Uniform" = 11 | runif(1000, 100, 200), 12 | check.names = FALSE 13 | ) 14 | 15 | random_80_10_10 <- function(main, sec1, sec2) { 16 | rnd <- sample(10) 17 | if (rnd <= 8) main 18 | else if (rnd == 9) sec1 19 | else sec2 20 | } 21 | 22 | randoms$Class <- "A" 23 | randoms$Class[randoms$Normal - 150 >= 25] <- "B" 24 | randoms$Class[randoms$Normal - 150 <= -25] <- "C" 25 | randoms$Class[sample(1000, 70)] <- "A" 26 | randoms$Class[sample(1000, 40)] <- "B" 27 | randoms$Class[sample(1000, 40)] <- "C" 28 | 29 | datasets <- list( 30 | "Random distribution" = randoms, 31 | "iris" = iris, 32 | "cars" = cars, 33 | "faithful" = faithful, 34 | "rock" = rock 35 | ) 36 | -------------------------------------------------------------------------------- /man/ggExtra-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ggExtra-package.R 3 | \docType{package} 4 | \name{ggExtra-package} 5 | \alias{ggExtra} 6 | \alias{ggExtra-package} 7 | \title{ggExtra: Add Marginal Histograms to 'ggplot2', and More 'ggplot2' Enhancements} 8 | \description{ 9 | Collection of functions and layers to enhance 'ggplot2'. The flagship function is 'ggMarginal()', which can be used to add marginal histograms/boxplots/density plots to 'ggplot2' scatterplots. 10 | } 11 | \seealso{ 12 | Useful links: 13 | \itemize{ 14 | \item \url{https://github.com/daattali/ggExtra} 15 | \item \url{https://daattali.com/shiny/ggExtra-ggMarginal-demo/} 16 | \item Report bugs at \url{https://github.com/daattali/ggExtra/issues} 17 | } 18 | 19 | } 20 | \author{ 21 | \strong{Maintainer}: Dean Attali \email{daattali@gmail.com} 22 | 23 | Authors: 24 | \itemize{ 25 | \item Christopher Baker \email{chriscrewbaker@gmail.com} 26 | } 27 | 28 | } 29 | \keyword{internal} 30 | -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | FROM rocker/tidyverse:4.5.0 2 | 3 | RUN R -e "install.packages('devtools', repos='https://cran.rstudio.com/')" 4 | WORKDIR /pkg 5 | COPY . /pkg/ 6 | 7 | RUN R -e "devtools::install_deps('/pkg', dependencies = TRUE)" 8 | RUN R -e "devtools::install('/pkg')" 9 | 10 | RUN R -e "remotes::install_version('vdiffr', version='1.0.8', repos='https://cran.rstudio.com/', upgrade='never')" && \ 11 | R -e "remotes::install_version('svglite', version='2.2.1', repos='https://cran.rstudio.com/', upgrade='never')" && \ 12 | R -e "remotes::install_version('fontquiver', version='0.2.1', repos='https://cran.rstudio.com/', upgrade='never')" 13 | 14 | ARG GGPLOT2_VERSION="" 15 | RUN if [ ! -z "$GGPLOT2_VERSION" ]; then \ 16 | R -e "remotes::install_version('ggplot2', version='$GGPLOT2_VERSION', repos='https://cran.rstudio.com/', upgrade='never')"; \ 17 | fi 18 | 19 | ENV RunVisualTests=yes 20 | ENV _R_CHECK_TESTS_NLINES_=0 21 | 22 | CMD ["R", "-e", "setwd('/pkg'); devtools::test(stop_on_failure = TRUE)"] 23 | -------------------------------------------------------------------------------- /R/rotateTextX.R: -------------------------------------------------------------------------------- 1 | #' Rotate x axis labels 2 | #' 3 | #' Rotate the labels on the x axis to be rotated so that they are vertical, 4 | #' which is often useful when there are many overlapping labels along the x 5 | #' axis. 6 | #' 7 | #' This function is quite simple, but it can be useful if you don't have 8 | #' the exact syntax to do this engraved in your head. 9 | #' 10 | #' @param angle Angle (in [0, 360]) 11 | #' @param hjust Horizontal justification (in [0, 1]) 12 | #' @param vjust Vertical justification (in [0, 1]) 13 | #' @return A ggplot2 layer that can be added to an existing ggplot2 object. 14 | #' @examples 15 | #' df <- data.frame(x = paste("Letter", LETTERS, sep = "_"), 16 | #' y = seq_along(LETTERS)) 17 | #' p <- ggplot2::ggplot(df, ggplot2::aes(x, y)) + ggplot2::geom_point() 18 | #' p + rotateTextX() 19 | #' @export 20 | rotateTextX <- function(angle = 90, hjust = 1, vjust = 0.5) { 21 | ggplot2::theme( 22 | axis.text.x = ggplot2::element_text(angle = angle, hjust = hjust, vjust = vjust) 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/test-ggplot2-versions.yml: -------------------------------------------------------------------------------- 1 | name: Test ggExtra with multiple ggplot2 versions 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | test-ggplot2-versions: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | ggplot2_version: ["3.5.0", "4.0.0"] 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | 18 | - name: Build Docker image 19 | run: | 20 | docker build -t ggextra-test-${{ matrix.ggplot2_version }} \ 21 | --build-arg GGPLOT2_VERSION=${{ matrix.ggplot2_version }} . 22 | 23 | - name: Run tests 24 | run: | 25 | docker run --rm ggextra-test-${{ matrix.ggplot2_version }} 26 | 27 | - name: Upload test results (on failure) 28 | if: failure() 29 | uses: actions/upload-artifact@v4 30 | with: 31 | name: test-results-ggplot2-${{ matrix.ggplot2_version }} 32 | path: | 33 | tests/testthat/ 34 | retention-days: 7 35 | -------------------------------------------------------------------------------- /man/rotateTextX.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rotateTextX.R 3 | \name{rotateTextX} 4 | \alias{rotateTextX} 5 | \title{Rotate x axis labels} 6 | \usage{ 7 | rotateTextX(angle = 90, hjust = 1, vjust = 0.5) 8 | } 9 | \arguments{ 10 | \item{angle}{Angle (in [0, 360])} 11 | 12 | \item{hjust}{Horizontal justification (in [0, 1])} 13 | 14 | \item{vjust}{Vertical justification (in [0, 1])} 15 | } 16 | \value{ 17 | A ggplot2 layer that can be added to an existing ggplot2 object. 18 | } 19 | \description{ 20 | Rotate the labels on the x axis to be rotated so that they are vertical, 21 | which is often useful when there are many overlapping labels along the x 22 | axis. 23 | } 24 | \details{ 25 | This function is quite simple, but it can be useful if you don't have 26 | the exact syntax to do this engraved in your head. 27 | } 28 | \examples{ 29 | df <- data.frame(x = paste("Letter", LETTERS, sep = "_"), 30 | y = seq_along(LETTERS)) 31 | p <- ggplot2::ggplot(df, ggplot2::aes(x, y)) + ggplot2::geom_point() 32 | p + rotateTextX() 33 | } 34 | -------------------------------------------------------------------------------- /man/ggMarginalGadget.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ggMarginalGadget.R 3 | \name{ggMarginalGadget} 4 | \alias{ggMarginalGadget} 5 | \title{ggMarginal gadget} 6 | \usage{ 7 | ggMarginalGadget(plot) 8 | } 9 | \arguments{ 10 | \item{plot}{A ggplot2 scatterplot} 11 | } 12 | \value{ 13 | An object of class \code{ggExtraPlot}. This object can be printed to 14 | show the marginal plots or saved using any of the typical image-saving functions 15 | } 16 | \description{ 17 | This gadget and addin allow you to select a ggplot2 plot and interactively 18 | use \code{ggMarginal} to build marginal plots on top of your scatterplot. 19 | } 20 | \note{ 21 | To use the RStudio addin, highlight the code for a plot in RStudio and 22 | select \emph{ggplot2 Marginal Plots} from the RStudio \emph{Addins} menu. This will 23 | embed the marginal plots code into your script. Alternatively, you can call 24 | \code{ggMarginalGadget()} with a ggplot2 plot, and the gadget will return 25 | a plot object. 26 | } 27 | \examples{ 28 | if (interactive()) { 29 | plot <- ggplot2::ggplot(mtcars, ggplot2::aes(wt, mpg)) + ggplot2::geom_point() 30 | plot2 <- ggMarginalGadget(plot) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /man/removeGrid.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/removeGrid.R 3 | \name{removeGrid} 4 | \alias{removeGrid} 5 | \alias{removeGridX} 6 | \alias{removeGridY} 7 | \title{Remove grid lines from ggplot2} 8 | \usage{ 9 | removeGrid(x = TRUE, y = TRUE) 10 | 11 | removeGridX() 12 | 13 | removeGridY() 14 | } 15 | \arguments{ 16 | \item{x}{Whether to remove grid lines from the x axis.} 17 | 18 | \item{y}{Whether to remove grid lines from the y axis.} 19 | } 20 | \value{ 21 | A ggplot2 layer that can be added to an existing ggplot2 object. 22 | } 23 | \description{ 24 | Remove grid lines from a ggplot2 plot, to have a cleaner and simpler 25 | plot 26 | } 27 | \details{ 28 | Minor grid lines are always removed. 29 | 30 | \code{removeGrid} removes the major grid lines from the x and/or y axis 31 | (both by default). 32 | 33 | \code{removeGridX} is a shortcut for \code{removeGrid(x = TRUE, y = FALSE)} 34 | 35 | \code{removeGridY} is a shortcut for \code{removeGrid(x = FALSE, y = TRUE)} 36 | } 37 | \examples{ 38 | df <- data.frame(x = 1:50, y = 1:50) 39 | p <- ggplot2::ggplot(df, ggplot2::aes(x, y)) + ggplot2::geom_point() 40 | p + removeGrid() 41 | p + removeGrid(y = FALSE) 42 | p + removeGridX() 43 | } 44 | -------------------------------------------------------------------------------- /R/runExample.R: -------------------------------------------------------------------------------- 1 | #' Run ggExtra example [DEPRECATED] 2 | #' 3 | #' Launch a Shiny app that shows a demo of what can be done with 4 | #' \code{ggExtra::ggMarginal}. 5 | #' 6 | #' This example is also 7 | #' \href{https://daattali.com/shiny/ggExtra-ggMarginal-demo/}{available online}.\cr\cr 8 | #' \strong{Deprecation Notice:} This function is no longer required since Shiny version 9 | #' 1.8.1 (March 2024). This function will be removed in a future release of \{ggExtra\}. 10 | #' You can use \code{shiny::runExample("ggMarginal", package = "ggExtra")} instead of 11 | #' \code{ggExtra::runExample()}. 12 | #' 13 | #' @examples 14 | #' ## Only run this example in interactive R sessions 15 | #' if (interactive()) { 16 | #' runExample() 17 | #' } 18 | #' @export 19 | runExample <- function() { 20 | message("WARNING: `ggExtra::runExample()` is deprecated. Please upgrade to {shiny} version 1.8.1 ", 21 | "and use `shiny::runExample(\"ggMarginal\", package = \"ggExtra\")` instead.\n") 22 | appDir <- system.file("examples-shiny", "ggMarginal", package = "ggExtra") 23 | if (appDir == "") { 24 | stop("Could not find example directory. Try re-installing `ggExtra`.", 25 | call. = FALSE 26 | ) 27 | } 28 | 29 | shiny::runApp(appDir, display.mode = "normal") 30 | } 31 | -------------------------------------------------------------------------------- /tests/testthat/test-ggMarginal.R: -------------------------------------------------------------------------------- 1 | runMarginalTests <- function() { 2 | withr::local_envvar(GGEXTRA_QUIET = "1") 3 | 4 | sapply(names(funList), function(x) { 5 | test_that(x, { 6 | sapply( 7 | names(funList[[x]]), 8 | function(y) vdiffr::expect_doppelganger( 9 | title = y, 10 | fig = funList[[x]][[y]](), 11 | variant = as.character(utils::packageVersion('ggplot2')), 12 | cran = TRUE # this is needed so that it will run with `R CMD check --as-cran`, but it 13 | # will not run on CRAN because we have our own check using `shouldTestVisual()` 14 | ) 15 | ) 16 | }) 17 | }) 18 | } 19 | 20 | # RunVisualTests is set to "yes" in dockerfile, which means shouldTestVisual() 21 | # will return TRUE only when it's run inside a docker container (i.e., it will 22 | # return FALSE on CRAN). 23 | shouldTestVisual <- function() { 24 | Sys.getenv("RunVisualTests") == "yes" 25 | } 26 | 27 | if (shouldTestVisual()) { 28 | runMarginalTests() 29 | } else { 30 | names <- list.files(test_path("_snaps"), pattern = "\\.svg$", recursive = TRUE) 31 | names <- unique(basename(names)) 32 | for(nm in names) announce_snapshot_file(name = nm) # announce the snapshots so they don't get deleted 33 | skip() 34 | } 35 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: ggExtra 2 | Title: Add Marginal Histograms to 'ggplot2', and More 'ggplot2' Enhancements 3 | Version: 0.11.0.9000 4 | Authors@R: c( 5 | person("Dean", "Attali", , "daattali@gmail.com", role = c("aut", "cre")), 6 | person("Christopher", "Baker", , "chriscrewbaker@gmail.com", role = "aut") 7 | ) 8 | Description: Collection of functions and layers to enhance 'ggplot2'. The 9 | flagship function is 'ggMarginal()', which can be used to add marginal 10 | histograms/boxplots/density plots to 'ggplot2' scatterplots. 11 | URL: https://github.com/daattali/ggExtra, https://daattali.com/shiny/ggExtra-ggMarginal-demo/ 12 | BugReports: https://github.com/daattali/ggExtra/issues 13 | Depends: 14 | R (>= 3.1.0) 15 | Imports: 16 | colourpicker (>= 1.0), 17 | ggplot2 (>= 2.2.0), 18 | grDevices, 19 | grid (>= 3.1.3), 20 | gtable (>= 0.2.0), 21 | miniUI (>= 0.1.1), 22 | scales (>= 0.2.0), 23 | shiny (>= 0.13.0), 24 | shinyjs (>= 0.5.2), 25 | utils, 26 | R6 27 | Suggests: 28 | knitr (>= 1.7), 29 | rmarkdown, 30 | rstudioapi (>= 0.5), 31 | testthat (>= 3.0.0), 32 | vdiffr, 33 | fontquiver, 34 | svglite, 35 | withr, 36 | devtools 37 | License: MIT + file LICENSE 38 | SystemRequirements: pandoc with https support 39 | VignetteBuilder: knitr 40 | RoxygenNote: 7.2.3 41 | Encoding: UTF-8 42 | Config/testthat/edition: 3 43 | -------------------------------------------------------------------------------- /tests/testthat/test-ggMarginal-errors.R: -------------------------------------------------------------------------------- 1 | test_that("ggMarginal info messages for ignored parameters work", { 2 | p <- ggplot2::ggplot(mtcars, ggplot2::aes(x = wt, y = drat, colour = factor(vs))) + 3 | ggplot2::geom_point() 4 | 5 | expect_no_message(ggMarginal(p)) 6 | expect_no_message(ggMarginal(p, color = "red")) 7 | expect_no_message(ggMarginal(p, groupColour = TRUE)) 8 | expect_message(ggMarginal(p, color = "red", groupColour = TRUE), "ignored") 9 | expect_no_message(ggMarginal(p, fill = "red")) 10 | expect_no_message(ggMarginal(p, groupFill = TRUE)) 11 | expect_message(ggMarginal(p, fill = "red", groupFill = TRUE), "ignored") 12 | }) 13 | 14 | test_that("ggMarginal error messages work", { 15 | expect_error(ggMarginal(), "must be provided") 16 | 17 | expect_error( 18 | ggMarginal(ggplot2::ggplot(mtcars, ggplot2::aes(x = wt, y = drat))), 19 | "layer" 20 | ) 21 | expect_no_error( 22 | ggMarginal(ggplot2::ggplot(mtcars, ggplot2::aes(x = wt, y = drat)) + ggplot2::geom_jitter()) 23 | ) 24 | 25 | expect_error( 26 | ggMarginal( 27 | ggplot2::ggplot(mtcars, ggplot2::aes(x = wt, y = drat)) + ggplot2::geom_point(), 28 | groupColour = TRUE 29 | ), 30 | "mapped" 31 | ) 32 | expect_no_error( 33 | ggMarginal( 34 | ggplot2::ggplot(mtcars, ggplot2::aes(x = wt, y = drat, color = factor(vs))) + ggplot2::geom_point(), 35 | groupColour = TRUE 36 | ) 37 | ) 38 | }) 39 | -------------------------------------------------------------------------------- /man/plotCount.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/plotCount.R 3 | \name{plotCount} 4 | \alias{plotCount} 5 | \title{Plot count data with ggplot2} 6 | \usage{ 7 | plotCount(x, ...) 8 | } 9 | \arguments{ 10 | \item{x}{A data.frame or table. See 'Details' for more information.} 11 | 12 | \item{...}{Extra parameters to pass to the barplot. Any parameter that 13 | \code{geom_bar()} accepts can be used. For example, \code{fill = "red"} can 14 | be used fto make the bars red.} 15 | } 16 | \value{ 17 | A ggplot2 object that can be have more layers added onto it. 18 | } 19 | \description{ 20 | Create a bar plot of count (frequency) data that is stored in a data.frame 21 | or table. 22 | } 23 | \details{ 24 | The argument to this function is expected to be either a data.frame or a 25 | table. 26 | 27 | If a data.frame is provided, it must have exactly two columns: 28 | the first column contains the unique values in the data, and the second 29 | column is the corresponding integer frequencies to each value. 30 | 31 | If a table is provided, it must have exactly one row: the rownames are the 32 | unique values in the data, and the row values are the corresponding integer 33 | frequencies to each value. 34 | } 35 | \examples{ 36 | plotCount(table(infert$education)) 37 | df <- data.frame("vehicle" = c("bicycle", "car", "unicycle", "Boeing747"), 38 | "NumWheels" = c(2, 4, 1, 16)) 39 | plotCount(df) + removeGridX() 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | 8 | name: R-CMD-check.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | R-CMD-check: 14 | runs-on: ${{ matrix.config.os }} 15 | 16 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | config: 22 | - {os: macos-latest, r: 'release'} 23 | - {os: windows-latest, r: 'release'} 24 | - {os: ubuntu-latest, r: 'release'} 25 | - {os: ubuntu-latest, r: 'oldrel-1'} 26 | 27 | env: 28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 29 | R_KEEP_PKG_SOURCE: yes 30 | 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - uses: r-lib/actions/setup-pandoc@v2 35 | 36 | - uses: r-lib/actions/setup-r@v2 37 | with: 38 | r-version: ${{ matrix.config.r }} 39 | http-user-agent: ${{ matrix.config.http-user-agent }} 40 | use-public-rspm: true 41 | 42 | - uses: r-lib/actions/setup-r-dependencies@v2 43 | with: 44 | extra-packages: any::rcmdcheck 45 | needs: check 46 | 47 | - uses: r-lib/actions/check-r-package@v2 48 | with: 49 | upload-snapshots: true 50 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 51 | -------------------------------------------------------------------------------- /inst/examples-shiny/ggMarginal/www/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: url("background.jpg") repeat; 3 | } 4 | #header { 5 | text-align: center; 6 | color: #fdfdfd; 7 | text-shadow: 0 0 1px #000; 8 | padding: 20px 0 30px; 9 | border-bottom: 1px solid #ddd; 10 | margin: 0 -15px; 11 | /* background taken from http://uigradients.com/ */ 12 | background: #2C3E50; /* fallback for old browsers */ 13 | background: -webkit-linear-gradient(to left, #2C3E50 , #4CA1AF); /* Chrome 10-25, Safari 5.1-6 */ 14 | background: linear-gradient(to left, #2C3E50 , #4CA1AF); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ 15 | } 16 | 17 | #title { 18 | font-size: 5em; 19 | text-shadow: 0 0 5px #000; 20 | } 21 | 22 | #subtitle { 23 | font-size: 2em; 24 | } 25 | 26 | #subsubtitle { 27 | font-size: 1.3em; 28 | } 29 | 30 | #subsubtitle a { 31 | color: #fdfdfd; 32 | text-decoration: underline; 33 | } 34 | #loading-content { 35 | position: absolute; 36 | background: #000000; 37 | opacity: 0.9; 38 | z-index: 100; 39 | left: 0; 40 | right: 0; 41 | height: 100%; 42 | text-align: center; 43 | color: #FFFFFF; 44 | } 45 | 46 | #app-content { 47 | margin-top: 25px; 48 | } 49 | 50 | .settings { 51 | background: #FEFEFE; 52 | padding-top: 0; 53 | } 54 | 55 | .settings .settings-title { 56 | text-align: center; 57 | font-weight: bold; 58 | } 59 | 60 | #plot img { 61 | outline: 1px solid #999999; 62 | } 63 | 64 | #code { 65 | margin-top: 15px; 66 | background: #FEFEFE; 67 | padding: 10px; 68 | border: 1px solid #999; 69 | } -------------------------------------------------------------------------------- /R/removeGrid.R: -------------------------------------------------------------------------------- 1 | #' Remove grid lines from ggplot2 2 | #' 3 | #' Remove grid lines from a ggplot2 plot, to have a cleaner and simpler 4 | #' plot 5 | #' 6 | #' Minor grid lines are always removed. 7 | #' 8 | #' \code{removeGrid} removes the major grid lines from the x and/or y axis 9 | #' (both by default). 10 | #' 11 | #' \code{removeGridX} is a shortcut for \code{removeGrid(x = TRUE, y = FALSE)} 12 | #' 13 | #' \code{removeGridY} is a shortcut for \code{removeGrid(x = FALSE, y = TRUE)} 14 | #' 15 | #' @param x Whether to remove grid lines from the x axis. 16 | #' @param y Whether to remove grid lines from the y axis. 17 | #' @return A ggplot2 layer that can be added to an existing ggplot2 object. 18 | #' @examples 19 | #' df <- data.frame(x = 1:50, y = 1:50) 20 | #' p <- ggplot2::ggplot(df, ggplot2::aes(x, y)) + ggplot2::geom_point() 21 | #' p + removeGrid() 22 | #' p + removeGrid(y = FALSE) 23 | #' p + removeGridX() 24 | #' @name removeGrid 25 | NULL 26 | 27 | #' @export 28 | #' @rdname removeGrid 29 | removeGrid <- function(x = TRUE, y = TRUE) { 30 | p <- ggplot2::theme(panel.grid.minor = ggplot2::element_blank()) 31 | if (x) { 32 | p <- p + 33 | ggplot2::theme(panel.grid.major.x = ggplot2::element_blank()) 34 | } 35 | if (y) { 36 | p <- p + 37 | ggplot2::theme(panel.grid.major.y = ggplot2::element_blank()) 38 | } 39 | 40 | p 41 | } 42 | 43 | #' @export 44 | #' @rdname removeGrid 45 | removeGridX <- function() { 46 | removeGrid(x = TRUE, y = FALSE) 47 | } 48 | 49 | #' @export 50 | #' @rdname removeGrid 51 | removeGridY <- function() { 52 | removeGrid(x = FALSE, y = TRUE) 53 | } 54 | -------------------------------------------------------------------------------- /tests/testthat/test-ggplot2-internals.R: -------------------------------------------------------------------------------- 1 | test_that("ggExtra's accession of ggplot2 title grobs works" , { 2 | 3 | titleP <- function(title) { 4 | basicScatP() + title 5 | } 6 | titleList <- list( 7 | noSub = ggplot2::ggtitle("hi"), 8 | sub = ggplot2::ggtitle("there", subtitle = "friend") 9 | ) 10 | 11 | expect_true({ 12 | gTest <- vapply( 13 | titleList, 14 | function(x) length(ggExtra:::getTitleGrobs(titleP(x))) == 2, 15 | logical(1) 16 | ) 17 | all(gTest) 18 | }) 19 | expect_true({ 20 | gTest <- vapply( 21 | titleList, 22 | function(x) !is.null(ggplot2::ggplot_build(titleP(x))$plot$labels$title), 23 | logical(1) 24 | ) 25 | all(gTest) 26 | }) 27 | 28 | expect_true({ 29 | is.null(ggplot2::ggplot_build(titleP(ggplot2::theme()))$plot$labels$title) 30 | }) 31 | 32 | }) 33 | 34 | test_that("ggplot2 models scatter plot data as expected" , { 35 | 36 | scatPbuilt <- ggplot2::ggplot_build(basicScatP()) 37 | scatDF <- scatPbuilt[["data"]][[1]] 38 | expect_true({ 39 | "x" %in% colnames(scatDF) && "y" %in% colnames(scatDF) 40 | }) 41 | 42 | }) 43 | 44 | test_that("ggplot2 uses positive integers for groups and -1 for no groups" , { 45 | 46 | p1 <- ggplot2::ggplot( 47 | mtcars, ggplot2::aes(x = wt, y = mpg, colour = factor(gear)) 48 | ) + ggplot2::geom_point() 49 | bp <- ggplot2::ggplot_build(p1) 50 | grp_vals <- unique(bp$data[[1]]$group) 51 | expect_true(all(grp_vals[order(grp_vals)] == c(1, 2, 3))) 52 | 53 | bp2 <- ggplot2::ggplot_build(basicScatP()) 54 | grp_vals2 <- unique(bp2$data[[1]]$group) 55 | expect_true(grp_vals2 == -1L) 56 | 57 | }) 58 | -------------------------------------------------------------------------------- /R/plotCount.R: -------------------------------------------------------------------------------- 1 | #' Plot count data with ggplot2 2 | #' 3 | #' Create a bar plot of count (frequency) data that is stored in a data.frame 4 | #' or table. 5 | #' 6 | #' The argument to this function is expected to be either a data.frame or a 7 | #' table. 8 | #' 9 | #' If a data.frame is provided, it must have exactly two columns: 10 | #' the first column contains the unique values in the data, and the second 11 | #' column is the corresponding integer frequencies to each value. 12 | #' 13 | #' If a table is provided, it must have exactly one row: the rownames are the 14 | #' unique values in the data, and the row values are the corresponding integer 15 | #' frequencies to each value. 16 | #' 17 | #' @param x A data.frame or table. See 'Details' for more information. 18 | #' @param ... Extra parameters to pass to the barplot. Any parameter that 19 | #' \code{geom_bar()} accepts can be used. For example, \code{fill = "red"} can 20 | #' be used fto make the bars red. 21 | #' @return A ggplot2 object that can be have more layers added onto it. 22 | #' @examples 23 | #' plotCount(table(infert$education)) 24 | #' df <- data.frame("vehicle" = c("bicycle", "car", "unicycle", "Boeing747"), 25 | #' "NumWheels" = c(2, 4, 1, 16)) 26 | #' plotCount(df) + removeGridX() 27 | #' @export 28 | plotCount <- function(x, ...) { 29 | x <- data.frame(x) 30 | 31 | stopifnot( 32 | ncol(x) == 2, 33 | is.numeric(x[, 2]), 34 | all.equal(as.integer(x[, 2]), x[, 2]), 35 | length(x[, 1]) == length(unique(x[, 1])) 36 | ) 37 | 38 | p <- 39 | ggplot2::ggplot(x) + 40 | ggplot2::aes(.data[[colnames(x)[1]]], .data[[colnames(x)[2]]]) + 41 | ggplot2::geom_bar(stat = "identity", ...) 42 | 43 | p 44 | } 45 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #!usr/bin/make -f 2 | # All commands are run as R functions rather than shell commands so that it will work easily on any Windows machine, even if the Windows machine isn't properly set up with all the right tools 3 | 4 | all: README.md 5 | 6 | clean: 7 | Rscript -e 'suppressWarnings(file.remove("README.md", "vignettes/ggExtra.md"))' 8 | 9 | .PHONY: all clean build-image test 10 | .DELETE_ON_ERROR: 11 | .SECONDARY: 12 | 13 | README.md : vignettes/ggExtra.Rmd 14 | # echo "Rendering the ggExtra vignette" 15 | Rscript -e 'rmarkdown::render("vignettes/ggExtra.Rmd", output_format = "md_document")' 16 | # echo "Correcting image paths" 17 | # sed -i -- 's,../inst,inst,g' vignettes/ggExtra.md 18 | Rscript -e 'file <- gsub("\\.\\./inst", "inst", readLines("vignettes/ggExtra.md")); writeLines(file, "vignettes/ggExtra.md")' 19 | Rscript -e 'dir.create("inst/vignette_files/", showWarnings = FALSE);file.copy("vignettes/ggExtra_files/", "inst/vignette_files/", overwrite = TRUE, recursive = TRUE)' 20 | Rscript -e 'file <- gsub("ggExtra_files", "inst/vignette_files/ggExtra_files", readLines("vignettes/ggExtra.md")); writeLines(file, "vignettes/ggExtra.md")' 21 | Rscript -e 'file <- gsub("\\\\```", "```", readLines("vignettes/ggExtra.md")); writeLines(file, "vignettes/ggExtra.md")' 22 | # echo "Copying output to README.md" 23 | # cp vignettes/ggExtra.md README.md 24 | Rscript -e 'file.copy("vignettes/ggExtra.md", "README.md", overwrite = TRUE)' 25 | Rscript -e 'suppressWarnings(file.remove("vignettes/ggExtra.md"))' 26 | 27 | build-image: 28 | docker build -t ggextra-image . 29 | 30 | test: 31 | docker run --rm -v `pwd`:/home/ggExtra ggextra-image \ 32 | /bin/bash -c 'R CMD build ../ggExtra && R CMD check ggExtra_* --as-cran --no-manual' 33 | -------------------------------------------------------------------------------- /inst/examples-shiny/ggMarginal/ui.R: -------------------------------------------------------------------------------- 1 | share <- list( 2 | title = "ggMarginal (from ggExtra package)", 3 | url = "https://daattali.com/shiny/ggExtra-ggMarginal-demo/", 4 | image = "https://daattali.com/shiny/img/ggmarginal.png", 5 | description = "Add marginal plots to ggplot2.", 6 | twitter_user = "daattali" 7 | ) 8 | 9 | fluidPage( 10 | shinydisconnect::disconnectMessage2(), 11 | title = "ggMarginal - add marginal plots to ggplot2", 12 | tags$head( 13 | includeCSS(file.path('www', 'style.css')), 14 | 15 | # Favicon 16 | tags$link(rel = "shortcut icon", type="image/x-icon", href="https://daattali.com/shiny/img/favicon.ico"), 17 | 18 | # Facebook OpenGraph tags 19 | tags$meta(property = "og:title", content = share$title), 20 | tags$meta(property = "og:type", content = "website"), 21 | tags$meta(property = "og:url", content = share$url), 22 | tags$meta(property = "og:image", content = share$image), 23 | tags$meta(property = "og:description", content = share$description), 24 | 25 | # Twitter summary cards 26 | tags$meta(name = "twitter:card", content = "summary"), 27 | tags$meta(name = "twitter:site", content = paste0("@", share$twitter_user)), 28 | tags$meta(name = "twitter:creator", content = paste0("@", share$twitter_user)), 29 | tags$meta(name = "twitter:title", content = share$title), 30 | tags$meta(name = "twitter:description", content = share$description), 31 | tags$meta(name = "twitter:image", content = share$image) 32 | ), 33 | tags$a( 34 | href="https://github.com/daattali/ggExtra", 35 | tags$img(style="position: absolute; top: 0; right: 0; border: 0;", 36 | src="github-orange-right.png", 37 | alt="Fork me on GitHub") 38 | ), 39 | useShinyjs(), 40 | 41 | div(id = "header", 42 | div(id = "title", 43 | "ggMarginal" 44 | ), 45 | div(id = "subtitle", 46 | "Add marginal plots to ggplot2 (from ggExtra package)"), 47 | div(id = "subsubtitle", 48 | "By", 49 | tags$a(href = "https://deanattali.com/", "Dean Attali"), 50 | HTML("•"), 51 | "Package available", 52 | tags$a(href = "https://github.com/daattali/ggExtra", "on GitHub"), 53 | HTML("•"), 54 | tags$a(href = "https://daattali.com/shiny/", "More apps"), "by Dean" 55 | ) 56 | ), 57 | 58 | div(id = "loading-content", h2("Loading...")), 59 | fluidRow(id = "app-content", 60 | column(3, wellPanel( 61 | class = "settings", 62 | h3(class = "settings-title", "Main plot"), 63 | selectInput("dataset", "Choose a dataset:", names(datasets)), 64 | uiOutput("x_var_select"), 65 | uiOutput("y_var_select"), 66 | uiOutput("col_var_select"), 67 | sliderInput("font_size", "Font size", 0, 50, 15, 1) 68 | )), 69 | 70 | column(3, wellPanel( 71 | class = "settings", 72 | h3(class = "settings-title", "Marginal plots"), 73 | checkboxInput("show_marginal", "Show marginal plots", TRUE), 74 | 75 | div(id = "marginal-settings", 76 | selectInput("type", NULL, c("density", "histogram", "boxplot", "violin", "densigram")), 77 | selectInput("margins", "Which margins?", c("both", "x", "y")), 78 | conditionalPanel( 79 | condition = "input.margins != 'y'", 80 | selectInput("xtrans", "X axis transformation", c("none", "log","reverse")) 81 | ), 82 | conditionalPanel( 83 | condition = "input.margins != 'x'", 84 | selectInput("ytrans", "Y axis transformation", c("none", "log","reverse")) 85 | ), 86 | checkboxInput("groupColour", "Show groups as 'colour'", FALSE), 87 | checkboxInput("groupFill", "Show groups as 'fill'", FALSE), 88 | sliderInput("size", 89 | "Size ratio of main plot:marginal plots", 90 | 1, 5, 5, 0.5), 91 | colourpicker::colourInput("col", "Marginal plot colour", "red", 92 | showColour = "background", allowTransparent = TRUE), 93 | colourpicker::colourInput("fill", "Marginal plot fill colour", "orange", 94 | showColour = "background", allowTransparent = TRUE) 95 | ) 96 | )), 97 | 98 | column(6, 99 | plotOutput("plot"), 100 | pre(id = "code") 101 | ) 102 | ) 103 | ) 104 | -------------------------------------------------------------------------------- /R/ggMarginal-helpers.R: -------------------------------------------------------------------------------- 1 | # Misc helpers found in the beginning of ggMarginal --------------------------- 2 | 3 | toParamList <- function(exPrm, xPrm, yPrm) { 4 | list( 5 | exPrm = exPrm, 6 | xPrm = xPrm, 7 | yPrm = yPrm 8 | ) 9 | } 10 | 11 | reconcileColParamApply <- function(prmL) { 12 | lapply(prmL, reconcileColParam) 13 | } 14 | 15 | reconcileColParam <- function(paramEl) { 16 | col_vrnts <- c("colour", "color", "col") 17 | vrnts_exts <- vapply( 18 | col_vrnts, function(x) !is.null(paramEl[[x]]), logical(1), USE.NAMES = TRUE 19 | ) 20 | 21 | if (any(vrnts_exts)) { 22 | paramEl$colour <- paramEl[[names(vrnts_exts[vrnts_exts])]] 23 | paramEl$col <- NULL 24 | paramEl$color <- NULL 25 | } 26 | 27 | paramEl 28 | } 29 | 30 | reconcileScatPlot <- function(p, data, x, y) { 31 | if (missing(p)) { 32 | if (missing(data) || missing(x) || missing(y)) { 33 | stop("`data`, `x`, and `y` must be provided if `p` is not provided", 34 | call. = FALSE 35 | ) 36 | } 37 | p <- ggplot2::ggplot(data, ggplot2::aes(.data[[x]], .data[[y]])) + 38 | ggplot2::geom_point() 39 | } 40 | p 41 | } 42 | 43 | # Pull out the title and subtitle grobs for a plot, after we have checked to 44 | # make sure there is a title. Note: plot.title and plot.subtitle will actually 45 | # always exist (I believe) in recent versions of ggplot2, even if the user 46 | # doesn't specify a title/subtitle. In these cases, the title/subtitle grobs 47 | # will be "zeroGrobs." However, a 'label' won't exist 48 | # (i.e, !is.null(pb$plot$labels$title) will be true) when there is no title, 49 | # so it's not like we will be needlessly adding zeroGrobs to our plot (though 50 | # it wouldn't be a problem, even if we did add the zeroGrobs - it would just take 51 | # a little longer. 52 | getTitleGrobs <- function(p) { 53 | grobs <- ggplot2::ggplotGrob(p)$grobs 54 | gindTitle <- vapply( 55 | grobs, function(x) grepl(pattern = "plot\\.title", x$name), logical(1) 56 | ) 57 | gindSub <- vapply( 58 | grobs, function(x) grepl(pattern = "plot\\.subtitle", x$name), logical(1) 59 | ) 60 | list( 61 | titleG = grobs[gindTitle][[1]], 62 | subTitleG = grobs[gindSub][[1]] 63 | ) 64 | } 65 | 66 | # Functions to add marginal plots to scatter plot --------------------------- 67 | 68 | addTopMargPlot <- function(ggMargGrob, top, size) { 69 | panelPos <- getPanelPos(ggMargGrob) 70 | topMargG <- getMargGrob(top) 71 | gt <- gtable::gtable_add_rows( 72 | x = ggMargGrob, 73 | heights = grid::unit(1 / size, "null"), pos = 0 74 | ) 75 | gtable::gtable_add_grob( 76 | x = gt, grobs = topMargG, t = 1, b = 1, 77 | l = panelPos[["l"]], r = panelPos[["r"]], 78 | z = Inf, clip = "on", name = "topMargPlot" 79 | ) 80 | } 81 | 82 | addRightMargPlot <- function(ggMargGrob, right, size) { 83 | panelPos <- getPanelPos(ggMargGrob) 84 | rightMargG <- getMargGrob(right) 85 | gt <- gtable::gtable_add_cols( 86 | x = ggMargGrob, 87 | widths = grid::unit(1 / size, "null"), 88 | pos = -1 89 | ) 90 | gtable::gtable_add_grob( 91 | x = gt, grobs = rightMargG, t = panelPos[["t"]], 92 | b = panelPos[["b"]], r = ncol(gt), l = ncol(gt), 93 | z = Inf, clip = "on", name = "rightMargPlot" 94 | ) 95 | } 96 | 97 | # Helper functions for appending the tableGrob that represents the scatter-plot 98 | # (i.e., the main plot, p) with the marginal plots - one for the x margin and 99 | # one for the y margin (x margin = top plot, y margin = right plot) 100 | getPanelPos <- function(gtableGrob) { 101 | layDF <- gtableGrob$layout 102 | layDF[layDF$name == "panel", c("t", "l", "b", "r")] 103 | } 104 | 105 | getMargGrob <- function(margPlot) { 106 | margG <- ggplot2::ggplotGrob(margPlot) 107 | gtable::gtable_filter(margG, pattern = "panel") 108 | } 109 | 110 | # Functions to add title grob to ggextra plot --------------------------- 111 | 112 | # Add the title/subtitle grobs to the main ggextra plot, along with a little 113 | # padding 114 | addTitleGrobs <- function(ggxtraNoTtl, titleGrobs) { 115 | layout <- ggxtraNoTtl$layout 116 | l <- layout[layout$name == "panel", "l"] 117 | spacerGrob <- grid::rectGrob( 118 | height = grid::unit(.2, "cm"), 119 | gp = grid::gpar(col = "white", fill = NULL) 120 | ) 121 | plotWSpace <- rbindGrobs( 122 | topGrob = spacerGrob, gtable = ggxtraNoTtl, 123 | l = l, r = l 124 | ) 125 | plotWSubTitle <- rbindGrobs( 126 | topGrob = titleGrobs$subTitleG, 127 | gtable = plotWSpace, l = l, r = l 128 | ) 129 | rbindGrobs( 130 | topGrob = titleGrobs$titleG, 131 | gtable = plotWSubTitle, l = l, r = l 132 | ) 133 | } 134 | 135 | rbindGrobs <- function(topGrob, gtable, l, r) { 136 | topH <- grid::grobHeight(topGrob) 137 | gt_t <- gtable::gtable_add_rows(x = gtable, heights = topH, pos = 0) 138 | gtable::gtable_add_grob( 139 | x = gt_t, grobs = topGrob, t = 1, b = 1, 140 | l = l, r = r, z = Inf 141 | ) 142 | } 143 | -------------------------------------------------------------------------------- /inst/examples-shiny/ggMarginal/server.R: -------------------------------------------------------------------------------- 1 | function(input, output, session) { 2 | # show/hide the marginal plots settings 3 | observe({ 4 | toggle(id = "marginal-settings", anim = TRUE, 5 | time = 0.3, condition = input$show_marginal) 6 | }) 7 | 8 | output$x_var_select <- renderUI({ 9 | dataset <- datasets[[input$dataset]] 10 | selectInput("x_var", "X variable", 11 | colnames(dataset), colnames(dataset)[1]) 12 | }) 13 | 14 | output$y_var_select <- renderUI({ 15 | dataset <- datasets[[input$dataset]] 16 | selectInput("y_var", "Y variable", 17 | colnames(dataset), colnames(dataset)[2]) 18 | }) 19 | output$col_var_select <- renderUI({ 20 | dataset <- datasets[[input$dataset]] 21 | is_factor_or_char <- function(x) is.factor(x) || is.character(x) 22 | factor_vars <- names(which(unlist(lapply(dataset, is_factor_or_char)))) 23 | if (length(factor_vars) == 0) { 24 | hide(selector = "#groupColour, #groupFill") 25 | return() 26 | } 27 | selectInput("col_var", "Colour by", c(factor_vars, "")) 28 | }) 29 | use_colour_var <- reactive({ 30 | dataset <- datasets[[input$dataset]] 31 | if (!is.null(input$col_var) && input$col_var %in% colnames(dataset)) { 32 | TRUE 33 | } else { 34 | FALSE 35 | } 36 | }) 37 | observe({ 38 | toggle(selector = "#groupColour, #groupFill", condition = use_colour_var()) 39 | }) 40 | 41 | # there's a bug with sliderInput where if you scroll all the way 42 | # to the left and exit the window, it returns NA and breaks Shiny 43 | fontSize <- reactive({ 44 | if (is.null(input$font_size)) { 45 | 0 46 | } else { 47 | input$font_size 48 | } 49 | }) 50 | size <- reactive({ 51 | if (is.null(input$size)) { 52 | 1 53 | } else { 54 | input$size 55 | } 56 | }) 57 | 58 | output$plot <- renderPlot({ 59 | dataset <- datasets[[input$dataset]] 60 | 61 | # make sure the x and y variable select boxes have been updated 62 | if (is.null(input$x_var) || is.null(input$y_var) || 63 | !input$x_var %in% colnames(dataset) || 64 | !input$y_var %in% colnames(dataset)) { 65 | return(NULL) 66 | } 67 | 68 | 69 | # when the plot changes, change the code as well 70 | html("code", code()) 71 | 72 | p <- 73 | ggplot(dataset, aes(.data[[input$x_var]], .data[[input$y_var]])) + 74 | geom_point() + 75 | theme_bw(fontSize()) 76 | 77 | if (use_colour_var()) { 78 | p <- p + aes(colour = .data[[input$col_var]]) 79 | } 80 | 81 | # apply axis transformations to ensure marginal plots still work 82 | if (input$xtrans == "log") { 83 | p <- p + scale_x_log10() 84 | } else if (input$xtrans == "reverse") { 85 | p <- p + scale_x_reverse() 86 | } 87 | if (input$ytrans == "log") { 88 | p <- p + scale_y_log10() 89 | } else if (input$ytrans == "reverse") { 90 | p <- p + scale_y_reverse() 91 | } 92 | 93 | params <- list( 94 | p = p, 95 | type = input$type, 96 | margins = input$margins, 97 | size = size() 98 | ) 99 | if (use_colour_var() && input$groupColour) { 100 | params$groupColour <- TRUE 101 | } else { 102 | params$colour <- input$col 103 | } 104 | if (use_colour_var() && input$groupFill) { 105 | params$groupFill <- TRUE 106 | } else { 107 | params$fill <- input$fill 108 | } 109 | 110 | if (input$show_marginal) { 111 | p <- do.call(ggExtra::ggMarginal, params) 112 | } 113 | 114 | p 115 | }) 116 | 117 | # the code to reproduce the plot 118 | code <- reactive({ 119 | code <- sprintf(paste0( 120 | "p <- ggplot(`%s`, aes(.data[['%s']], .data[['%s']])) +\n"), 121 | input$dataset, input$x_var, input$y_var 122 | ) 123 | 124 | if (use_colour_var()) { 125 | code <- paste0(code, " aes(colour = .data[['", input$col_var, "']]) +\n") 126 | } 127 | 128 | code <- paste0(code, " geom_point() + theme_bw(", fontSize(), ")") 129 | 130 | 131 | if (input$xtrans == "log") { 132 | code <- paste0(code, " + scale_x_log10()") 133 | } else if (input$xtrans == "reverse") { 134 | code <- paste0(code, " + scale_x_reverse()") 135 | } 136 | if (input$ytrans == "log") { 137 | code <- paste0(code, " + scale_y_log10()") 138 | } else if (input$ytrans == "reverse") { 139 | code <- paste0(code, " + scale_y_reverse()") 140 | } 141 | 142 | code <- paste0(code, "\n\n") 143 | 144 | if (input$show_marginal) { 145 | code <- paste0(code, sprintf(paste0( 146 | "ggExtra::ggMarginal(\n", 147 | " p,\n", 148 | " type = '%s',\n", 149 | " margins = '%s',\n", 150 | " size = %s,\n"), 151 | input$type, input$margins, size())) 152 | if (use_colour_var() && input$groupColour) { 153 | code <- paste0(code, " groupColour = TRUE,\n") 154 | } else { 155 | code <- paste0(code, " colour = '", input$col, "',\n") 156 | } 157 | if (use_colour_var() && input$groupFill) { 158 | code <- paste0(code, " groupFill = TRUE\n") 159 | } else { 160 | code <- paste0(code, " fill = '", input$fill, "'\n") 161 | } 162 | code <- paste0(code, ")") 163 | } else { 164 | code <- paste0(code, "p") 165 | } 166 | }) 167 | 168 | # hide the loading message 169 | hide("loading-content", TRUE, "fade") 170 | } 171 | -------------------------------------------------------------------------------- /tests/testthat/helper-funs.R: -------------------------------------------------------------------------------- 1 | # Wrappers around ggMarginal and ggplot function calls ------------------------ 2 | 3 | basicScatP <- function() { 4 | ggplot2::ggplot(mtcars, ggplot2::aes(x = wt, y = drat)) + 5 | ggplot2::geom_point(na.rm = TRUE) 6 | } 7 | 8 | ggMarg2 <- function(type, ...) { 9 | ggMarginal(basicScatP(), type = type, ...) 10 | } 11 | 12 | margMapP <- function() { 13 | ggplot2::ggplot(mtcars, ggplot2::aes(x = wt, y = drat, colour = factor(vs))) + 14 | ggplot2::geom_point() + 15 | ggplot2::scale_colour_manual(values = c("green", "blue")) 16 | } 17 | 18 | basicScatPWithLims <- function() { 19 | basicScatP() + ggplot2::scale_x_continuous(limits = c(0, 2)) 20 | } 21 | 22 | # functions that plot the test figs ------------------------------------------- 23 | 24 | basicMarginals <- list( 25 | "basic density" = function() ggMarg2("density"), 26 | "basic histogram" = function() ggMarg2("histogram"), 27 | "basic boxplot" = function() ggMarg2("boxplot"), 28 | "basic violin plot" = function() ggMarg2("violin"), 29 | "basic densigram" = function() ggMarg2("densigram"), 30 | "scatter plot from data" = function() ggMarginal( 31 | data = mtcars, x = "mpg", y = "disp", type = "density" 32 | ) 33 | ) 34 | otherParams <- list( 35 | "only x margin" = function() ggMarg2("density", margins = "x"), 36 | "smaller marginal plots" = function() ggMarg2("density", size = 10), 37 | "both hists red col" = function() ggMarg2("histogram", colour = "red"), 38 | "top hist red col and fill" = function() ggMarg2( 39 | "histogram", xparams = list(colour = "red", fill = "red") 40 | ), 41 | "center and boundary set" = function() ggMarginal( 42 | ggplot2::ggplot(mtcars, ggplot2::aes(x = cyl, y = carb)) + 43 | ggplot2::geom_point() + 44 | ggplot2::xlim(0, 10), 45 | type = "histogram", 46 | xparams = list(center = 0, binwidth = 0.5), 47 | yparams = list(boundary = 0, binwidth = 1), 48 | ) 49 | ) 50 | miscIssues <- list( 51 | "theme bw" = function() ggMarginal( 52 | basicScatP() + ggplot2::theme_bw(), type = "density" 53 | ), 54 | "legend and title" = function() ggMarginal( 55 | ggplot2::ggplot(mtcars) + 56 | ggplot2::geom_point(ggplot2::aes(x = wt, y = drat, colour = gear)) + 57 | ggplot2::ggtitle("pretty sweet title", "not a bad subtitle either") + 58 | ggplot2::theme(plot.title = ggplot2::element_text(colour = "red")) 59 | ), 60 | "flipped coord where x is drat and y is wt" = function() ggMarginal( 61 | basicScatP() + ggplot2::coord_flip(), type = "density" 62 | ), 63 | "subtitle but no title" = function() ggMarginal( 64 | basicScatP() + ggplot2::labs(subtitle = "This should be above marginal") 65 | ), 66 | "geom_line provided as first geom" = function() ggMarginal( 67 | ggplot2::ggplot(mtcars, ggplot2::aes(x = wt, y = mpg)) + 68 | ggplot2::geom_line() + 69 | ggplot2::geom_point() 70 | ), 71 | "no density fill for densigrams" = function() ggMarginal( 72 | basicScatP(), type = "densigram", fill = "blue" 73 | ) 74 | ) 75 | groupingFeature <- list( 76 | "col and fill mapped" = function() ggMarginal( 77 | margMapP(), groupColour = TRUE, groupFill = TRUE 78 | ), 79 | "fill mapped with low alpha" = function() ggMarginal( 80 | margMapP(), groupFill = TRUE, alpha = .2 81 | ), 82 | "colour mapped with grey fill" = function() ggMarginal( 83 | p = margMapP(), groupColour = TRUE, fill = "grey" 84 | ), 85 | "colour mapped and colour param provided" = function() ggMarginal( 86 | margMapP(), groupColour = TRUE, colour = "red" 87 | ), 88 | "colour & fill mapped and both params provided" = function() ggMarginal( 89 | margMapP(), groupColour = TRUE, groupFill = TRUE, 90 | colour = "red", fill = "blue" 91 | ), 92 | "groupFill doesn't impact hist heights - no fill" = function() ggMarginal( 93 | margMapP(), type = "histogram", xparams = list(binwidth = .3) 94 | ), 95 | "groupFill doesn't impact hist heights - with fill" = function() ggMarginal( 96 | margMapP(), type = "histogram", xparams = list(binwidth = .3), 97 | groupFill = TRUE 98 | ), 99 | "widths of boxplots are the same within a marginal" = function() ggMarginal( 100 | margMapP(), type = "boxplot", groupColour = TRUE 101 | ) 102 | ) 103 | transforms <- list( 104 | "x-axis limits using scale_x_continuous" = function() ggMarginal( 105 | basicScatPWithLims() 106 | ), 107 | "axis limits using xlim and ylim" = function() ggMarginal( 108 | basicScatP() + ggplot2::xlim(2, 5) + ggplot2::ylim(3, 4.5) 109 | ), 110 | "x-axis limits for histograms" = function() ggMarginal( 111 | basicScatPWithLims(), type = "histogram" 112 | ), 113 | "x-axis limits for marginals with y aes" = function() ggMarginal( 114 | basicScatPWithLims(), type = "violin" 115 | ), 116 | "x and y scale_reverse" = function() ggMarginal( 117 | basicScatP() + ggplot2::scale_x_reverse() + ggplot2::scale_y_reverse() 118 | ), 119 | "geom_smooth with aligned marg plots" = function() ggMarginal( 120 | basicScatP() + ggplot2::geom_smooth(method = "loess", formula = y ~ x), type = "histogram" 121 | ) 122 | ) 123 | 124 | funList <- list( 125 | "ggMarginal can produce basic marginal plots" = basicMarginals, 126 | "ggMarginal's other params work" = otherParams, 127 | "Misc issues are solved" = miscIssues, 128 | "Grouping feature works as expected" = groupingFeature, 129 | "Transforms to scatter plot scales are reflected in marginals" = transforms 130 | ) 131 | -------------------------------------------------------------------------------- /man/ggMarginal.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ggMarginal.R 3 | \name{ggMarginal} 4 | \alias{ggMarginal} 5 | \title{Add marginal density/histogram to ggplot2 scatterplots} 6 | \usage{ 7 | ggMarginal( 8 | p, 9 | data, 10 | x, 11 | y, 12 | type = c("density", "histogram", "boxplot", "violin", "densigram"), 13 | margins = c("both", "x", "y"), 14 | size = 5, 15 | ..., 16 | xparams = list(), 17 | yparams = list(), 18 | groupColour = FALSE, 19 | groupFill = FALSE 20 | ) 21 | } 22 | \arguments{ 23 | \item{p}{A ggplot2 scatterplot to add marginal plots to. If \code{p} is 24 | not provided, then all of \code{data}, \code{x}, and \code{y} must be 25 | provided.} 26 | 27 | \item{data}{The data.frame to use for creating the marginal plots. Ignored 28 | if \code{p} is provided.} 29 | 30 | \item{x}{The name of the variable along the x axis. Ignored if \code{p} is 31 | provided.} 32 | 33 | \item{y}{The name of the variable along the y axis. Ignored if \code{p} is 34 | provided.} 35 | 36 | \item{type}{What type of marginal plot to show. One of: [density, histogram, boxplot, violin, densigram] 37 | (a "densigram" is when a density plot is overlaid on a histogram).} 38 | 39 | \item{margins}{Along which margins to show the plots. One of: [both, x, y].} 40 | 41 | \item{size}{Integer describing the relative size of the marginal plots 42 | compared to the main plot. A size of 5 means that the main plot is 5x wider 43 | and 5x taller than the marginal plots.} 44 | 45 | \item{...}{Extra parameters to pass to the marginal plots. Any parameter that 46 | \code{geom_line()}, \code{geom_histogram()}, \code{geom_boxplot()}, or \code{geom_violin()} accepts 47 | can be used. For example, \code{colour = "red"} can be used for any marginal plot type, 48 | and \code{binwidth = 10} can be used for histograms.} 49 | 50 | \item{xparams}{List of extra parameters to use only for the marginal plot along 51 | the x axis.} 52 | 53 | \item{yparams}{List of extra parameters to use only for the marginal plot along 54 | the y axis.} 55 | 56 | \item{groupColour}{If \code{TRUE}, the colour (or outline) of the marginal 57 | plots will be grouped according to the variable mapped to \code{colour} in the 58 | scatter plot. The variable mapped to \code{colour} in the scatter plot must 59 | be a character or factor variable. See examples below.} 60 | 61 | \item{groupFill}{If \code{TRUE}, the fill of the marginal 62 | plots will be grouped according to the variable mapped to \code{colour} in the 63 | scatter plot. The variable mapped to \code{colour} in the scatter plot must 64 | be a character or factor variable. See examples below.} 65 | } 66 | \value{ 67 | An object of class \code{ggExtraPlot}. This object can be printed to show the 68 | plots or saved using any of the typical image-saving functions (for example, using 69 | \code{png()} or \code{pdf()}). 70 | } 71 | \description{ 72 | Create a ggplot2 scatterplot with marginal density plots (default) or 73 | histograms, or add the marginal plots to an existing scatterplot. 74 | } 75 | \note{ 76 | The \code{grid} and \code{gtable} packages are required for this 77 | function. 78 | 79 | Since the \code{size} parameter is used by \code{ggMarginal}, if you want 80 | to pass a size to the marginal plots, you cannot 81 | use the \code{...} parameter. Instead, you must pass \code{size} to 82 | both \code{xparams} and \code{yparams}. For example, 83 | \code{ggMarginal(p, size = 2)} will change the size of the main vs marginal plot, 84 | while \code{ggMarginal(p, xparams = list(size=2), yparams = list(size=2))} 85 | will make the density plot outline thicker. 86 | } 87 | \examples{ 88 | \dontrun{ 89 | library(ggplot2) 90 | 91 | # basic usage 92 | p <- ggplot(mtcars, aes(wt, mpg)) + geom_point() 93 | ggMarginal(p) 94 | 95 | # using some parameters 96 | set.seed(30) 97 | df <- data.frame(x = rnorm(500, 50, 10), y = runif(500, 0, 50)) 98 | p2 <- ggplot(df, aes(x, y)) + geom_point() 99 | ggMarginal(p2) 100 | ggMarginal(p2, type = "histogram") 101 | ggMarginal(p2, margins = "x") 102 | ggMarginal(p2, size = 2) 103 | ggMarginal(p2, colour = "red") 104 | ggMarginal(p2, colour = "red", xparams = list(colour = "blue", size = 3)) 105 | ggMarginal(p2, type = "histogram", bins = 10) 106 | 107 | # Using violin plot 108 | ggMarginal(p2, type = "violin") 109 | 110 | # Using a "densigram" plot 111 | ggMarginal(p2, type = "densigram") 112 | 113 | # specifying the data directly instead of providing a plot 114 | ggMarginal(data = df, x = "x", y = "y") 115 | 116 | # more examples showing how the marginal plots are properly aligned even when 117 | # the main plot axis/margins/size/etc are changed 118 | set.seed(30) 119 | df2 <- data.frame(x = c(rnorm(250, 50, 10), rnorm(250, 100, 10)), 120 | y = runif(500, 0, 50)) 121 | p2 <- ggplot(df2, aes(x, y)) + geom_point() 122 | ggMarginal(p2) 123 | 124 | p2 <- p2 + ggtitle("Random data") + theme_bw(30) 125 | ggMarginal(p2) 126 | 127 | p3 <- ggplot(df2, aes(log(x), y - 500)) + geom_point() 128 | ggMarginal(p3) 129 | 130 | p4 <- p3 + scale_x_continuous(limits = c(2, 6)) + theme_bw(50) 131 | ggMarginal(p4) 132 | 133 | # Using groupColour and groupFill 134 | # In order to use either of these arguments, we must map 'colour' in the 135 | # scatter plot to a factor or character variable 136 | p <- ggplot(mtcars, aes(x = wt, y = drat, colour = factor(vs))) + 137 | geom_point() 138 | ggMarginal(p, groupColour = TRUE) 139 | ggMarginal(p, groupColour = TRUE, groupFill = TRUE) 140 | } 141 | 142 | } 143 | \seealso{ 144 | \href{https://daattali.com/shiny/ggExtra-ggMarginal-demo/}{Demo Shiny app} 145 | } 146 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | TBD 4 | 5 | - Completely update the testing framework, so that the unit tests only test using currently installed packages and a GitHub Actions workflow uss a docker container to test multiple ggplot2 versions 6 | 7 | # ggExtra 0.11.0 8 | 9 | 2025-09-01 10 | 11 | - **DEPRECATION NOTICE** The `runExample()` function is no longer required since {shiny} version 1.8.1 (March 2024). You can now use `shiny::runExample("ggMarginal", package = "ggExtra")` instead of `ggExtra::runExample()`. (#180) 12 | - Removed deprecated call to ggplot2's `..density..` (#175) 13 | - Removed deprecated call to ggplot2's `aes_string()` (#175) 14 | 15 | # ggExtra 0.10.1 16 | 17 | 2023-08-19 18 | 19 | - Fixed issue where grouped boxplots and violin plots were variable widths (#173) 20 | 21 | # ggExtra 0.10.0 22 | 23 | 2022-03-22 24 | 25 | - Fix histograms to have consistent height when grouped vs ungrouped (#151) 26 | - Addin now works when called with a variable name that was also a function (such as `plot`) (#158) 27 | - Addin no longer throws error messages in the console for "figure margins too large" (#159) 28 | - Allow users to pass in custom boundary or center param (#164) 29 | 30 | # ggExtra 0.9 31 | 32 | 2019-08-27 33 | 34 | - Fix #109: using ggMarginal on a plot where geom_point was not the first layer was buggy (#116) 35 | - Add documentation about how to use ggMarginal in R Notebooks or Rmarkdown 36 | - Added support for densigram (density+histogram) plots (#118) 37 | - Lots of internal refactoring (thanks @crew102) 38 | - Fix the "two chunk" requirement for rendering ggMarginalPlots in Rmd (#148) 39 | 40 | # ggExtra 0.8 41 | 42 | 2018-04-04 43 | 44 | NEW FEATURES 45 | 46 | - Added support for violin plots (#62) 47 | - Added support for mapping colour from the scatter plot to colour/fill in the marginal plots (#61) 48 | 49 | BUX FIXES 50 | 51 | - Make sure marginal data comes from correct data frame (#67) 52 | - Fix #81: many issues when the x or y axis have custom scales applied (#101) 53 | - Fix #99: plot subtitle was in the wrong position when no title was used (#103) 54 | 55 | # ggExtra 0.7 56 | 57 | 2017-06-21 58 | 59 | ### Refactoring of `ggMarginal()`: 60 | 61 | - Several of the stages that `ggMarginal` completes to create the final plot are now broken into (more) helper functions 62 | - Code that uses internal ggplot2 structure works on different versions of ggplot2 63 | - Removed code that added y labels to marginal plot to deal with long y labels as well as spacing issues (https://github.com/daattali/ggExtra/blob/6c4923c2ad2e700226cdcdc9666de513d6ae3a41/R/ggMarginal.R#L186-L201) 64 | - Removed code that set the marginal plot limits when the marginal plot was a boxplot (https://github.com/daattali/ggExtra/blob/6c4923c2ad2e700226cdcdc9666de513d6ae3a41/R/ggMarginalHelpers.R#L177) 65 | 66 | ### Other features 67 | 68 | - add visual tests 69 | - add tests over different versions of ggplot2, to ensure backwards compatibility 70 | - add arguments to `rotateTextX()` 71 | 72 | # ggExtra 0.6 73 | 74 | 2016-11-11 75 | 76 | - support new ggplot2 v2.2.0 (not backwards compatible unforunately because ggplot2 internals changed too much) 77 | 78 | # ggExtra 0.5.2 79 | 80 | 2016-10-03 81 | 82 | - use `colourpicker` package instead of deprecated colour input from shinyjs 83 | - bug fix: retain the title font face (#30) 84 | 85 | # ggExtra 0.5.1 86 | 87 | 2016-07-25 88 | 89 | - UI improvements to shiny app 90 | - add social media meta tags to shiny app 91 | 92 | # ggExtra 0.5 93 | 94 | 2016-05-29 95 | 96 | - ggMarginal now supports plots with legends (thanks to @crew102) (#23) 97 | 98 | # ggExtra 0.4.2 99 | 100 | 2016-04-30 101 | 102 | - ggMarginal addin now works on all screen resolutions (#24) 103 | 104 | # ggExtra 0.4.1 105 | 106 | 2016-04-24 107 | 108 | - Remove hack required by old version of `gridExtra` 109 | 110 | # ggExtra 0.4.0 111 | 112 | 2016-03-25 113 | 114 | - Added an RStudio addin and gadget for creating ggplot2 marginal plots (select *ggplot2 Marginal Plots* from the RStudio *Addins* menu, or call `ggExtra::ggMarginalGadget(plot)`) 115 | 116 | # ggExtra 0.3.3 117 | 118 | 2015-12-14 119 | 120 | - Small UI changes to the Shiny app demo 121 | 122 | # ggExtra 0.3.2 123 | 124 | 2015-11-10 125 | 126 | - Fixed bug where using `ggplot2::set_theme()` was causing the marginal plots to also use that theme 127 | 128 | # ggExtra 0.3.1 129 | 130 | 2015-11-05 131 | 132 | - Fixed bug where first and last bins of histograms were never showing (#18) 133 | - Finally tackled a long standing problem: if main plot has a title, move the title on top of the marginal plots. An unwanted side effect of this is that the title font size will not be retained because the title is its own grob. (#3) 134 | 135 | # ggExtra 0.3.0 136 | 137 | 2015-09-02 138 | 139 | - significant internal refactoring of `ggMarginal` to make it work with new ggplot2 version (after version 1.0.1 ggplot2 had tons of breaking changes) (some parts of the function use different code depending on the version of ggplot2 installed, I hope this doesn't raise any bugs) 140 | - make `ggMarginal` a little more robust to many different theme options so that even if the main plot changes the tick mark lengths or x axis size or many different options, the marginal plots will still align properly 141 | - add more usage examples to `ggMarginal` 142 | 143 | # ggExtra 0.2.3 144 | 145 | 2015-08-19 146 | 147 | - bug fix: ggMarginal now works when the original plot has expressions as the x/y variables. For example, calling ggMarginal on a plot that had `aes(x+10, log(y))` did not work before 148 | 149 | # ggExtra 0.2.2 150 | 151 | 2015-08-17 152 | 153 | - simplify and remove some unneeded package checks since `grid` and `gridExtra` should be installed automatically 154 | 155 | # ggExtra 0.2.1 156 | 157 | 2015-08-04 158 | 159 | minor changes 160 | - small updates to ggMarginal demo shiny app 161 | - small changes to ggMarginal documentation 162 | - small changes to package DESCRIPTION 163 | 164 | # ggExtra 0.2.0 165 | 166 | 2015-07-10 167 | 168 | - marginal plots now use the same axis transformations (log/reverse/limits/etc) as the main plot 169 | - rewrote `ggMarginal` to support the new `gridExtra` package which has been completely rewritten after 2 years of inactivity 170 | 171 | # ggExtra 0.1.6 172 | 173 | 2015-06-26 174 | 175 | - added `...` parameter to `plotCount` after a request to add a way to colour the bars 176 | 177 | # ggExtra 0.1.5 178 | 179 | 2015-06-08 180 | 181 | - ggMarginal: add support for boxplots 182 | - ggMarginal: add `...` parameter that allows you to pass any arguments to the 183 | corresponding ggplot2 geom layer 184 | - ggMarginal: add `xparams` and `yparams` parameters to pass any arguments 185 | to only the x/y marginal plot 186 | - **BREAKING CHANGE**: ggMarginal: `marginCol` and `marginFill` params have been 187 | removed since `colour` and `fill` can be provided as regular params thanks 188 | to the `...` parameter 189 | 190 | # ggExtra 0.1.1 191 | 192 | 2015-04-21 193 | 194 | Add a Shiny app that shows how to use `ggMarginal`, can be viewed with 195 | `runExample` or on my Shiny Server 196 | 197 | 198 | 199 | # ggExtra 0.1.0 200 | 201 | 2015-03-28 202 | 203 | Package is officially released to the public and is now on CRAN 204 | -------------------------------------------------------------------------------- /R/ggMarginal.R: -------------------------------------------------------------------------------- 1 | #' Add marginal density/histogram to ggplot2 scatterplots 2 | #' 3 | #' Create a ggplot2 scatterplot with marginal density plots (default) or 4 | #' histograms, or add the marginal plots to an existing scatterplot. 5 | #' 6 | #' @note The \code{grid} and \code{gtable} packages are required for this 7 | #' function. 8 | #' @param p A ggplot2 scatterplot to add marginal plots to. If \code{p} is 9 | #' not provided, then all of \code{data}, \code{x}, and \code{y} must be 10 | #' provided. 11 | #' @param data The data.frame to use for creating the marginal plots. Ignored 12 | #' if \code{p} is provided. 13 | #' @param x The name of the variable along the x axis. Ignored if \code{p} is 14 | #' provided. 15 | #' @param y The name of the variable along the y axis. Ignored if \code{p} is 16 | #' provided. 17 | #' @param type What type of marginal plot to show. One of: [density, histogram, boxplot, violin, densigram] 18 | #' (a "densigram" is when a density plot is overlaid on a histogram). 19 | #' @param margins Along which margins to show the plots. One of: [both, x, y]. 20 | #' @param size Integer describing the relative size of the marginal plots 21 | #' compared to the main plot. A size of 5 means that the main plot is 5x wider 22 | #' and 5x taller than the marginal plots. 23 | #' @param ... Extra parameters to pass to the marginal plots. Any parameter that 24 | #' \code{geom_line()}, \code{geom_histogram()}, \code{geom_boxplot()}, or \code{geom_violin()} accepts 25 | #' can be used. For example, \code{colour = "red"} can be used for any marginal plot type, 26 | #' and \code{binwidth = 10} can be used for histograms. 27 | #' @param xparams List of extra parameters to use only for the marginal plot along 28 | #' the x axis. 29 | #' @param yparams List of extra parameters to use only for the marginal plot along 30 | #' the y axis. 31 | #' @param groupColour If \code{TRUE}, the colour (or outline) of the marginal 32 | #' plots will be grouped according to the variable mapped to \code{colour} in the 33 | #' scatter plot. The variable mapped to \code{colour} in the scatter plot must 34 | #' be a character or factor variable. See examples below. 35 | #' @param groupFill If \code{TRUE}, the fill of the marginal 36 | #' plots will be grouped according to the variable mapped to \code{colour} in the 37 | #' scatter plot. The variable mapped to \code{colour} in the scatter plot must 38 | #' be a character or factor variable. See examples below. 39 | #' @return An object of class \code{ggExtraPlot}. This object can be printed to show the 40 | #' plots or saved using any of the typical image-saving functions (for example, using 41 | #' \code{png()} or \code{pdf()}). 42 | #' @note Since the \code{size} parameter is used by \code{ggMarginal}, if you want 43 | #' to pass a size to the marginal plots, you cannot 44 | #' use the \code{...} parameter. Instead, you must pass \code{size} to 45 | #' both \code{xparams} and \code{yparams}. For example, 46 | #' \code{ggMarginal(p, size = 2)} will change the size of the main vs marginal plot, 47 | #' while \code{ggMarginal(p, xparams = list(size=2), yparams = list(size=2))} 48 | #' will make the density plot outline thicker. 49 | #' @examples 50 | #' \dontrun{ 51 | #' library(ggplot2) 52 | #' 53 | #' # basic usage 54 | #' p <- ggplot(mtcars, aes(wt, mpg)) + geom_point() 55 | #' ggMarginal(p) 56 | #' 57 | #' # using some parameters 58 | #' set.seed(30) 59 | #' df <- data.frame(x = rnorm(500, 50, 10), y = runif(500, 0, 50)) 60 | #' p2 <- ggplot(df, aes(x, y)) + geom_point() 61 | #' ggMarginal(p2) 62 | #' ggMarginal(p2, type = "histogram") 63 | #' ggMarginal(p2, margins = "x") 64 | #' ggMarginal(p2, size = 2) 65 | #' ggMarginal(p2, colour = "red") 66 | #' ggMarginal(p2, colour = "red", xparams = list(colour = "blue", size = 3)) 67 | #' ggMarginal(p2, type = "histogram", bins = 10) 68 | #' 69 | #' # Using violin plot 70 | #' ggMarginal(p2, type = "violin") 71 | #' 72 | #' # Using a "densigram" plot 73 | #' ggMarginal(p2, type = "densigram") 74 | #' 75 | #' # specifying the data directly instead of providing a plot 76 | #' ggMarginal(data = df, x = "x", y = "y") 77 | #' 78 | #' # more examples showing how the marginal plots are properly aligned even when 79 | #' # the main plot axis/margins/size/etc are changed 80 | #' set.seed(30) 81 | #' df2 <- data.frame(x = c(rnorm(250, 50, 10), rnorm(250, 100, 10)), 82 | #' y = runif(500, 0, 50)) 83 | #' p2 <- ggplot(df2, aes(x, y)) + geom_point() 84 | #' ggMarginal(p2) 85 | #' 86 | #' p2 <- p2 + ggtitle("Random data") + theme_bw(30) 87 | #' ggMarginal(p2) 88 | #' 89 | #' p3 <- ggplot(df2, aes(log(x), y - 500)) + geom_point() 90 | #' ggMarginal(p3) 91 | #' 92 | #' p4 <- p3 + scale_x_continuous(limits = c(2, 6)) + theme_bw(50) 93 | #' ggMarginal(p4) 94 | #' 95 | #' # Using groupColour and groupFill 96 | #' # In order to use either of these arguments, we must map 'colour' in the 97 | #' # scatter plot to a factor or character variable 98 | #' p <- ggplot(mtcars, aes(x = wt, y = drat, colour = factor(vs))) + 99 | #' geom_point() 100 | #' ggMarginal(p, groupColour = TRUE) 101 | #' ggMarginal(p, groupColour = TRUE, groupFill = TRUE) 102 | #' } 103 | #' 104 | #' @seealso \href{https://daattali.com/shiny/ggExtra-ggMarginal-demo/}{Demo Shiny app} 105 | #' @export 106 | ggMarginal <- function(p, data, x, y, 107 | type = c("density", "histogram", "boxplot", "violin", 108 | "densigram"), 109 | margins = c("both", "x", "y"), size = 5, 110 | ..., xparams = list(), yparams = list(), 111 | groupColour = FALSE, groupFill = FALSE) { 112 | 113 | # Figure out all the default parameters. 114 | type <- match.arg(type) 115 | margins <- match.arg(margins) 116 | 117 | # Fill in param defaults and consolidate params into single list (prmL). 118 | prmL <- toParamList(list(...), xparams, yparams) 119 | 120 | # Reconcile different naming variants on "colour" param 121 | prmL <- reconcileColParamApply(prmL) 122 | 123 | # Create one version of the scat plot (scatP), based on values of p, data, x, 124 | # and y...also remove all margin around plot so that it's easier to position 125 | # the density plots beside the main plot 126 | scatP <- reconcileScatPlot(p, data, x, y) + 127 | ggplot2::theme(plot.margin = grid::unit(c(0, 0, .25, .25), "cm")) 128 | 129 | # Decompose scatP to grab all sorts of information from it 130 | scatPbuilt <- ggplot2::ggplot_build(scatP) 131 | 132 | # Pull out the plot title/subtitle if one exists and save it as a grob for 133 | # later use 134 | labels <- scatPbuilt$plot$labels 135 | hasTitle <- (!is.null(labels$title) || !is.null(labels$subtitle)) 136 | if (hasTitle) { 137 | titleGrobs <- getTitleGrobs(p) 138 | scatP$labels$title <- NULL 139 | scatP$labels$subtitle <- NULL 140 | } 141 | 142 | # Create the margin plots by calling genFinalMargPlot 143 | if (margins != "y") { 144 | plt <- MarginalPlot$new("x", type, scatPbuilt, prmL, groupColour, groupFill) 145 | top <- plt$build() 146 | } 147 | if (margins != "x") { 148 | plt <- MarginalPlot$new("y", type, scatPbuilt, prmL, groupColour, groupFill) 149 | right <- plt$build() 150 | } 151 | 152 | # Now add the marginal plots to the scatter plot 153 | pGrob <- ggplot2::ggplotGrob(scatP) 154 | withCallingHandlers({ 155 | suppressMessages({ 156 | if (margins == "both") { 157 | ggxtraTmp <- addTopMargPlot(pGrob, top, size) 158 | ggxtraNoTtl <- addRightMargPlot(ggxtraTmp, right, size) 159 | } else if (margins == "x") { 160 | ggxtraTmp <- gtable::gtable_add_padding( 161 | pGrob, grid::unit(c(0, 0.5, 0, 0), "lines") 162 | ) 163 | ggxtraNoTtl <- addTopMargPlot(ggxtraTmp, top, size) 164 | } else if (margins == "y") { 165 | ggxtraTmp <- gtable::gtable_add_padding( 166 | pGrob, grid::unit(c(0.5, 0, 0, 0), "lines") 167 | ) 168 | ggxtraNoTtl <- addRightMargPlot(ggxtraTmp, right, size) 169 | } 170 | }) 171 | }, warning = function(w) { 172 | if (grepl("did you forget aes", w, ignore.case = TRUE)) { 173 | invokeRestart("muffleWarning") 174 | } 175 | }) 176 | 177 | # Add the title to the resulting ggExtra plot if it exists 178 | if (hasTitle) { 179 | ggExtraPlot <- addTitleGrobs(ggxtraNoTtl, titleGrobs) 180 | } else { 181 | ggExtraPlot <- ggxtraNoTtl 182 | } 183 | 184 | # Add a class for S3 method dispatch for printing the ggExtra plot 185 | class(ggExtraPlot) <- c("ggExtraPlot", class(ggExtraPlot)) 186 | 187 | ggExtraPlot 188 | } 189 | 190 | #' Print a ggExtraPlot object 191 | #' 192 | #' \code{ggExtraPlot} objects are created from \code{ggMarginal}. This is the S3 193 | #' generic print method to print the result of the scatterplot with its marginal 194 | #' plots. 195 | #' 196 | #' @param x ggExtraPlot object. 197 | #' @param newpage Should a new page (i.e., an empty page) be drawn before the 198 | #' ggExtraPlot is drawn? 199 | #' @param ... ignored 200 | #' @seealso \code{\link{ggMarginal}} 201 | #' @export 202 | #' @keywords internal 203 | print.ggExtraPlot <- function(x, newpage = grDevices::dev.interactive(), ...) { 204 | if (newpage) grid::grid.newpage() 205 | if (isTRUE(getOption("rstudio.notebook.executing"))) { 206 | x <- ggplot2::ggplot() + 207 | ggplot2::geom_blank() + 208 | ggplot2::annotation_custom(x) + 209 | ggplot2::theme_void() 210 | print(x) 211 | } else { 212 | grid::grid.draw(x) 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /vignettes/ggExtra.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Package ggExtra" 3 | author: "Dean Attali" 4 | date: "`r Sys.Date()`" 5 | output: rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteIndexEntry{Package ggExtra} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\usepackage[utf8]{inputenc} 10 | --- 11 | 12 | ```{r setup, echo = FALSE, message = FALSE} 13 | knitr::opts_chunk$set(tidy = FALSE, comment = "#>", fig.width = 6, 14 | fig.height = 4, fig.align = "center") 15 | # Encode the images into the document to make it self-contained (disabled 16 | # because it looks like GitHub markdown doesn't suppport this) 17 | #knitr::opts_knit$set(upload.fun = knitr::image_uri) 18 | ``` 19 | 20 | # ggExtra - Add marginal histograms to ggplot2, and more ggplot2 enhancements 21 | 22 | [![CRAN version](https://www.r-pkg.org/badges/version/ggExtra)](https://cran.r-project.org/package=ggExtra) 23 | [![CI build](https://github.com/daattali/ggExtra/actions/workflows/build.yml/badge.svg)](https://github.com/daattali/ggExtra/actions/workflows/build.yml) 24 | 25 | > *Copyright 2016 [Dean Attali](https://deanattali.com). Licensed under the MIT license.* 26 | 27 | `ggExtra` is a collection of functions and layers to enhance ggplot2. The flagship function is `ggMarginal`, which can be used to add marginal histograms/boxplots/density plots to ggplot2 scatterplots. You can view a 28 | [live interactive demo](https://daattali.com/shiny/ggExtra-ggMarginal-demo/) 29 | to test it out! 30 | 31 | Most other functions/layers are quite simple but are useful because they are fairly common ggplot2 operations that are a bit verbose. 32 | 33 | This is an instructional document, but I also wrote [a blog post](https://deanattali.com/2015/03/29/ggExtra-r-package/) about the reasoning 34 | behind and development of this package. 35 | 36 | Note: it was brought to my attention that several years ago there was a different package called `ggExtra`, by Baptiste (the author of `gridExtra`). That old `ggExtra` package was deleted in 2011 (two years before I even knew what R is!), and this package has nothing to do with the old one. 37 | 38 | ## Installation 39 | 40 | `ggExtra` is available through both CRAN and GitHub. 41 | 42 | To install the CRAN version: 43 | 44 | ``` 45 | install.packages("ggExtra") 46 | ``` 47 | 48 | To install the latest development version from GitHub: 49 | 50 | ``` 51 | install.packages("devtools") 52 | devtools::install_github("daattali/ggExtra") 53 | ``` 54 | 55 | ## Marginal plots RStudio addin/gadget 56 | 57 | `ggExtra` comes with an addin for `ggMarginal()`, which lets you interactively add marginal plots to a scatter plot. To use it, simply highlight the code for a ggplot2 plot in your script, and select *ggplot2 Marginal Plots* from the RStudio *Addins* menu. Alternatively, you can call the addin directly by calling `ggMarginalGadget(plot)` with a ggplot2 plot. 58 | 59 | ![ggMarginal gadget screenshot](../inst/img/ggmarginal-gadget.png) 60 | 61 | ## Usage 62 | 63 | We'll first load the package and ggplot2, and then see how all the functions work. 64 | 65 | ```{r load-pkg, warning=FALSE, message=FALSE} 66 | library("ggExtra") 67 | library("ggplot2") 68 | ``` 69 | 70 | ## `ggMarginal` - Add marginal histograms/boxplots/density plots to ggplot2 scatterplots 71 | 72 | `ggMarginal()` is an easy drop-in solution for adding marginal 73 | density plots/histograms/boxplots to a ggplot2 scatterplot. The easiest way to use it is by simply passing it a ggplot2 scatter plot, and `ggMarginal()` will add the marginal plots. 74 | 75 | As a simple first example, let's create a dataset with 500 points where the 76 | x values are normally distributed and the y values are uniformly distributed, 77 | and plot a simple ggplot2 scatterplot. 78 | 79 | ```{r init-plot} 80 | set.seed(30) 81 | df1 <- data.frame(x = rnorm(500, 50, 10), y = runif(500, 0, 50)) 82 | p1 <- ggplot(df1, aes(x, y)) + geom_point() + theme_bw() 83 | p1 84 | ``` 85 | 86 | And now to add marginal density plots: 87 | 88 | ```{r ggmarginal-basic} 89 | ggMarginal(p1) 90 | ``` 91 | 92 | That was easy. Notice how the syntax does not follow the standard ggplot2 93 | syntax - **you don't "add" a ggMarginal layer with `p1 + ggMarginal()`, but rather 94 | ggMarginal takes the object as an argument** and returns a different object. This means that you can use magrittr pipes, for example 95 | `p1 %>% ggMarginal()`. 96 | 97 | Let's make the text a bit larger to make it easier to see. 98 | 99 | ```{r ggmarginal-large} 100 | ggMarginal(p1 + theme_bw(30) + ylab("Two\nlines")) 101 | ``` 102 | 103 | Notice how the marginal plots occupy the correct space; even when the main 104 | plot's points are pushed to the right because of larger text or longer axis labels, 105 | the marginal plots automatically adjust. 106 | 107 | If your scatterplot has a factor variable mapping to a colour (ie. points in the scatterplot are colour-coded according to a variable in the data, by using `aes(colour = ...)`), then you can use `groupColour = TRUE` and/or `groupFill = TRUE` to reflect these groupings in the marginal plots. The result is multiple marginal plots, one for each colour group of points. Here's an example using the iris dataset. 108 | 109 | ```{r ggmarginal-grouping, message=FALSE} 110 | piris <- ggplot(iris, aes(Sepal.Length, Sepal.Width, colour = Species)) + 111 | geom_point() 112 | ggMarginal(piris, groupColour = TRUE, groupFill = TRUE) 113 | ``` 114 | 115 | You can also show histograms instead. 116 | 117 | ```{r ggmarginal-hist, message=FALSE} 118 | ggMarginal(p1, type = "histogram") 119 | ``` 120 | 121 | There are several more parameters, here is an example with a few more being used. Note that you can use any parameters that the `geom_XXX()` layers accept, such as `col` and `fill`, and they will be passed to these layers. 122 | 123 | ```{r ggmarginal-params, message=FALSE} 124 | ggMarginal(p1, margins = "x", size = 2, type = "histogram", 125 | col = "blue", fill = "orange") 126 | ``` 127 | 128 | In the above example, `size = 2` means that the main scatterplot should occupy twice as much height/width as the margin plots (default is 5). The `col` and `fill` parameters are simply passed to the ggplot layer for both margin plots. 129 | 130 | If you want to specify some parameter for only one of the marginal plots, you can use the `xparams` or `yparams` parameters, like this: 131 | 132 | ```{r ggmarginal-extraparams} 133 | ggMarginal(p1, type = "histogram", xparams = list(binwidth = 1, fill = "orange")) 134 | ``` 135 | 136 | Last but not least - you can also save the output from `ggMarginal()` and display 137 | it later. (This may sound trivial, but it was not an easy problem to solve 138 | - [see this discussion](https://stackoverflow.com/questions/29062766/store-output-from-gridextragrid-arrange-into-an-object)). 139 | 140 | ```{r ggmarginal-save} 141 | p <- ggMarginal(p1) 142 | p 143 | ``` 144 | 145 | You can also create marginal box plots and violin plots. For more information, see `?ggExtra::ggMarginal`. 146 | 147 | #### Using `ggMarginal()` in R Notebooks or Rmarkdown 148 | 149 | If you try including a `ggMarginal()` plot inside an R Notebook or Rmarkdown code chunk, you'll notice that the plot doesn't get output. In order to get a `ggMarginal()` to show up in an these contexts, you need to save the ggMarginal plot as a variable in one code chunk, and explicitly print it using the `grid` package in another chunk, like this: 150 | 151 | ```` 152 | \```{r} 153 | library(ggplot2) 154 | library(ggExtra) 155 | p <- ggplot(mtcars, aes(wt, mpg)) + geom_point() 156 | p <- ggMarginal(p) 157 | ``` 158 | \```{r} 159 | grid::grid.newpage() 160 | grid::grid.draw(p) 161 | ``` 162 | ```` 163 | 164 | ## `removeGrid` - Remove grid lines from ggplot2 165 | 166 | This is just a convenience function to save a bit of typing and memorization. 167 | Minor grid lines are always removed, and the major x or y grid lines can be 168 | removed as well (default is to remove both). 169 | 170 | `removeGridX` is a shortcut for `removeGrid(x = TRUE, y = FALSE)`, and 171 | `removeGridY` is similarly a shortcut for... . 172 | 173 | ```{r removeGrid} 174 | df2 <- data.frame(x = 1:50, y = 1:50) 175 | p2 <- ggplot2::ggplot(df2, ggplot2::aes(x, y)) + ggplot2::geom_point() 176 | p2 + removeGrid() 177 | ``` 178 | 179 | For more information, see `?ggExtra::removeGrid`. 180 | 181 | ## `rotateTextX` - Rotate x axis labels 182 | 183 | Often times it is useful to rotate the x axis labels to be vertical if there are 184 | too many labels and they overlap. This function accomplishes that and ensures 185 | the labels are horizontally centered relative to the tick line. 186 | 187 | ```{r rotateTextX} 188 | df3 <- data.frame(x = paste("Letter", LETTERS, sep = "_"), 189 | y = seq_along(LETTERS)) 190 | p3 <- ggplot2::ggplot(df3, ggplot2::aes(x, y)) + ggplot2::geom_point() 191 | p3 + rotateTextX() 192 | ``` 193 | 194 | For more information, see `?ggExtra::rotateTextX`. 195 | 196 | ## `plotCount` - Plot count data with ggplot2 197 | 198 | This is a convenience function to quickly plot a bar plot of count (frequency) 199 | data. The input must be either a frequency table (obtained with `base::table`) 200 | or a data.frame with 2 columns where the first column contains the values and 201 | the second column contains the counts. 202 | 203 | An example using a table: 204 | ```{r plotCount-table} 205 | plotCount(table(infert$education)) 206 | ``` 207 | 208 | An example using a data.frame: 209 | ```{r plotCount-df} 210 | df4 <- data.frame("vehicle" = c("bicycle", "car", "unicycle", "Boeing747"), 211 | "NumWheels" = c(2, 4, 1, 16)) 212 | plotCount(df4) + removeGridX() 213 | ``` 214 | 215 | For more information, see `?ggExtra::plotCount`. 216 | -------------------------------------------------------------------------------- /R/ggMarginal-MarginalPlot.R: -------------------------------------------------------------------------------- 1 | MarginalPlot <- R6::R6Class("MarginalPlot", 2 | 3 | public = list( 4 | 5 | marg = NULL, 6 | type = NULL, 7 | scatPbuilt = NULL, 8 | prmL = NULL, 9 | groupColour = NULL, 10 | groupFill = NULL, 11 | 12 | initialize = function(marg, type, scatPbuilt, prmL, groupColour, 13 | groupFill) { 14 | self$marg <- marg 15 | self$type <- type 16 | self$scatPbuilt <- scatPbuilt 17 | self$prmL <- prmL 18 | self$groupColour <- groupColour 19 | self$groupFill <- groupFill 20 | }, 21 | 22 | build = function() { 23 | data <- private$getVarDf() 24 | noGeomPlot <- private$margPlotNoGeom(data) 25 | finalParms <- private$alterParams() 26 | rawMarg <- private$addLayers(noGeomPlot, finalParms) 27 | margThemed <- rawMarg + ggplot2::theme_void() 28 | private$addLimits(margThemed) 29 | } 30 | ), 31 | 32 | private = list( 33 | 34 | getVarDf = function() { 35 | marg <- self$marg 36 | if (private$wasFlipped()) { 37 | marg <- switch(marg, 38 | "x" = "y", 39 | "y" = "x" 40 | ) 41 | } 42 | # Get data frame with geom_point layer data 43 | scatDF <- private$getGeomPointDf() 44 | 45 | # When points are excluded from the scatter plot via a limit on the x 46 | # axis, the y values in the built scatter plot's `data`` object will be NA 47 | # (and visa-versa for the y axis/x values). Exclude these NA points from 48 | # the data, as they don't actually show up in the scatter plot (and thus 49 | # shouldn't be in the marginal plots either). 50 | scatDF <- scatDF[!(is.na(scatDF$x) | is.na(scatDF$y)), ] 51 | 52 | if (marg == "y") { 53 | scatDF$x <- scatDF$y 54 | } 55 | 56 | # We never want to actually use the y aesthetic when creating the marginal 57 | # plots, but geom_violin requires it. Also note that, in order to make the 58 | # widths of violin and boxplots the same in the marginal plots, we need a 59 | # constant y. 60 | scatDF$y <- 1 61 | scatDF[, c("x", "y", "fill", "colour", "group")] 62 | }, 63 | 64 | wasFlipped = function() { 65 | classCoord <- class(self$scatPbuilt$plot$coordinates) 66 | any(grepl("flip", classCoord, ignore.case = TRUE)) 67 | }, 68 | 69 | getGeomPointDf = function() { 70 | layerBool <- vapply( 71 | self$scatPbuilt$plot$layers, 72 | function(x) grepl("geom_?point", class(x$geom)[1], ignore.case = TRUE), 73 | logical(1) 74 | ) 75 | if (!any(layerBool)) { 76 | stop("No geom_point layer was found in your scatter plot", call. = FALSE) 77 | } 78 | self$scatPbuilt[["data"]][layerBool][[1]] 79 | }, 80 | 81 | margPlotNoGeom = function(data) { 82 | mapping <- "x" 83 | haveMargMap <- self$groupColour || self$groupFill 84 | 85 | if (haveMargMap) { 86 | # Make sure user hasn't mapped a non-factor 87 | if (data[["group"]][1] < 0) { 88 | stop( 89 | "Colour must be mapped to a factor or character variable ", 90 | "(not a numeric variable) in your scatter plot if you set ", 91 | "groupColour = TRUE or groupFill = TRUE (i.e. use ", 92 | "`aes(colour = ...)`)" 93 | ) 94 | } 95 | 96 | data <- data[, c("x", "y", "colour", "group"), drop = FALSE] 97 | if (self$groupFill) { 98 | data[, "fill"] <- data[, "colour"] 99 | } 100 | 101 | values <- unique(data$colour) 102 | names(values) <- values 103 | 104 | if (self$groupColour && !self$groupFill) { 105 | xtraMapNames <- c("colour", "group") 106 | } else if (self$groupColour && self$groupFill) { 107 | xtraMapNames <- c("colour", "fill") 108 | } else { 109 | xtraMapNames <- c("fill", "group") 110 | } 111 | 112 | mapping <- c(mapping, xtraMapNames) 113 | } 114 | 115 | # Boxplot and violin plots need y aes 116 | if (self$type %in% c("boxplot", "violin")) { 117 | mapping <- c(mapping, "y") 118 | } 119 | 120 | # Build plot (sans geom) 121 | plot <- ggplot2::ggplot(data, ggplot2::aes_all(mapping)) 122 | 123 | if (haveMargMap) { 124 | if ("colour" %in% xtraMapNames) { 125 | plot <- plot + ggplot2::scale_colour_manual(values = values) 126 | } 127 | if ("fill" %in% xtraMapNames) { 128 | plot <- plot + ggplot2::scale_fill_manual(values = values) 129 | } 130 | } 131 | 132 | plot 133 | }, 134 | 135 | alterParams = function() { 136 | prmL2 <- self$prmL 137 | if (is.null(prmL2$exPrm$colour) && !self$groupColour) { 138 | prmL2$exPrm[["colour"]] <- "black" 139 | } 140 | 141 | # default to an alpha of .5 if user specifies a margin mapping 142 | if (is.null(prmL2$exPrm[["alpha"]]) && 143 | (self$groupColour || self$groupFill)) { 144 | prmL2$exPrm[["alpha"]] <- .5 145 | } 146 | 147 | # merge the parameters in an order that ensures that marginal plot params 148 | # overwrite general params 149 | prmL2$exPrm <- append(prmL2[[paste0(self$marg, "Prm")]], prmL2$exPrm) 150 | prmL2$exPrm <- prmL2$exPrm[!duplicated(names(prmL2$exPrm))] 151 | 152 | # In order to get the marginal plots to align with the scatter plot, we 153 | # have to apply limits on the marginal plot in addLimits. This can result 154 | # in bidwidths being cut for bins towards the limits, hence we apply 155 | # `boundary` below, which will fix the issue for the casual user. Users 156 | # who pass in either `center` or `boundary` are on their own. 157 | panScale <- private$getPanelScale(self$marg) 158 | limFun <- panScale$get_limits 159 | binPositionParams <- c("center", "boundary") 160 | binPositionParamsNotSet <- !any(binPositionParams %in% names(prmL2$exPrm)) 161 | if ( 162 | self$type %in% c("histogram", "densigram") && 163 | !is.null(limFun) && 164 | binPositionParamsNotSet) { 165 | prmL2$exPrm[["boundary"]] <- limFun()[1] 166 | } 167 | 168 | prmL2 <- private$overrideMappedParams(prmL2, "colour", self$groupColour) 169 | prmL2 <- private$overrideMappedParams(prmL2, "fill", self$groupFill) 170 | 171 | prmL2$exPrm 172 | }, 173 | 174 | overrideMappedParams = function(prmL2, paramName, groupVar) { 175 | if (!is.null(prmL2$exPrm[[paramName]]) && groupVar) { 176 | if (Sys.getenv("GGEXTRA_QUIET") != "1") { 177 | message( 178 | "You specified group", paramName, " = TRUE as well as a ", paramName, 179 | " parameter for a marginal plot. The ", paramName, " parameter will", 180 | " be ignored in favor of using ", paramName, "s mapped from the", 181 | " scatter plot." 182 | ) 183 | } 184 | prmL2$exPrm[[paramName]] <- NULL 185 | } 186 | prmL2 187 | }, 188 | 189 | addLayers = function(noGeomPlot, finalParms) { 190 | geomFun <- private$getGeomFun() 191 | if (self$type %in% c("density", "densigram")) { 192 | # to create a density plot, we use both geom_density geom_line b/c of issue 193 | # mentioned at https://twitter.com/winston_chang/status/957057715750166528. 194 | # also note that we get the colour of the density plot from geom_line and 195 | # the fill from geom_density (hence the calls to dropParams when creating 196 | # densityParams and lineParams) 197 | 198 | # we get a boundary param added in alterParams if the type is either 199 | # histogram or densigram, but we don't want this param to be used when 200 | # creating the density plot component of the densigram (only the histogram 201 | # component needs it), so drop it here 202 | finalParmsDplot <- private$dropParams(finalParms, "boundary") 203 | 204 | # first create geom_density layer 205 | densityParms <- private$dropParams(finalParmsDplot, "colour") 206 | if (self$type == "densigram") { 207 | # drop fill b/c of https://github.com/daattali/ggExtra/issues/123 208 | densityParms <- private$dropParams(densityParms, "fill") 209 | } 210 | layer1 <- do.call(geomFun, densityParms) 211 | 212 | # now create geom_line layer 213 | # we have to drop alpha param b/c of issue mentioned at 214 | # https://github.com/rstudio/rstudio/issues/2196 215 | lineParms <- private$dropParams(finalParmsDplot, c("fill", "alpha")) 216 | lineParms$stat <- "density" 217 | layer2 <- do.call(ggplot2::geom_line, lineParms) 218 | 219 | layers <- list(layer1, layer2) 220 | 221 | if (self$type == "densigram") { 222 | # if type is densigram, have to add a histogram layer to layers list 223 | layer3 <- do.call(geom_histogram2, finalParms) 224 | layers <- list(layer3, layers) 225 | } 226 | } else { 227 | layer <- do.call(geomFun, finalParms) 228 | layers <- list(layer) 229 | } 230 | 231 | plot <- Reduce(`+`, c(list(noGeomPlot), layers)) 232 | 233 | if (private$needsFlip()) { 234 | plot <- plot + ggplot2::coord_flip() 235 | } 236 | plot 237 | }, 238 | 239 | getGeomFun = function() { 240 | switch(self$type, 241 | "density" = geom_density2, 242 | "densigram" = geom_density2, 243 | "histogram" = ggplot2::geom_histogram, 244 | "boxplot" = ggplot2::geom_boxplot, 245 | "violin" = ggplot2::geom_violin 246 | ) 247 | }, 248 | 249 | dropParams = function(finalParams, toDrop) { 250 | finalParams[!names(finalParams) %in% toDrop] 251 | }, 252 | 253 | addLimits = function(margThemed) { 254 | limits <- private$getLimits() 255 | margThemed + 256 | ggplot2::scale_x_continuous(limits = limits, oob = scales::squish) 257 | }, 258 | 259 | getPanelScale = function(marg) { 260 | above_221 <- utils::packageVersion("ggplot2") > "2.2.1" 261 | if (above_221) { 262 | if (marg == "x") { 263 | self$scatPbuilt$layout$panel_scales_x[[1]] 264 | } else { 265 | self$scatPbuilt$layout$panel_scales_y[[1]] 266 | } 267 | } else { 268 | if (marg == "x") { 269 | self$scatPbuilt$layout$panel_scales$x[[1]] 270 | } else { 271 | self$scatPbuilt$layout$panel_scales$y[[1]] 272 | } 273 | } 274 | }, 275 | 276 | needsFlip = function() { 277 | self$marg == "y" 278 | }, 279 | 280 | # Get the axis range of the x or y axis of the given ggplot build object 281 | # This is needed so that if the range of the plot is manually changed, the 282 | # marginal plots will use the same range 283 | getLimits = function() { 284 | marg <- self$marg 285 | if (private$wasFlipped()) { 286 | marg <- switch(marg, 287 | "x" = "y", 288 | "y" = "x" 289 | ) 290 | } 291 | scale <- private$getPanelScale(marg) 292 | range <- scale$get_limits() 293 | if (is.null(range)) { 294 | range <- scale$range$range 295 | } 296 | range 297 | } 298 | ) 299 | ) 300 | 301 | geom_density2 <- function(...) { 302 | ggplot2::geom_density(colour = "NA", ...) 303 | } 304 | 305 | geom_histogram2 <- function(...) { 306 | ggplot2::geom_histogram(ggplot2::aes(y = ggplot2::after_stat(density)), ...) 307 | } 308 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/3.5.0/ggMarginal/basic-boxplot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 3.0 93 | 3.5 94 | 4.0 95 | 4.5 96 | 5.0 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 2 107 | 3 108 | 4 109 | 5 110 | wt 111 | drat 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ggExtra - Add marginal histograms to ggplot2, and more ggplot2 enhancements 2 | 3 | [![CRAN 4 | version](https://www.r-pkg.org/badges/version/ggExtra)](https://cran.r-project.org/package=ggExtra) 5 | [![R-CMD-check](https://github.com/daattali/ggExtra/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/daattali/ggExtra/actions/workflows/R-CMD-check.yaml) 6 | 7 | > *Copyright 2016 [Dean Attali](https://deanattali.com). Licensed under 8 | > the MIT license.* 9 | 10 | `ggExtra` is a collection of functions and layers to enhance ggplot2. 11 | The flagship function is `ggMarginal`, which can be used to add marginal 12 | histograms/boxplots/density plots to ggplot2 scatterplots. You can view 13 | a [live interactive 14 | demo](https://daattali.com/shiny/ggExtra-ggMarginal-demo/) to test it 15 | out! 16 | 17 | Most other functions/layers are quite simple but are useful because they 18 | are fairly common ggplot2 operations that are a bit verbose. 19 | 20 | This is an instructional document, but I also wrote [a blog 21 | post](https://deanattali.com/2015/03/29/ggExtra-r-package/) about the 22 | reasoning behind and development of this package. 23 | 24 | Note: it was brought to my attention that several years ago there was a 25 | different package called `ggExtra`, by Baptiste (the author of 26 | `gridExtra`). That old `ggExtra` package was deleted in 2011 (two years 27 | before I even knew what R is!), and this package has nothing to do with 28 | the old one. 29 | 30 | ## Installation 31 | 32 | `ggExtra` is available through both CRAN and GitHub. 33 | 34 | To install the CRAN version: 35 | 36 | install.packages("ggExtra") 37 | 38 | To install the latest development version from GitHub: 39 | 40 | install.packages("devtools") 41 | devtools::install_github("daattali/ggExtra") 42 | 43 | ## Marginal plots RStudio addin/gadget 44 | 45 | `ggExtra` comes with an addin for `ggMarginal()`, which lets you 46 | interactively add marginal plots to a scatter plot. To use it, simply 47 | highlight the code for a ggplot2 plot in your script, and select 48 | *ggplot2 Marginal Plots* from the RStudio *Addins* menu. Alternatively, 49 | you can call the addin directly by calling `ggMarginalGadget(plot)` with 50 | a ggplot2 plot. 51 | 52 | ![ggMarginal gadget screenshot](inst/img/ggmarginal-gadget.png) 53 | 54 | ## Usage 55 | 56 | We’ll first load the package and ggplot2, and then see how all the 57 | functions work. 58 | 59 | library("ggExtra") 60 | library("ggplot2") 61 | 62 | ## `ggMarginal` - Add marginal histograms/boxplots/density plots to ggplot2 scatterplots 63 | 64 | `ggMarginal()` is an easy drop-in solution for adding marginal density 65 | plots/histograms/boxplots to a ggplot2 scatterplot. The easiest way to 66 | use it is by simply passing it a ggplot2 scatter plot, and 67 | `ggMarginal()` will add the marginal plots. 68 | 69 | As a simple first example, let’s create a dataset with 500 points where 70 | the x values are normally distributed and the y values are uniformly 71 | distributed, and plot a simple ggplot2 scatterplot. 72 | 73 | set.seed(30) 74 | df1 <- data.frame(x = rnorm(500, 50, 10), y = runif(500, 0, 50)) 75 | p1 <- ggplot(df1, aes(x, y)) + geom_point() + theme_bw() 76 | p1 77 | 78 | 79 | 80 | And now to add marginal density plots: 81 | 82 | ggMarginal(p1) 83 | 84 | 85 | 86 | That was easy. Notice how the syntax does not follow the standard 87 | ggplot2 syntax - **you don’t “add” a ggMarginal layer with 88 | `p1 + ggMarginal()`, but rather ggMarginal takes the object as an 89 | argument** and returns a different object. This means that you can use 90 | magrittr pipes, for example `p1 %>% ggMarginal()`. 91 | 92 | Let’s make the text a bit larger to make it easier to see. 93 | 94 | ggMarginal(p1 + theme_bw(30) + ylab("Two\nlines")) 95 | 96 | 97 | 98 | Notice how the marginal plots occupy the correct space; even when the 99 | main plot’s points are pushed to the right because of larger text or 100 | longer axis labels, the marginal plots automatically adjust. 101 | 102 | If your scatterplot has a factor variable mapping to a colour (ie. 103 | points in the scatterplot are colour-coded according to a variable in 104 | the data, by using `aes(colour = ...)`), then you can use 105 | `groupColour = TRUE` and/or `groupFill = TRUE` to reflect these 106 | groupings in the marginal plots. The result is multiple marginal plots, 107 | one for each colour group of points. Here’s an example using the iris 108 | dataset. 109 | 110 | piris <- ggplot(iris, aes(Sepal.Length, Sepal.Width, colour = Species)) + 111 | geom_point() 112 | ggMarginal(piris, groupColour = TRUE, groupFill = TRUE) 113 | 114 | 115 | 116 | You can also show histograms instead. 117 | 118 | ggMarginal(p1, type = "histogram") 119 | 120 | 121 | 122 | There are several more parameters, here is an example with a few more 123 | being used. Note that you can use any parameters that the `geom_XXX()` 124 | layers accept, such as `col` and `fill`, and they will be passed to 125 | these layers. 126 | 127 | ggMarginal(p1, margins = "x", size = 2, type = "histogram", 128 | col = "blue", fill = "orange") 129 | 130 | 131 | 132 | In the above example, `size = 2` means that the main scatterplot should 133 | occupy twice as much height/width as the margin plots (default is 5). 134 | The `col` and `fill` parameters are simply passed to the ggplot layer 135 | for both margin plots. 136 | 137 | If you want to specify some parameter for only one of the marginal 138 | plots, you can use the `xparams` or `yparams` parameters, like this: 139 | 140 | ggMarginal(p1, type = "histogram", xparams = list(binwidth = 1, fill = "orange")) 141 | 142 | 143 | 144 | Last but not least - you can also save the output from `ggMarginal()` 145 | and display it later. (This may sound trivial, but it was not an easy 146 | problem to solve - [see this 147 | discussion](https://stackoverflow.com/questions/29062766/store-output-from-gridextragrid-arrange-into-an-object)). 148 | 149 | p <- ggMarginal(p1) 150 | p 151 | 152 | 153 | 154 | You can also create marginal box plots and violin plots. For more 155 | information, see `?ggExtra::ggMarginal`. 156 | 157 | #### Using `ggMarginal()` in R Notebooks or Rmarkdown 158 | 159 | If you try including a `ggMarginal()` plot inside an R Notebook or 160 | Rmarkdown code chunk, you’ll notice that the plot doesn’t get output. In 161 | order to get a `ggMarginal()` to show up in an these contexts, you need 162 | to save the ggMarginal plot as a variable in one code chunk, and 163 | explicitly print it using the `grid` package in another chunk, like 164 | this: 165 | 166 | ```{r} 167 | library(ggplot2) 168 | library(ggExtra) 169 | p <- ggplot(mtcars, aes(wt, mpg)) + geom_point() 170 | p <- ggMarginal(p) 171 | ``` 172 | ```{r} 173 | grid::grid.newpage() 174 | grid::grid.draw(p) 175 | ``` 176 | 177 | ## `removeGrid` - Remove grid lines from ggplot2 178 | 179 | This is just a convenience function to save a bit of typing and 180 | memorization. Minor grid lines are always removed, and the major x or y 181 | grid lines can be removed as well (default is to remove both). 182 | 183 | `removeGridX` is a shortcut for `removeGrid(x = TRUE, y = FALSE)`, and 184 | `removeGridY` is similarly a shortcut for… 185 | . 186 | 187 | df2 <- data.frame(x = 1:50, y = 1:50) 188 | p2 <- ggplot2::ggplot(df2, ggplot2::aes(x, y)) + ggplot2::geom_point() 189 | p2 + removeGrid() 190 | 191 | 192 | 193 | For more information, see `?ggExtra::removeGrid`. 194 | 195 | ## `rotateTextX` - Rotate x axis labels 196 | 197 | Often times it is useful to rotate the x axis labels to be vertical if 198 | there are too many labels and they overlap. This function accomplishes 199 | that and ensures the labels are horizontally centered relative to the 200 | tick line. 201 | 202 | df3 <- data.frame(x = paste("Letter", LETTERS, sep = "_"), 203 | y = seq_along(LETTERS)) 204 | p3 <- ggplot2::ggplot(df3, ggplot2::aes(x, y)) + ggplot2::geom_point() 205 | p3 + rotateTextX() 206 | 207 | 208 | 209 | For more information, see `?ggExtra::rotateTextX`. 210 | 211 | ## `plotCount` - Plot count data with ggplot2 212 | 213 | This is a convenience function to quickly plot a bar plot of count 214 | (frequency) data. The input must be either a frequency table (obtained 215 | with `base::table`) or a data.frame with 2 columns where the first 216 | column contains the values and the second column contains the counts. 217 | 218 | An example using a table: 219 | 220 | plotCount(table(infert$education)) 221 | 222 | 223 | 224 | An example using a data.frame: 225 | 226 | df4 <- data.frame("vehicle" = c("bicycle", "car", "unicycle", "Boeing747"), 227 | "NumWheels" = c(2, 4, 1, 16)) 228 | plotCount(df4) + removeGridX() 229 | 230 | 231 | 232 | For more information, see `?ggExtra::plotCount`. 233 | 234 | ## Testing `ggMarginal()` visual output 235 | 236 | Visual tests (comparing plot output against expected snapshots) are essential for ensuring `ggMarginal()` produces correct plots. However, these tests are very sensitive to operating system and environment differences, which can cause test failures even when plots seem to be visually identical. To solve this, we use Docker containers to provide a reproducible testing environment with pinned package versions. 237 | 238 | When tests are run outside of Docker, the visual tests will be skipped by default unless you set the `RunVisualTests` environment variable to `"yes"`. To run the visual tests, first build a Docker container and then run it: 239 | 240 | docker build -t ggextra-test --build-arg GGPLOT2_VERSION=3.5.0 . 241 | docker run --rm ggextra-test 242 | 243 | If you want to run the tests against the latest version of {ggplot2}, simply omit the build argument from the `docker build` command. 244 | 245 | When adding new `ggMarginal()` tests (in [`tests/testthat/helper-funs.R`](tests/testthat/helper-funs.R)), you'll need to first generate the expected plots because there is nothing to test against yet. Use the same `docker build` command, but add an argument to the `run` command that will mount the container's folder onto your file system: 246 | 247 | docker run --rm -v "$(pwd):/pkg" ggextra-test 248 | 249 | Now the new snapshots will be created in your computer and you can review them to make sure they look correct. If you're happy with them, you can commit them to GitHub, and any further tests will use these images as the expectation. 250 | 251 | On GitHub, the visual tests are run against a few {ggplot2} versions that are defined in [the GitHub Action workflow](.github/workflows/test-ggplot2-versions.yml). 252 | -------------------------------------------------------------------------------- /R/ggMarginalGadget.R: -------------------------------------------------------------------------------- 1 | #' ggMarginal gadget 2 | #' 3 | #' This gadget and addin allow you to select a ggplot2 plot and interactively 4 | #' use \code{ggMarginal} to build marginal plots on top of your scatterplot. 5 | #' 6 | #' @param plot A ggplot2 scatterplot 7 | #' @note To use the RStudio addin, highlight the code for a plot in RStudio and 8 | #' select \emph{ggplot2 Marginal Plots} from the RStudio \emph{Addins} menu. This will 9 | #' embed the marginal plots code into your script. Alternatively, you can call 10 | #' \code{ggMarginalGadget()} with a ggplot2 plot, and the gadget will return 11 | #' a plot object. 12 | #' @return An object of class \code{ggExtraPlot}. This object can be printed to 13 | #' show the marginal plots or saved using any of the typical image-saving functions 14 | #' @export 15 | #' @examples 16 | #' if (interactive()) { 17 | #' plot <- ggplot2::ggplot(mtcars, ggplot2::aes(wt, mpg)) + ggplot2::geom_point() 18 | #' plot2 <- ggMarginalGadget(plot) 19 | #' } 20 | ggMarginalGadget <- function(plot) { 21 | if (missing(plot)) { 22 | stop("You must provide a ggplot2 plot.", call. = FALSE) 23 | } 24 | 25 | ggMarginalGadgetHelper(deparse(substitute(plot)), addin = FALSE) 26 | } 27 | 28 | ggMarginalGadgetAddin <- function() { 29 | context <- rstudioapi::getActiveDocumentContext() 30 | text <- context$selection[[1]]$text 31 | if (nchar(text) == 0) { 32 | stop("Please highlight a ggplot2 plot before selecting this addin.") 33 | } 34 | ggMarginalGadgetHelper(text, addin = TRUE) 35 | } 36 | 37 | #' @import shiny 38 | #' @import miniUI 39 | ggMarginalGadgetHelper <- function(origPlot, addin) { 40 | if (!requireNamespace("rstudioapi", quietly = TRUE)) { 41 | stop("You must have RStudio v0.99.878 or newer to use the colour picker", 42 | call. = FALSE 43 | ) 44 | } 45 | 46 | # Remove leading and trailing whitespace from input 47 | origPlot <- trimws(origPlot) 48 | 49 | # If the given plot is a variable (object) holding a plot object, use that 50 | # as the plot name 51 | if (exists(origPlot)) { 52 | plotname <- origPlot 53 | baseCode <- "" 54 | } 55 | # If the given plot is not an object, it means it's probably actual code 56 | # for generating a plot. So assign that code to a a new unique variable 57 | else { 58 | 59 | # Find a unique variable to assign to this plot (make sure this variable 60 | # name isn't already in use) 61 | plotnum <- 1 62 | while (TRUE) { 63 | plotname <- paste0("p", plotnum) 64 | if (!exists(plotname)) { 65 | break 66 | } 67 | plotnum <- plotnum + 1 68 | } 69 | 70 | tryCatch( 71 | assign(plotname, eval(parse(text = origPlot)), envir = .GlobalEnv), 72 | error = function(err) { 73 | stop("You did not provide a valid ggplot2 plot.", call. = FALSE) 74 | } 75 | ) 76 | baseCode <- paste0(plotname, " <- ", origPlot, "\n\n") 77 | } 78 | 79 | if (!ggplot2::is.ggplot(get(plotname, envir = .GlobalEnv))) { 80 | stop("You did not provide a ggplot2 plot.", call. = FALSE) 81 | } 82 | 83 | resourcePath <- system.file("gadgets", "ggmarginal", package = "ggExtra") 84 | shiny::addResourcePath("ggm", resourcePath) 85 | 86 | ui <- miniPage( 87 | shinyjs::useShinyjs(), 88 | tags$head(includeCSS(file.path(resourcePath, "css", "app.css"))), 89 | tags$script("$(document).on('shiny:connected', function(event) { 90 | var height = $('#panels-area').height(); 91 | Shiny.onInputChange('plotHeight', height); 92 | });"), 93 | 94 | gadgetTitleBar( 95 | span( 96 | strong("Add marginal plots to ggplot2"), 97 | span( 98 | id = "author", "By", 99 | a(href = "https://deanattali.com", "Dean Attali") 100 | ) 101 | ) 102 | ), 103 | 104 | shinyjs::hidden( 105 | div( 106 | id = "error", 107 | div("Error with the advanced options:"), 108 | div(tags$i(id = "errorMsg")) 109 | ) 110 | ), 111 | 112 | plotOutput("plot", width = "60%", height = "auto"), 113 | img( 114 | id = "plot-spinner", 115 | src = file.path("ggm", "img", "ajax-loader.gif") 116 | ), 117 | 118 | miniTabstripPanel( 119 | miniTabPanel( 120 | "Main options", 121 | icon = icon("cog"), 122 | miniContentPanel( 123 | id = "panels-area", 124 | padding = 0, 125 | fillRow( 126 | flex = c(2, 3), 127 | fillCol( 128 | class = "left-panel-area", 129 | selectInput( 130 | "type", "Plot type", 131 | c("density", "histogram", "boxplot", "violin", "densigram") 132 | ), 133 | selectInput( 134 | "margins", "Which margins?", 135 | c("both", "x axis only" = "x", "y axis only" = "y") 136 | ), 137 | sliderInput( 138 | "size", 139 | "Size ratio of main plot:marginal plots", 140 | 1, 5, 5, 0.5 141 | ), 142 | div() 143 | ), 144 | fillCol( 145 | class = "plot-area", 146 | div() 147 | ) 148 | ) 149 | ) 150 | ), 151 | miniTabPanel( 152 | "Colours", 153 | icon = icon("paint-brush"), 154 | miniContentPanel( 155 | padding = 0, 156 | fillRow( 157 | flex = c(2, 3), 158 | fillCol( 159 | class = "left-panel-area", 160 | colourpicker::colourInput("col", "Marginal plot colour", "black", 161 | showColour = "background", returnName = TRUE, 162 | allowTransparent = TRUE 163 | ), 164 | colourpicker::colourInput("fill", "Marginal plot fill colour", "gray", 165 | showColour = "background", returnName = TRUE, 166 | allowTransparent = TRUE 167 | ), 168 | div( 169 | helpText( 170 | "Colour must be mapped to a factor or character variable", 171 | "in order to use the two options below." 172 | ), 173 | checkboxInput("groupColour", "Show groups as 'colour'", FALSE), 174 | checkboxInput("groupFill", "Show groups as 'fill'", FALSE) 175 | ), 176 | div() 177 | ), 178 | fillCol( 179 | class = "plot-area", 180 | div() 181 | ) 182 | ) 183 | ) 184 | ), 185 | miniTabPanel( 186 | "Advanced options", 187 | icon = icon("sliders"), 188 | miniContentPanel( 189 | padding = 0, 190 | fillRow( 191 | flex = c(2, 3), 192 | fillCol( 193 | flex = c(NA, 1, 1, 1), 194 | class = "left-panel-area", 195 | div( 196 | h3(style = "margin-top:0;", "Extra parameters to pass to the marginal plots"), 197 | div( 198 | "Any parameter that ", 199 | tags$code(textOutput("extraType", inline = TRUE)), 200 | " accepts", br(), 201 | textOutput("extraExample", inline = TRUE), br(), tags$hr(), br() 202 | ) 203 | ), 204 | textInput("extraparams", "Parameters for both plots"), 205 | textInput("xparams", "X axis plot only"), 206 | textInput("yparams", "Y axis plot only") 207 | ), 208 | fillCol( 209 | class = "plot-area", 210 | div() 211 | ) 212 | ) 213 | ) 214 | ), 215 | miniTabPanel( 216 | "Code", 217 | icon = icon("code"), 218 | miniContentPanel( 219 | padding = 0, 220 | fillRow( 221 | flex = c(2, 3), 222 | fillCol( 223 | class = "left-panel-area", 224 | verbatimTextOutput("code") 225 | ), 226 | fillCol( 227 | class = "plot-area", 228 | div() 229 | ) 230 | ) 231 | ) 232 | ) 233 | ) 234 | ) 235 | 236 | server <- function(input, output) { 237 | 238 | # User canceled 239 | observeEvent(input$cancel, { 240 | stopApp(stop("User canceled", call. = FALSE)) 241 | }) 242 | 243 | values <- reactiveValues( 244 | plot = NULL, 245 | error = NULL 246 | ) 247 | 248 | observeEvent(input$done, { 249 | if (addin) { 250 | rstudioapi::insertText(completeCode()) 251 | stopApp() 252 | } else { 253 | stopApp(values$plot) 254 | } 255 | }) 256 | 257 | observe({ 258 | shinyjs::toggleState(id = "xparams", condition = input$margins != "y") 259 | shinyjs::toggleState(id = "yparams", condition = input$margins != "x") 260 | }) 261 | 262 | output$extraType <- renderText({ 263 | if (input$type == "density") { 264 | "geom_line()" 265 | } else { 266 | paste0("geom_", input$type, "()") 267 | } 268 | }) 269 | 270 | output$extraExample <- renderText({ 271 | if (input$type == "density") { 272 | "(e.g. adjust = 3)" 273 | } else if (input$type == "histogram") { 274 | "(e.g. bins = 10)" 275 | } else { 276 | '(e.g. outlier.colour = "red")' 277 | } 278 | }) 279 | 280 | observeEvent(values$error, ignoreNULL = FALSE, { 281 | shinyjs::hide("error") 282 | shinyjs::toggleState("done", condition = is.null(values$error)) 283 | if (is.null(values$error)) { 284 | shinyjs::hide(id = "error") 285 | } else { 286 | shinyjs::html("errorMsg", as.character(values$error)) 287 | shinyjs::show(id = "error", anim = TRUE, animType = "fade") 288 | } 289 | }) 290 | 291 | observeEvent(marginCode(), { 292 | tryCatch({ 293 | values$plot <- eval(parse(text = marginCode()), envir = .GlobalEnv) 294 | values$error <- NULL 295 | }, error = function(err) { 296 | values$error <- as.character(err$message) 297 | }) 298 | }) 299 | 300 | output$plot <- renderPlot({ 301 | if (is.null(input$plotHeight)) return(NULL) 302 | values$plot 303 | }, height = function() { 304 | if (is.null(input$plotHeight)) { 305 | 1 306 | } else { 307 | input$plotHeight 308 | } 309 | }) 310 | 311 | marginCode <- reactive({ 312 | code <- "" 313 | code <- paste0(code, sprintf(paste0( 314 | "ggExtra::ggMarginal(\n", 315 | " p = %s,\n", 316 | " type = '%s',\n", 317 | " margins = '%s',\n", 318 | " size = %s,\n" 319 | ), plotname, input$type, input$margins, input$size)) 320 | 321 | if (input$groupColour) { 322 | code <- paste0(code, " groupColour = TRUE,\n") 323 | } else { 324 | code <- paste0(code, " colour = '", input$col, "',\n") 325 | } 326 | if (input$groupFill) { 327 | code <- paste0(code, " groupFill = TRUE") 328 | } else { 329 | code <- paste0(code, " fill = '", input$fill, "'") 330 | } 331 | 332 | if (input$margins != "x") { 333 | yparams <- trimws(input$yparams) 334 | if (nzchar(yparams)) { 335 | code <- paste0(code, sprintf(",\n yparams = list(%s)", yparams)) 336 | } 337 | } 338 | if (input$margins != "y") { 339 | xparams <- trimws(input$xparams) 340 | if (nzchar(xparams)) { 341 | code <- paste0(code, sprintf(",\n xparams = list(%s)", xparams)) 342 | } 343 | } 344 | extraparams <- trimws(input$extraparams) 345 | if (nzchar(extraparams)) { 346 | code <- paste0(code, sprintf(",\n %s", extraparams)) 347 | } 348 | 349 | code <- paste0(code, "\n)") 350 | code 351 | }) 352 | 353 | completeCode <- reactive({ 354 | paste0(baseCode, marginCode()) 355 | }) 356 | 357 | output$code <- renderText({ 358 | completeCode() 359 | }) 360 | } 361 | 362 | viewer <- dialogViewer("Add marginal plots to ggplot2", 1000, 630) 363 | runGadget(shinyApp(ui, server), viewer = viewer, stopOnCancel = FALSE) 364 | } 365 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/4.0.0/ggMarginal/basic-boxplot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 3.0 93 | 3.5 94 | 4.0 95 | 4.5 96 | 5.0 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 2 107 | 3 108 | 4 109 | 5 110 | wt 111 | drat 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/3.5.0/ggMarginal/center-and-boundary-set.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 2 91 | 4 92 | 6 93 | 8 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 0.0 104 | 2.5 105 | 5.0 106 | 7.5 107 | 10.0 108 | cyl 109 | carb 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/3.5.0/ggMarginal/widths-of-boxplots-are-the-same-within-a-marginal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 3.0 93 | 3.5 94 | 4.0 95 | 4.5 96 | 5.0 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 2 107 | 3 108 | 4 109 | 5 110 | wt 111 | drat 112 | 113 | factor(vs) 114 | 115 | 116 | 117 | 118 | 0 119 | 1 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/4.0.0/ggMarginal/center-and-boundary-set.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 2 91 | 4 92 | 6 93 | 8 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 0.0 104 | 2.5 105 | 5.0 106 | 7.5 107 | 10.0 108 | cyl 109 | carb 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/4.0.0/ggMarginal/widths-of-boxplots-are-the-same-within-a-marginal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 3.0 93 | 3.5 94 | 4.0 95 | 4.5 96 | 5.0 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 2 107 | 3 108 | 4 109 | 5 110 | wt 111 | drat 112 | 113 | factor(vs) 114 | 115 | 116 | 117 | 118 | 0 119 | 1 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/3.5.0/ggMarginal/x-axis-limits-for-histograms.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 3.0 65 | 3.5 66 | 4.0 67 | 4.5 68 | 5.0 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 0.0 80 | 0.5 81 | 1.0 82 | 1.5 83 | 2.0 84 | wt 85 | drat 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | --------------------------------------------------------------------------------