├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ ├── R-CMD-check.yaml │ ├── pkgdown.yaml │ └── test-coverage.yaml ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── facet_ragged.R ├── facet_ragged_cols.R ├── facet_ragged_rows.R ├── ggragged-package.R ├── grid.R ├── gtable.R └── layout.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── codecov.yml ├── cran-comments.md ├── ggragged.Rproj ├── man ├── facet_ragged.Rd ├── figures │ ├── README-example-1.png │ ├── README-example-2.png │ └── logo.png └── ggragged-package.Rd ├── pkgdown └── favicon │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon.ico ├── tests ├── testthat.R └── testthat │ ├── _snaps │ ├── facet_ragged_cols │ │ ├── cols-aligned-to-end-in-an-inverted-l-shape.svg │ │ ├── x-axes-have-varying-ranges-when-free.svg │ │ ├── x-axes-on-both-sides-default.svg │ │ └── x-axes-on-both-sides-switched.svg │ └── facet_ragged_rows │ │ ├── rows-aligned-to-end-in-an-inverted-l-shape.svg │ │ ├── y-axes-on-both-sides-default.svg │ │ └── y-axes-on-both-sides-switched.svg │ ├── test-facet_ragged_cols.R │ ├── test-facet_ragged_rows.R │ └── test-layout.R └── vignettes ├── .gitignore └── ggragged.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^ggragged\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | ^README\.Rmd$ 5 | ^_pkgdown\.yml$ 6 | ^docs$ 7 | ^pkgdown$ 8 | ^\.github$ 9 | ^codecov\.yml$ 10 | ^cran-comments\.md$ 11 | ^CRAN-SUBMISSION$ 12 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.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 | branches: [main, master] 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macos-latest, r: 'release'} 22 | - {os: windows-latest, r: 'release'} 23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: '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@v3 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 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.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 | branches: [main, master] 8 | release: 9 | types: [published] 10 | workflow_dispatch: 11 | 12 | name: pkgdown 13 | 14 | jobs: 15 | pkgdown: 16 | runs-on: ubuntu-latest 17 | # Only restrict concurrency for non-PR jobs 18 | concurrency: 19 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 20 | env: 21 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 22 | steps: 23 | - uses: actions/checkout@v3 24 | 25 | - uses: r-lib/actions/setup-pandoc@v2 26 | 27 | - uses: r-lib/actions/setup-r@v2 28 | with: 29 | use-public-rspm: true 30 | 31 | - uses: r-lib/actions/setup-r-dependencies@v2 32 | with: 33 | extra-packages: any::pkgdown, local::. 34 | needs: website 35 | 36 | - name: Build site 37 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 38 | shell: Rscript {0} 39 | 40 | - name: Deploy to GitHub pages 🚀 41 | if: github.event_name != 'pull_request' 42 | uses: JamesIves/github-pages-deploy-action@v4.4.1 43 | with: 44 | clean: false 45 | branch: gh-pages 46 | folder: docs 47 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.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 | branches: [main, master] 8 | 9 | name: test-coverage 10 | 11 | jobs: 12 | test-coverage: 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - uses: r-lib/actions/setup-r@v2 21 | with: 22 | use-public-rspm: true 23 | 24 | - uses: r-lib/actions/setup-r-dependencies@v2 25 | with: 26 | extra-packages: any::covr 27 | needs: coverage 28 | 29 | - name: Test coverage 30 | run: | 31 | covr::codecov( 32 | quiet = FALSE, 33 | clean = FALSE, 34 | install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") 35 | ) 36 | shell: Rscript {0} 37 | 38 | - name: Show testthat output 39 | if: always() 40 | run: | 41 | ## -------------------------------------------------------------------- 42 | find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true 43 | shell: bash 44 | 45 | - name: Upload test results 46 | if: failure() 47 | uses: actions/upload-artifact@v3 48 | with: 49 | name: coverage-test-failures 50 | path: ${{ runner.temp }}/package 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .Rdata 4 | .httr-oauth 5 | .DS_Store 6 | docs 7 | inst/doc 8 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: ggragged 2 | Title: Ragged Grids for 'ggplot2' 3 | Version: 0.2.0 4 | Authors@R: 5 | person("Mikko", "Marttila", , "mikkmart@protonmail.com", role = c("aut", "cre", "cph")) 6 | Description: Extend 'ggplot2' facets to panel layouts arranged in a grid 7 | with ragged edges. facet_ragged_rows() groups panels into rows that 8 | can vary in length, facet_ragged_cols() does the same but for columns. 9 | These can be useful, for example, to represent nested or partially 10 | crossed relationships between faceting variables. 11 | License: MIT + file LICENSE 12 | URL: https://github.com/mikmart/ggragged, 13 | https://mikmart.github.io/ggragged/ 14 | BugReports: https://github.com/mikmart/ggragged/issues 15 | Depends: 16 | ggplot2 17 | Imports: 18 | grid, 19 | gtable, 20 | rlang, 21 | vctrs 22 | Suggests: 23 | covr, 24 | knitr, 25 | nlme, 26 | ragg, 27 | rmarkdown, 28 | roxygen2, 29 | testthat (>= 3.0.0), 30 | vdiffr 31 | VignetteBuilder: 32 | knitr 33 | Config/testthat/edition: 3 34 | Encoding: UTF-8 35 | Roxygen: list(markdown = TRUE) 36 | RoxygenNote: 7.3.2 37 | Collate: 38 | 'facet_ragged.R' 39 | 'facet_ragged_rows.R' 40 | 'facet_ragged_cols.R' 41 | 'ggragged-package.R' 42 | 'grid.R' 43 | 'gtable.R' 44 | 'layout.R' 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2023 2 | COPYRIGHT HOLDER: ggragged authors 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2023 ggragged authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(facet_ragged_cols) 4 | export(facet_ragged_rows) 5 | import(ggplot2) 6 | import(gtable) 7 | importFrom(rlang,"%||%") 8 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # ggragged 0.2.0 2 | 3 | ## New features 4 | 5 | * Facets gain a `strips` parameter to control how strips are drawn between panels. 6 | * Facets gain an `axes` parameter to control how axes are drawn between panels. 7 | * Facets gain an `align` parameter to control how panels are positioned within rows/columns. 8 | * Added a vignette showing examples of usage in broader context. 9 | 10 | ## Bug fixes 11 | 12 | * Fixed an issue that caused some axes to be rendered incorrectly when using 13 | free scales with `coord_flip()` (#2). 14 | * Fixed an issue that caused the package to fail to build (with an "argument is 15 | missing" error message) when an older version of ggplot2 was installed. 16 | 17 | # ggragged 0.1.0 18 | 19 | * Initial release. 20 | -------------------------------------------------------------------------------- /R/facet_ragged.R: -------------------------------------------------------------------------------- 1 | #' Lay out panels in a ragged grid 2 | #' 3 | #' These facets create layouts in-between [ggplot2::facet_wrap()] and 4 | #' [ggplot2::facet_grid()]. Panels are arranged into groups stacked along the 5 | #' defining dimension, but remain independent in the other dimension, allowing 6 | #' for a grid with ragged edges. This can be useful, for example, to represent 7 | #' nested or partially crossed relationships between faceting variables. 8 | #' 9 | #' @param rows,cols A set of variables or expressions quoted by [ggplot2::vars()], 10 | #' the combinations of which define the panels in the layout. 11 | #' @param ... Arguments reserved for future use. 12 | #' @param scales Determines which panels share axis ranges. By default (`"fixed"`), 13 | #' all panels share the same scales. Use `"free_x"` to let x-axes vary, use 14 | #' `"free_y"` to let y-axes vary, or `"free"` to let both axes vary. Panels 15 | #' within groups always share the scale along the grouping dimension. 16 | #' @param switch Determines how facet label strips are positioned. By default 17 | #' (`"none"`), strips are drawn to the top and right of the panels. Use `"x"` 18 | #' to switch the top strip to the bottom, use `"y"` to switch the right strip 19 | #' to the left, or `"both"` to do both. 20 | #' @param strips Determines which facet label strips are drawn. By default 21 | #' (`"margins"`), strips between panels along the grouping dimension will be 22 | #' suppressed. Use `"all"` to always draw both strips. 23 | #' @param axes Determines which axes are drawn. By default (`"margins"`), axes 24 | #' between panels will be suppressed if they are fixed. Use `"all_x"` to 25 | #' always draw x-axes, `"all_y"` to always draw y-axes, or `"all"` to always 26 | #' draw both axes. 27 | #' @param align Determines how panels are positioned within groups. By default 28 | #' (`"start"`), panels in groups are densely packed from the start. Use 29 | #' `"end"` to instead pack panels to the end of the group. 30 | #' @inheritParams ggplot2::facet_wrap 31 | #' 32 | #' @returns A `Facet` that can be added to a `ggplot`. 33 | #' 34 | #' @examples 35 | #' p <- ggplot(mpg, aes(displ, cty)) + geom_point() 36 | #' p + facet_ragged_rows(vars(drv), vars(cyl)) 37 | #' p + facet_ragged_cols(vars(cyl), vars(drv)) 38 | #' \donttest{ 39 | #' # Allow axes to vary between panels 40 | #' p + facet_ragged_rows(vars(drv), vars(cyl), scales = "free_y") 41 | #' p + facet_ragged_rows(vars(drv), vars(cyl), scales = "free") 42 | #' 43 | #' # Change strip label positions 44 | #' p + facet_ragged_rows(vars(drv), vars(cyl), switch = "y") 45 | #' p + facet_ragged_rows(vars(drv), vars(cyl), switch = "both") 46 | #' 47 | #' # Draw strips between panels 48 | #' p + facet_ragged_rows(vars(drv), vars(cyl), strips = "all") 49 | #' 50 | #' # Draw axes between panels 51 | #' p + facet_ragged_rows(vars(drv), vars(cyl), axes = "all_x") 52 | #' p + facet_ragged_rows(vars(drv), vars(cyl), axes = "all") 53 | #' } 54 | #' # Change panel alignment 55 | #' p + facet_ragged_rows(vars(drv), vars(cyl), align = "end") 56 | #' @name facet_ragged 57 | NULL 58 | 59 | FacetRagged <- ggproto("FacetRagged", Facet, 60 | shrink = TRUE, 61 | 62 | setup_params = function(data, params) { 63 | params <- Facet$setup_params(data, params) 64 | params$rows <- rlang::quos_auto_name(params$rows) 65 | params$cols <- rlang::quos_auto_name(params$cols) 66 | params$free <- list( 67 | x = params$scales %in% c("free_x", "free"), 68 | y = params$scales %in% c("free_y", "free") 69 | ) 70 | params$switch <- list( 71 | x = params$switch %in% c("x", "both"), 72 | y = params$switch %in% c("y", "both") 73 | ) 74 | params$axes <- list( 75 | x = params$axes %in% c("all_x", "all"), 76 | y = params$axes %in% c("all_y", "all") 77 | ) 78 | params$strip.position <- c( 79 | if (params$switch$x) "bottom" else "top", 80 | if (params$switch$y) "left" else "right" 81 | ) 82 | params 83 | }, 84 | 85 | map_data = function(data, layout, params) { 86 | FacetGrid$map_data(data, layout, params) 87 | }, 88 | 89 | vars = function(self) { 90 | names(c(self$params$rows, self$params$cols)) 91 | }, 92 | 93 | draw_panels = function(self, panels, layout, x_scales, y_scales, ranges, coord, data, theme, params) { 94 | table <- self$init_gtable(panels, layout, ranges, coord, theme, params) 95 | table <- self$attach_axes(table, layout, ranges, coord, theme, params) 96 | table <- self$attach_strips(table, layout, theme, params) 97 | table <- self$finalise_gtable(table, layout, params) 98 | table 99 | }, 100 | 101 | init_gtable = function(panels, layout, ranges, coord, theme, params) { 102 | if (!coord$is_free() && (params$free$x || params$free$y)) 103 | stop("Can't use free scales with a fixed coordinate system.") 104 | aspect_ratio <- theme$aspect.ratio %||% coord$aspect(ranges[[1]]) 105 | 106 | # Create an empty table with dimensions from layout 107 | rows_count <- max(layout$ROW) 108 | cols_count <- max(layout$COL) 109 | widths <- rep(unit(1, "null"), cols_count) 110 | heights <- rep(unit(aspect_ratio %||% 1, "null"), rows_count) 111 | table <- gtable(widths, heights, respect = !is.null(aspect_ratio)) 112 | 113 | # Insert panel grobs according to layout and add spacing 114 | panel_name <- sprintf("panel-%d", layout$PANEL) 115 | table <- gtable_add_grob(table, panels, layout$ROW, layout$COL, name = panel_name) 116 | table <- gtable_add_col_space(table, calc_element("panel.spacing.x", theme)) 117 | table <- gtable_add_row_space(table, calc_element("panel.spacing.y", theme)) 118 | 119 | table 120 | }, 121 | 122 | attach_axes = function(table, layout, ranges, coord, theme, params) { 123 | axes <- render_unique_axes(layout, ranges, coord, theme) 124 | axes <- list( 125 | t = lapply(axes$x, `[[`, "top"), 126 | b = lapply(axes$x, `[[`, "bottom"), 127 | l = lapply(axes$y, `[[`, "left"), 128 | r = lapply(axes$y, `[[`, "right") 129 | ) 130 | add_panel_decorations(table, layout, axes, kind = "axis") 131 | }, 132 | 133 | attach_strips = function(table, layout, theme, params) { 134 | # Render strips with faceting variable data 135 | cols_data <- layout[names(params$cols)] 136 | rows_data <- layout[names(params$rows)] 137 | strips <- render_unique_strips(cols_data, rows_data, params$labeller, theme) 138 | strips <- c(strips$x, strips$y) 139 | 140 | # Zero out strips which shouldn't be added 141 | for (side in c("top", "bottom", "left", "right")) 142 | if (!side %in% params$strip.position) 143 | strips[[side]][] <- list(zeroGrob()) 144 | 145 | # Make strips stick correctly in zero-sized rows/cols 146 | for (side in c("top", "bottom", "left", "right")) 147 | strips[[side]] <- lapply(strips[[side]], set_strip_viewport, side) 148 | 149 | add_panel_decorations(table, layout, strips, kind = "strip") 150 | } 151 | ) 152 | 153 | render_unique_axes <- function(layout, ranges, coord, theme) { 154 | if (inherits(coord, "CoordFlip")) { 155 | # Switch the scales back 156 | layout[c("SCALE_X", "SCALE_Y")] <- layout[c("SCALE_Y", "SCALE_X")] 157 | } 158 | 159 | # Identify groups 160 | SCALE_X <- match(layout$SCALE_X, unique(layout$SCALE_X)) 161 | SCALE_Y <- match(layout$SCALE_Y, unique(layout$SCALE_Y)) 162 | 163 | # Render representatives 164 | x_rep <- ranges[match(unique(SCALE_X), SCALE_X)] 165 | y_rep <- ranges[match(unique(SCALE_Y), SCALE_Y)] 166 | axes <- render_axes(x_rep, y_rep, coord, theme) 167 | 168 | # Distribute to groups 169 | axes$x <- axes$x[SCALE_X] 170 | axes$y <- axes$y[SCALE_Y] 171 | axes 172 | } 173 | 174 | render_unique_strips <- function(x, y, labeller, theme) { 175 | # Identify groups 176 | STRIP_X <- vctrs::vec_match(x, vctrs::vec_unique(x)) 177 | STRIP_Y <- vctrs::vec_match(y, vctrs::vec_unique(y)) 178 | 179 | # Render representatives 180 | x_rep <- vctrs::vec_slice(x, match(unique(STRIP_X), STRIP_X)) 181 | y_rep <- vctrs::vec_slice(y, match(unique(STRIP_Y), STRIP_Y)) 182 | strips <- render_strips(x_rep, y_rep, labeller, theme) 183 | 184 | # Distribute to groups 185 | strips$x <- lapply(strips$x, function(x) x[STRIP_X]) 186 | strips$y <- lapply(strips$y, function(y) y[STRIP_Y]) 187 | strips 188 | } 189 | 190 | add_panel_decorations <- function(table, layout, grobs, kind) { 191 | kind <- rlang::arg_match0(kind, c("axis", "strip")) 192 | 193 | # Add rows for horizontal decorations 194 | height_t <- max_height(grobs$t) 195 | height_b <- max_height(grobs$b) 196 | for (t in sort(panel_rows(table)$t, decreasing = TRUE)) { 197 | table <- gtable_add_rows(table, height_t, t - 1) 198 | table <- gtable_add_rows(table, height_b, t + 1) 199 | } 200 | 201 | # Add columns for vertical decorations 202 | width_l <- max_width(grobs$l) 203 | width_r <- max_width(grobs$r) 204 | for (l in sort(panel_cols(table)$l, decreasing = TRUE)) { 205 | table <- gtable_add_cols(table, width_l, l - 1) 206 | table <- gtable_add_cols(table, width_r, l + 1) 207 | } 208 | 209 | # Find panel positions after layout changes 210 | panel_pos <- gtable_get_grob_position(table, sprintf("panel-%d", layout$PANEL)) 211 | 212 | # Add decorations around panels 213 | table <- gtable_add_grob(table, grobs$t, panel_pos$t - 1, panel_pos$l, name = sprintf("%s-t-%d", kind, layout$PANEL)) 214 | table <- gtable_add_grob(table, grobs$b, panel_pos$b + 1, panel_pos$l, name = sprintf("%s-b-%d", kind, layout$PANEL)) 215 | table <- gtable_add_grob(table, grobs$l, panel_pos$t, panel_pos$l - 1, name = sprintf("%s-l-%d", kind, layout$PANEL)) 216 | table <- gtable_add_grob(table, grobs$r, panel_pos$t, panel_pos$r + 1, name = sprintf("%s-r-%d", kind, layout$PANEL)) 217 | 218 | table 219 | } 220 | 221 | cull_inner_panel_decorations <- function(table, layout, sides, kind) { 222 | kind <- rlang::arg_match0(kind, c("axis", "strip")) 223 | for (side in sides) { 224 | # Remove grobs from inner panels 225 | panels <- panels_with_neighbour(layout, side) 226 | names <- sprintf("%s-%s-%d", kind, side, panels) 227 | table <- gtable_set_grobs(table, names, list(zeroGrob())) 228 | 229 | # And the space allocated for them 230 | table <- switch( 231 | side, 232 | t = , 233 | b = gtable_set_height(table, names, unit(0, "cm")), 234 | l = , 235 | r = gtable_set_width(table, names, unit(0, "cm")), 236 | stop("internal error: invalid side: ", side) 237 | ) 238 | 239 | # Shift axes at inner margins to start at strip edge. It would be much 240 | # cleaner to have the axes attached to the strips, but that doesn't play 241 | # nicely with how ggplot2 expects the axes to be present in the gtable. 242 | if (kind == "strip") 243 | table <- shift_inner_margin_axes(table, layout, side) 244 | } 245 | table 246 | } 247 | 248 | shift_inner_margin_axes <- function(table, layout, side) { 249 | for (panel in inner_margin_panels(layout, side)) { 250 | # Get the strip and axis, bailing if either isn't there 251 | strip_name <- sprintf("strip-%s-%d", side, panel) 252 | strip <- gtable_get_grob(table, strip_name) 253 | if (is.null(strip) || inherits(strip, "zeroGrob")) next 254 | 255 | axis_name <- sprintf("axis-%s-%d", side, panel) 256 | axis <- gtable_get_grob(table, axis_name) 257 | if (is.null(axis) || inherits(axis, "zeroGrob")) next 258 | 259 | # Shift the axis to start at the edge of the strip 260 | axis <- switch( 261 | side, 262 | t = grob_shift_viewport(axis, y = +grid::grobHeight(strip)), 263 | b = grob_shift_viewport(axis, y = -grid::grobHeight(strip)), 264 | l = grob_shift_viewport(axis, x = -grid::grobWidth(strip)), 265 | r = grob_shift_viewport(axis, x = +grid::grobWidth(strip)), 266 | stop("internal error: invalid side: ", side) 267 | ) 268 | table <- gtable_set_grobs(table, axis_name, list(axis)) 269 | } 270 | table 271 | } 272 | 273 | set_strip_viewport <- function(strip, side) { 274 | strip$vp <- switch( 275 | substr(side, 1, 1), 276 | # TODO: `clip = "off"` not needed in ggplot2 dev version (3.5.1.9000), could be removed in the future. 277 | t = grid::viewport(clip = "off", height = grid::grobHeight(strip), y = unit(0, "npc"), just = "bottom"), 278 | b = grid::viewport(clip = "off", height = grid::grobHeight(strip), y = unit(1, "npc"), just = "top"), 279 | l = grid::viewport(clip = "off", width = grid::grobWidth(strip), x = unit(1, "npc"), just = "right"), 280 | r = grid::viewport(clip = "off", width = grid::grobWidth(strip), x = unit(0, "npc"), just = "left"), 281 | stop("internal error: invalid side: ", side) 282 | ) 283 | strip 284 | } 285 | -------------------------------------------------------------------------------- /R/facet_ragged_cols.R: -------------------------------------------------------------------------------- 1 | #' @include facet_ragged_rows.R 2 | #' @rdname facet_ragged 3 | #' @export 4 | facet_ragged_cols <- function(rows, cols, ..., scales = "fixed", switch = "none", strips = "margins", axes = "margins", align = "start", labeller = "label_value") { 5 | rlang::check_dots_empty() 6 | switch <- switch %||% "none" # Compatibility with old default value NULL 7 | 8 | scales <- rlang::arg_match0(scales, c("fixed", "free_x", "free_y", "free")) 9 | switch <- rlang::arg_match0(switch, c("none", "x", "y", "both")) 10 | strips <- rlang::arg_match0(strips, c("margins", "all")) 11 | axes <- rlang::arg_match0(axes, c("margins", "all_x", "all_y", "all")) 12 | align <- rlang::arg_match0(align, c("start", "end")) 13 | 14 | ggproto( 15 | NULL, 16 | FacetRaggedCols, 17 | params = list( 18 | rows = rows, 19 | cols = cols, 20 | scales = scales, 21 | switch = switch, 22 | strips = strips, 23 | axes = axes, 24 | align = align, 25 | labeller = labeller 26 | ) 27 | ) 28 | } 29 | 30 | FacetRaggedCols <- ggproto("FacetRaggedCols", FacetRagged, 31 | compute_layout = function(data, params) { 32 | rows <- params$rows 33 | cols <- params$cols 34 | vars <- c(cols, rows) 35 | 36 | panels <- combine_vars( 37 | data = data, 38 | env = params$plot_env, 39 | vars = vars, 40 | drop = TRUE 41 | ) 42 | panels <- vctrs::vec_sort(panels) 43 | layout <- layout_ragged_cols(panels[names(cols)], params$free, params$align) 44 | 45 | cbind(layout, panels) 46 | }, 47 | 48 | finalise_gtable = function(table, layout, params) { 49 | if (!params$axes$x) 50 | table <- cull_inner_panel_decorations(table, layout, sides = c("t", "b"), kind = "axis") 51 | 52 | if (!params$axes$y && !params$free$y) 53 | table <- cull_inner_panel_decorations(table, layout, sides = c("l", "r"), kind = "axis") 54 | 55 | if (params$strips == "margins") 56 | table <- cull_inner_panel_decorations(table, layout, sides = c("t", "b"), kind = "strip") 57 | 58 | table 59 | } 60 | ) 61 | 62 | layout_ragged_cols <- function(x, free = list(), align = "start") { 63 | layout <- layout_ragged(x, groups = "cols", align = align) 64 | layout$SCALE_X <- if (!isTRUE(free$x)) 1L else layout$COL 65 | layout$SCALE_Y <- if (!isTRUE(free$y)) 1L else layout$PANEL 66 | layout 67 | } 68 | -------------------------------------------------------------------------------- /R/facet_ragged_rows.R: -------------------------------------------------------------------------------- 1 | #' @include facet_ragged.R 2 | #' @rdname facet_ragged 3 | #' @export 4 | facet_ragged_rows <- function(rows, cols, ..., scales = "fixed", switch = "none", strips = "margins", axes = "margins", align = "start", labeller = "label_value") { 5 | rlang::check_dots_empty() 6 | switch <- switch %||% "none" # Compatibility with old default value NULL 7 | 8 | scales <- rlang::arg_match0(scales, c("fixed", "free_x", "free_y", "free")) 9 | switch <- rlang::arg_match0(switch, c("none", "x", "y", "both")) 10 | strips <- rlang::arg_match0(strips, c("margins", "all")) 11 | axes <- rlang::arg_match0(axes, c("margins", "all_x", "all_y", "all")) 12 | align <- rlang::arg_match0(align, c("start", "end")) 13 | 14 | ggproto( 15 | NULL, 16 | FacetRaggedRows, 17 | params = list( 18 | rows = rows, 19 | cols = cols, 20 | scales = scales, 21 | switch = switch, 22 | strips = strips, 23 | axes = axes, 24 | align = align, 25 | labeller = labeller 26 | ) 27 | ) 28 | } 29 | 30 | FacetRaggedRows <- ggproto("FacetRaggedRows", FacetRagged, 31 | compute_layout = function(data, params) { 32 | rows <- params$rows 33 | cols <- params$cols 34 | vars <- c(rows, cols) 35 | 36 | panels <- combine_vars( 37 | data = data, 38 | env = params$plot_env, 39 | vars = vars, 40 | drop = TRUE 41 | ) 42 | panels <- vctrs::vec_sort(panels) 43 | layout <- layout_ragged_rows(panels[names(rows)], params$free, params$align) 44 | 45 | cbind(layout, panels) 46 | }, 47 | 48 | finalise_gtable = function(table, layout, params) { 49 | if (!params$axes$x && !params$free$x) 50 | table <- cull_inner_panel_decorations(table, layout, sides = c("t", "b"), kind = "axis") 51 | 52 | if (!params$axes$y) 53 | table <- cull_inner_panel_decorations(table, layout, sides = c("l", "r"), kind = "axis") 54 | 55 | if (params$strips == "margins") 56 | table <- cull_inner_panel_decorations(table, layout, sides = c("l", "r"), kind = "strip") 57 | 58 | table 59 | } 60 | ) 61 | 62 | layout_ragged_rows <- function(x, free = list(), align = "start") { 63 | layout <- layout_ragged(x, groups = "rows", align = align) 64 | layout$SCALE_X <- if (!isTRUE(free$x)) 1L else layout$PANEL 65 | layout$SCALE_Y <- if (!isTRUE(free$y)) 1L else layout$ROW 66 | layout 67 | } 68 | -------------------------------------------------------------------------------- /R/ggragged-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | #' @import ggplot2 3 | #' @import gtable 4 | "_PACKAGE" 5 | 6 | ## usethis namespace: start 7 | ## usethis namespace: end 8 | NULL 9 | -------------------------------------------------------------------------------- /R/grid.R: -------------------------------------------------------------------------------- 1 | #' @importFrom rlang %||% 2 | grob_shift_viewport <- function(grob, x = NULL, y = NULL) { 3 | vp <- grob$vp %||% return(grob) 4 | if (!is.null(x)) { 5 | vp$x <- vp$x + x 6 | } 7 | if (!is.null(y)) { 8 | vp$y <- vp$y + y 9 | } 10 | grob$vp <- vp 11 | grob 12 | } 13 | -------------------------------------------------------------------------------- /R/gtable.R: -------------------------------------------------------------------------------- 1 | gtable_get_grob <- function(x, name) { 2 | x$grobs[[match(name, x$layout$name)]] 3 | } 4 | 5 | gtable_set_grobs <- function(x, name, grob) { 6 | x$grobs[match(name, x$layout$name)] <- grob 7 | x 8 | } 9 | 10 | gtable_set_height <- function(x, name, height) { 11 | t <- x$layout$t[match(name, x$layout$name)] 12 | x$heights[t] <- height 13 | x 14 | } 15 | 16 | gtable_set_width <- function(x, name, width) { 17 | l <- x$layout$l[match(name, x$layout$name)] 18 | x$widths[l] <- width 19 | x 20 | } 21 | 22 | gtable_get_grob_position <- function(x, name) { 23 | x$layout[match(name, x$layout$name), c("t", "b", "l", "r")] 24 | } 25 | -------------------------------------------------------------------------------- /R/layout.R: -------------------------------------------------------------------------------- 1 | layout_ragged <- function(x, groups, align = "start") { 2 | groups <- rlang::arg_match0(groups, c("rows", "cols")) 3 | align <- rlang::arg_match0(align, c("start", "end")) 4 | 5 | r <- vctrs::vec_group_rle(x) 6 | n <- vctrs::field(r, "length") 7 | 8 | PANEL <- vctrs::vec_seq_along(x) 9 | ROW <- rep.int(seq_along(n), n) 10 | COL <- sequence(n, from = if (align == "end") max(n) - n + 1L else 1L) 11 | 12 | layout <- vctrs::data_frame(PANEL = PANEL, ROW = ROW, COL = COL) 13 | if (groups == "cols") 14 | layout[c("ROW", "COL")] <- layout[c("COL", "ROW")] 15 | 16 | layout 17 | } 18 | 19 | panels_with_neighbour <- function(layout, side) { 20 | neighbour <- switch( 21 | side, 22 | t = list(PANEL = layout$PANEL, ROW = layout$ROW - 1, COL = layout$COL), 23 | b = list(PANEL = layout$PANEL, ROW = layout$ROW + 1, COL = layout$COL), 24 | l = list(PANEL = layout$PANEL, ROW = layout$ROW, COL = layout$COL - 1), 25 | r = list(PANEL = layout$PANEL, ROW = layout$ROW, COL = layout$COL + 1), 26 | stop("internal error: invalid side: ", side) 27 | ) 28 | merge(layout[c("ROW", "COL")], neighbour)$PANEL 29 | } 30 | 31 | inner_margin_panels <- function(layout, side) { 32 | setdiff(margin_panels(layout, side), outermost_panels(layout, side)) 33 | } 34 | 35 | margin_panels <- function(layout, side) { 36 | setdiff(layout$PANEL, panels_with_neighbour(layout, side)) 37 | } 38 | 39 | outermost_panels <- function(layout, side) { 40 | switch( 41 | side, 42 | t = layout$PANEL[layout$ROW == min(layout$ROW)], 43 | b = layout$PANEL[layout$ROW == max(layout$ROW)], 44 | l = layout$PANEL[layout$COL == min(layout$COL)], 45 | r = layout$PANEL[layout$COL == max(layout$COL)], 46 | stop("internal error: invalid side: ", side) 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%", 13 | dev = "ragg_png", 14 | dpi = 144 15 | ) 16 | ``` 17 | 18 | # ggragged 19 | 20 | 21 | [![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) 22 | [![R-CMD-check](https://github.com/mikmart/ggragged/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/mikmart/ggragged/actions/workflows/R-CMD-check.yaml) 23 | [![Codecov test coverage](https://codecov.io/gh/mikmart/ggragged/branch/main/graph/badge.svg)](https://app.codecov.io/gh/mikmart/ggragged?branch=main) 24 | [![CRAN status](https://www.r-pkg.org/badges/version/ggragged)](https://CRAN.R-project.org/package=ggragged) 25 | 26 | 27 | ggragged extends the faceting system in [ggplot2](https://ggplot2.tidyverse.org/) 28 | to ragged grids---a hybrid layout between `facet_wrap()` and `facet_grid()`. 29 | 30 | - `facet_ragged_rows()` groups panels into rows that can vary in length. 31 | - `facet_ragged_cols()` groups panels into columns that can vary in length. 32 | 33 | ## Installation 34 | 35 | Install the current release from [CRAN](https://cran.r-project.org/package=ggragged): 36 | 37 | ``` r 38 | install.packages("ggragged") 39 | ``` 40 | 41 | Or the development version from [GitHub](https://github.com/mikmart/ggragged): 42 | 43 | ``` r 44 | remotes::install_github("mikmart/ggragged") 45 | ``` 46 | 47 | ## Example 48 | 49 | Ragged grids can be used to clearly separate nested hierarchies in the panel layout: 50 | 51 | ```{r example} 52 | library(ggplot2) 53 | library(ggragged) 54 | 55 | p <- ggplot(mpg, aes(displ, cty)) + geom_point() 56 | p + facet_ragged_rows(vars(drv), vars(cyl)) 57 | p + facet_ragged_cols(vars(cyl), vars(drv)) 58 | ``` 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # ggragged 5 | 6 | 7 | 8 | [![Lifecycle: 9 | stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://lifecycle.r-lib.org/articles/stages.html#stable) 10 | [![R-CMD-check](https://github.com/mikmart/ggragged/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/mikmart/ggragged/actions/workflows/R-CMD-check.yaml) 11 | [![Codecov test 12 | coverage](https://codecov.io/gh/mikmart/ggragged/branch/main/graph/badge.svg)](https://app.codecov.io/gh/mikmart/ggragged?branch=main) 13 | [![CRAN 14 | status](https://www.r-pkg.org/badges/version/ggragged)](https://CRAN.R-project.org/package=ggragged) 15 | 16 | 17 | ggragged extends the faceting system in 18 | [ggplot2](https://ggplot2.tidyverse.org/) to ragged grids—a hybrid 19 | layout between `facet_wrap()` and `facet_grid()`. 20 | 21 | - `facet_ragged_rows()` groups panels into rows that can vary in length. 22 | - `facet_ragged_cols()` groups panels into columns that can vary in 23 | length. 24 | 25 | ## Installation 26 | 27 | Install the current release from 28 | [CRAN](https://cran.r-project.org/package=ggragged): 29 | 30 | ``` r 31 | install.packages("ggragged") 32 | ``` 33 | 34 | Or the development version from 35 | [GitHub](https://github.com/mikmart/ggragged): 36 | 37 | ``` r 38 | remotes::install_github("mikmart/ggragged") 39 | ``` 40 | 41 | ## Example 42 | 43 | Ragged grids can be used to clearly separate nested hierarchies in the 44 | panel layout: 45 | 46 | ``` r 47 | library(ggplot2) 48 | library(ggragged) 49 | 50 | p <- ggplot(mpg, aes(displ, cty)) + geom_point() 51 | p + facet_ragged_rows(vars(drv), vars(cyl)) 52 | ``` 53 | 54 | 55 | 56 | ``` r 57 | p + facet_ragged_cols(vars(cyl), vars(drv)) 58 | ``` 59 | 60 | 61 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://mikmart.github.io/ggragged/ 2 | template: 3 | bootstrap: 5 4 | 5 | news: 6 | releases: 7 | - text: ggragged 0.2.0 8 | href: https://www.mikkomarttila.com/2024/10/11/ggragged-0-2-0/ 9 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## R CMD check results 2 | 3 | 0 errors | 0 warnings | 0 notes 4 | -------------------------------------------------------------------------------- /ggragged.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | LineEndingConversion: Posix 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /man/facet_ragged.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/facet_ragged.R, R/facet_ragged_rows.R, 3 | % R/facet_ragged_cols.R 4 | \name{facet_ragged} 5 | \alias{facet_ragged} 6 | \alias{facet_ragged_rows} 7 | \alias{facet_ragged_cols} 8 | \title{Lay out panels in a ragged grid} 9 | \usage{ 10 | facet_ragged_rows( 11 | rows, 12 | cols, 13 | ..., 14 | scales = "fixed", 15 | switch = "none", 16 | strips = "margins", 17 | axes = "margins", 18 | align = "start", 19 | labeller = "label_value" 20 | ) 21 | 22 | facet_ragged_cols( 23 | rows, 24 | cols, 25 | ..., 26 | scales = "fixed", 27 | switch = "none", 28 | strips = "margins", 29 | axes = "margins", 30 | align = "start", 31 | labeller = "label_value" 32 | ) 33 | } 34 | \arguments{ 35 | \item{rows, cols}{A set of variables or expressions quoted by \code{\link[ggplot2:vars]{ggplot2::vars()}}, 36 | the combinations of which define the panels in the layout.} 37 | 38 | \item{...}{Arguments reserved for future use.} 39 | 40 | \item{scales}{Determines which panels share axis ranges. By default (\code{"fixed"}), 41 | all panels share the same scales. Use \code{"free_x"} to let x-axes vary, use 42 | \code{"free_y"} to let y-axes vary, or \code{"free"} to let both axes vary. Panels 43 | within groups always share the scale along the grouping dimension.} 44 | 45 | \item{switch}{Determines how facet label strips are positioned. By default 46 | (\code{"none"}), strips are drawn to the top and right of the panels. Use \code{"x"} 47 | to switch the top strip to the bottom, use \code{"y"} to switch the right strip 48 | to the left, or \code{"both"} to do both.} 49 | 50 | \item{strips}{Determines which facet label strips are drawn. By default 51 | (\code{"margins"}), strips between panels along the grouping dimension will be 52 | suppressed. Use \code{"all"} to always draw both strips.} 53 | 54 | \item{axes}{Determines which axes are drawn. By default (\code{"margins"}), axes 55 | between panels will be suppressed if they are fixed. Use \code{"all_x"} to 56 | always draw x-axes, \code{"all_y"} to always draw y-axes, or \code{"all"} to always 57 | draw both axes.} 58 | 59 | \item{align}{Determines how panels are positioned within groups. By default 60 | (\code{"start"}), panels in groups are densely packed from the start. Use 61 | \code{"end"} to instead pack panels to the end of the group.} 62 | 63 | \item{labeller}{A function that takes one data frame of labels and 64 | returns a list or data frame of character vectors. Each input 65 | column corresponds to one factor. Thus there will be more than 66 | one with \code{vars(cyl, am)}. Each output 67 | column gets displayed as one separate line in the strip 68 | label. This function should inherit from the "labeller" S3 class 69 | for compatibility with \code{\link[ggplot2:labeller]{labeller()}}. You can use different labeling 70 | functions for different kind of labels, for example use \code{\link[ggplot2:label_parsed]{label_parsed()}} for 71 | formatting facet labels. \code{\link[ggplot2:label_value]{label_value()}} is used by default, 72 | check it for more details and pointers to other options.} 73 | } 74 | \value{ 75 | A \code{Facet} that can be added to a \code{ggplot}. 76 | } 77 | \description{ 78 | These facets create layouts in-between \code{\link[ggplot2:facet_wrap]{ggplot2::facet_wrap()}} and 79 | \code{\link[ggplot2:facet_grid]{ggplot2::facet_grid()}}. Panels are arranged into groups stacked along the 80 | defining dimension, but remain independent in the other dimension, allowing 81 | for a grid with ragged edges. This can be useful, for example, to represent 82 | nested or partially crossed relationships between faceting variables. 83 | } 84 | \examples{ 85 | p <- ggplot(mpg, aes(displ, cty)) + geom_point() 86 | p + facet_ragged_rows(vars(drv), vars(cyl)) 87 | p + facet_ragged_cols(vars(cyl), vars(drv)) 88 | \donttest{ 89 | # Allow axes to vary between panels 90 | p + facet_ragged_rows(vars(drv), vars(cyl), scales = "free_y") 91 | p + facet_ragged_rows(vars(drv), vars(cyl), scales = "free") 92 | 93 | # Change strip label positions 94 | p + facet_ragged_rows(vars(drv), vars(cyl), switch = "y") 95 | p + facet_ragged_rows(vars(drv), vars(cyl), switch = "both") 96 | 97 | # Draw strips between panels 98 | p + facet_ragged_rows(vars(drv), vars(cyl), strips = "all") 99 | 100 | # Draw axes between panels 101 | p + facet_ragged_rows(vars(drv), vars(cyl), axes = "all_x") 102 | p + facet_ragged_rows(vars(drv), vars(cyl), axes = "all") 103 | } 104 | # Change panel alignment 105 | p + facet_ragged_rows(vars(drv), vars(cyl), align = "end") 106 | } 107 | -------------------------------------------------------------------------------- /man/figures/README-example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikmart/ggragged/cbffef3c440e47327adc4d1860ad24801084a816/man/figures/README-example-1.png -------------------------------------------------------------------------------- /man/figures/README-example-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikmart/ggragged/cbffef3c440e47327adc4d1860ad24801084a816/man/figures/README-example-2.png -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikmart/ggragged/cbffef3c440e47327adc4d1860ad24801084a816/man/figures/logo.png -------------------------------------------------------------------------------- /man/ggragged-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ggragged-package.R 3 | \docType{package} 4 | \name{ggragged-package} 5 | \alias{ggragged} 6 | \alias{ggragged-package} 7 | \title{ggragged: Ragged Grids for 'ggplot2'} 8 | \description{ 9 | \if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}} 10 | 11 | Extend 'ggplot2' facets to panel layouts arranged in a grid with ragged edges. facet_ragged_rows() groups panels into rows that can vary in length, facet_ragged_cols() does the same but for columns. These can be useful, for example, to represent nested or partially crossed relationships between faceting variables. 12 | } 13 | \seealso{ 14 | Useful links: 15 | \itemize{ 16 | \item \url{https://github.com/mikmart/ggragged} 17 | \item \url{https://mikmart.github.io/ggragged/} 18 | \item Report bugs at \url{https://github.com/mikmart/ggragged/issues} 19 | } 20 | 21 | } 22 | \author{ 23 | \strong{Maintainer}: Mikko Marttila \email{mikkmart@protonmail.com} [copyright holder] 24 | 25 | } 26 | \keyword{internal} 27 | -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikmart/ggragged/cbffef3c440e47327adc4d1860ad24801084a816/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikmart/ggragged/cbffef3c440e47327adc4d1860ad24801084a816/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikmart/ggragged/cbffef3c440e47327adc4d1860ad24801084a816/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikmart/ggragged/cbffef3c440e47327adc4d1860ad24801084a816/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikmart/ggragged/cbffef3c440e47327adc4d1860ad24801084a816/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikmart/ggragged/cbffef3c440e47327adc4d1860ad24801084a816/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikmart/ggragged/cbffef3c440e47327adc4d1860ad24801084a816/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikmart/ggragged/cbffef3c440e47327adc4d1860ad24801084a816/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikmart/ggragged/cbffef3c440e47327adc4d1860ad24801084a816/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # This file is part of the standard setup for testthat. 2 | # It is recommended that you do not modify it. 3 | # 4 | # Where should you do additional test configuration? 5 | # Learn more about the roles of various files in: 6 | # * https://r-pkgs.org/tests.html 7 | # * https://testthat.r-lib.org/reference/test_package.html#special-files 8 | 9 | library(testthat) 10 | library(ggragged) 11 | 12 | test_check("ggragged") 13 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/facet_ragged_cols/cols-aligned-to-end-in-an-inverted-l-shape.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 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 1.0 101 | 1.5 102 | 2.0 103 | 2.5 104 | 3.0 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 | 1.0 141 | 1.5 142 | 2.0 143 | 2.5 144 | 3.0 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 1.0 163 | 1.5 164 | 2.0 165 | 2.5 166 | 3.0 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 1.0 194 | 1.5 195 | 2.0 196 | 2.5 197 | 3.0 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | A 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | B 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 1 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 2 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 3 380 | 381 | 382 | 383 | 384 | x 385 | y 386 | cols aligned to end in an inverted l shape 387 | 388 | 389 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/facet_ragged_cols/x-axes-have-varying-ranges-when-free.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 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 2.950 100 | 2.975 101 | 3.000 102 | 3.025 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 | 1.00 139 | 1.25 140 | 1.50 141 | 1.75 142 | 2.00 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 1.0 161 | 1.5 162 | 2.0 163 | 2.5 164 | 3.0 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 1.0 201 | 1.5 202 | 2.0 203 | 2.5 204 | 3.0 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | A 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | B 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 1 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 2 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 3 378 | 379 | 380 | 381 | 382 | y 383 | x 384 | x axes have varying ranges when free 385 | 386 | 387 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/facet_ragged_cols/x-axes-on-both-sides-default.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 | 1.0 74 | 1.5 75 | 2.0 76 | 2.5 77 | 3.0 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 1.0 105 | 1.5 106 | 2.0 107 | 2.5 108 | 3.0 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 | 1.0 145 | 1.5 146 | 2.0 147 | 2.5 148 | 3.0 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 | 180 | 181 | 182 | 183 | 184 | 1.0 185 | 1.5 186 | 2.0 187 | 2.5 188 | 3.0 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 1.0 207 | 1.5 208 | 2.0 209 | 2.5 210 | 3.0 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 1.0 247 | 1.5 248 | 2.0 249 | 2.5 250 | 3.0 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | A 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | B 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 1 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 2 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 3 424 | 425 | 426 | 427 | 428 | x 429 | x 430 | y 431 | x axes on both sides, default 432 | 433 | 434 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/facet_ragged_cols/x-axes-on-both-sides-switched.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 | 1.0 74 | 1.5 75 | 2.0 76 | 2.5 77 | 3.0 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 1.0 105 | 1.5 106 | 2.0 107 | 2.5 108 | 3.0 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 | 1.0 149 | 1.5 150 | 2.0 151 | 2.5 152 | 3.0 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 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 1.0 189 | 1.5 190 | 2.0 191 | 2.5 192 | 3.0 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 1.0 211 | 1.5 212 | 2.0 213 | 2.5 214 | 3.0 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 1.0 251 | 1.5 252 | 2.0 253 | 2.5 254 | 3.0 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | A 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | B 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 1 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 2 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 3 428 | 429 | 430 | 431 | 432 | x 433 | x 434 | y 435 | x axes on both sides, switched 436 | 437 | 438 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/facet_ragged_rows/rows-aligned-to-end-in-an-inverted-l-shape.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 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 1.0 110 | 1.5 111 | 2.0 112 | 2.5 113 | 3.0 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 | 1.0 141 | 1.5 142 | 2.0 143 | 2.5 144 | 3.0 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 1.0 163 | 1.5 164 | 2.0 165 | 2.5 166 | 3.0 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 1.0 194 | 1.5 195 | 2.0 196 | 2.5 197 | 3.0 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 1 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 2 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 3 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | A 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | B 385 | 386 | 387 | 388 | 389 | x 390 | y 391 | rows aligned to end in an inverted l shape 392 | 393 | 394 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/facet_ragged_rows/y-axes-on-both-sides-default.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 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 1.0 110 | 1.5 111 | 2.0 112 | 2.5 113 | 3.0 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 | 1.0 141 | 1.5 142 | 2.0 143 | 2.5 144 | 3.0 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 1.0 163 | 1.5 164 | 2.0 165 | 2.5 166 | 3.0 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 1.0 194 | 1.5 195 | 2.0 196 | 2.5 197 | 3.0 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 1.0 247 | 1.5 248 | 2.0 249 | 2.5 250 | 3.0 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 1.0 287 | 1.5 288 | 2.0 289 | 2.5 290 | 3.0 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 1 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 2 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 3 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | A 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | B 433 | 434 | 435 | 436 | 437 | x 438 | y 439 | y 440 | y axes on both sides, default 441 | 442 | 443 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/facet_ragged_rows/y-axes-on-both-sides-switched.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 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 1.0 110 | 1.5 111 | 2.0 112 | 2.5 113 | 3.0 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 | 1.0 141 | 1.5 142 | 2.0 143 | 2.5 144 | 3.0 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 1.0 163 | 1.5 164 | 2.0 165 | 2.5 166 | 3.0 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 1.0 194 | 1.5 195 | 2.0 196 | 2.5 197 | 3.0 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 1.0 243 | 1.5 244 | 2.0 245 | 2.5 246 | 3.0 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 1.0 283 | 1.5 284 | 2.0 285 | 2.5 286 | 3.0 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 1 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 2 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 3 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | A 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | B 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | x 434 | y 435 | y 436 | y axes on both sides, switched 437 | 438 | 439 | -------------------------------------------------------------------------------- /tests/testthat/test-facet_ragged_cols.R: -------------------------------------------------------------------------------- 1 | tbl <- data.frame(foo = c("A", "B", "B"), bar = 1:3, x = 1:3, y = 3:1) 2 | 3 | test_that("axes and strips don't overlap", { 4 | p <- ggplot(tbl, aes(x, y)) + scale_x_continuous(sec.axis = dup_axis()) 5 | 6 | p <- p + facet_ragged_cols(vars(bar), vars(foo)) 7 | vdiffr::expect_doppelganger("x axes on both sides, default", p) 8 | 9 | p <- p + facet_ragged_cols(vars(bar), vars(foo), switch = "x") 10 | vdiffr::expect_doppelganger("x axes on both sides, switched", p) 11 | }) 12 | 13 | test_that("alignment to end works", { 14 | p <- ggplot(tbl, aes(x, y)) 15 | p <- p + facet_ragged_cols(vars(bar), vars(foo), align = "end") 16 | vdiffr::expect_doppelganger("cols aligned to end in an inverted l shape", p) 17 | }) 18 | 19 | test_that("correct axes are rendered with free and coord_flip (#2)", { 20 | p <- ggplot(tbl, aes(x, y)) + coord_flip() 21 | p <- p + facet_ragged_cols(vars(bar), vars(foo), scales = "free_x") 22 | vdiffr::expect_doppelganger("x axes have varying ranges when free", p) 23 | }) 24 | -------------------------------------------------------------------------------- /tests/testthat/test-facet_ragged_rows.R: -------------------------------------------------------------------------------- 1 | tbl <- data.frame(foo = c("A", "B", "B"), bar = 1:3, x = 1:3, y = 3:1) 2 | 3 | test_that("axes and strips don't overlap", { 4 | p <- ggplot(tbl, aes(x, y)) + scale_y_continuous(sec.axis = dup_axis()) 5 | 6 | p <- p + facet_ragged_rows(vars(foo), vars(bar)) 7 | vdiffr::expect_doppelganger("y axes on both sides, default", p) 8 | 9 | p <- p + facet_ragged_rows(vars(foo), vars(bar), switch = "y") 10 | vdiffr::expect_doppelganger("y axes on both sides, switched", p) 11 | }) 12 | 13 | test_that("alignment to end works", { 14 | p <- ggplot(tbl, aes(x, y)) 15 | p <- p + facet_ragged_rows(vars(foo), vars(bar), align = "end") 16 | vdiffr::expect_doppelganger("rows aligned to end in an inverted l shape", p) 17 | }) 18 | -------------------------------------------------------------------------------- /tests/testthat/test-layout.R: -------------------------------------------------------------------------------- 1 | test_that("panels are positioned correctly", { 2 | ragged_rows <- layout_ragged_rows(c("A", "B", "B")) 3 | expect_equal(ragged_rows$ROW, c(1, 2, 2)) 4 | expect_equal(ragged_rows$COL, c(1, 1, 2)) 5 | 6 | ragged_cols <- layout_ragged_cols(c("A", "B", "B")) 7 | expect_equal(ragged_cols$ROW, c(1, 1, 2)) 8 | expect_equal(ragged_cols$COL, c(1, 2, 2)) 9 | }) 10 | 11 | test_that("free scales work", { 12 | rows_free_x <- layout_ragged_rows(c("A", "B", "B"), free = list(x = TRUE)) 13 | expect_equal(rows_free_x$SCALE_X, c(1, 2, 3)) 14 | expect_equal(rows_free_x$SCALE_Y, c(1, 1, 1)) 15 | 16 | rows_free_y <- layout_ragged_rows(c("A", "B", "B"), free = list(y = TRUE)) 17 | expect_equal(rows_free_y$SCALE_X, c(1, 1, 1)) 18 | expect_equal(rows_free_y$SCALE_Y, c(1, 2, 2)) 19 | 20 | cols_free_x <- layout_ragged_cols(c("A", "B", "B"), free = list(x = TRUE)) 21 | expect_equal(cols_free_x$SCALE_X, c(1, 2, 2)) 22 | expect_equal(cols_free_x$SCALE_Y, c(1, 1, 1)) 23 | 24 | cols_free_y <- layout_ragged_cols(c("A", "B", "B"), free = list(y = TRUE)) 25 | expect_equal(cols_free_y$SCALE_X, c(1, 1, 1)) 26 | expect_equal(cols_free_y$SCALE_Y, c(1, 2, 3)) 27 | }) 28 | 29 | test_that("panels can be aligned to the end", { 30 | expect_equal(layout_ragged_rows(c("A", "B", "B"), align = "start")$COL, c(1, 1, 2)) 31 | expect_equal(layout_ragged_cols(c("A", "B", "B"), align = "start")$ROW, c(1, 1, 2)) 32 | expect_equal(layout_ragged_rows(c("A", "B", "B"), align = "end")$COL, c(2, 1, 2)) 33 | expect_equal(layout_ragged_cols(c("A", "B", "B"), align = "end")$ROW, c(2, 1, 2)) 34 | }) 35 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/ggragged.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Ragged grids for ggplot2" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{Ragged grids for ggplot2} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | comment = "#>", 14 | fig.dpi = 96, 15 | fig.width = 7.0, 16 | fig.height = 4.5 17 | ) 18 | ``` 19 | 20 | ## Introduction 21 | 22 | ggragged extends the faceting system in [ggplot2](https://cran.r-project.org/package=ggplot2) 23 | to ragged grid layouts. In a ragged grid, panels are grouped into either rows or columns. 24 | Groups can have varying lengths, and panel placement within them is independent of other 25 | groups. 26 | 27 | Here we showcase some examples of applying ragged grid layouts. 28 | 29 | ```{r setup} 30 | library(ggragged) 31 | ``` 32 | 33 | ## Example: Imbalanced rows 34 | 35 | The motivation for creating ggragged came in the context of a phase I clinical 36 | trial---a dose-escalation study that enrolled cohorts of different sizes. 37 | For visual review of individual subject data, it would have been very useful to have 38 | cohorts clearly grouped together. The situation called for panels laid out in rows of 39 | uneven length. 40 | 41 | Let's demonstrate with some publicly available data. 42 | 43 | There are a number of pharmacokinetic datasets distributed with R. None of them quite 44 | match the structure for this example, so we first prepare a dataset with 45 | the required features. Using the built-in `Theoph` dataset as a starting point, 46 | we assign subjects into 3 imbalanced cohorts and simulate doubling doses between them: 47 | 48 | ```{r sad-data} 49 | data(Theoph) 50 | 51 | Theoph2 <- transform(Theoph, Cohort = (as.numeric(Subject) - 1) %% 3 + 1) 52 | Theoph2 <- transform(Theoph2, Subject = (as.numeric(Subject) - 1) %/% 3 + 1) 53 | Theoph2 <- transform(Theoph2, Subject = sprintf("%d0%d", Cohort, Subject)) 54 | Theoph2 <- transform(Theoph2, conc = conc * 2^(Cohort - 1)) 55 | Theoph2 <- subset(Theoph2, Subject != "204") 56 | 57 | with(Theoph2, table(Cohort, Subject)) 58 | ``` 59 | 60 | We are interested in visualizing drug concentrations over time after a single 61 | dose for each subject. The standard tool for that is `facet_wrap()`. However, 62 | due to the imbalanced group sizes, the result here does not do a great job at 63 | communicating the cohort structure in the data: 64 | 65 | ```{r sad-wrap} 66 | p <- ggplot(Theoph2, aes(Time, conc)) + geom_line() 67 | p + facet_wrap(vars(Subject, Cohort), labeller = label_both) 68 | ``` 69 | 70 | With `facet_ragged_rows()`, we can have each cohort distinctively laid out 71 | on a row of its own: 72 | 73 | ```{r sad-ragged} 74 | p + facet_ragged_rows(vars(Cohort), vars(Subject), labeller = label_both) 75 | ``` 76 | 77 | This plot also clearly shows the general escalation of concentration levels between 78 | cohorts, as all panels share the same y-axis range. If we instead wanted to focus 79 | on variation between subjects within cohorts, for example for data review purposes, 80 | we can set `scales = "free_y"` to let y-axis ranges vary between rows: 81 | 82 | ```{r sad-ragged-free} 83 | p + facet_ragged_rows(vars(Cohort), vars(Subject), labeller = label_both, scales = "free_y") 84 | ``` 85 | 86 | This effect would not be possible with `facet_wrap()`, where we could only have 87 | axis ranges vary between all panels, or none at all. 88 | 89 | ## Example: Balanced columns 90 | 91 | Ragged grid layouts can be useful in a balanced group setting, too. They can still 92 | clarify the nesting hierarchy between faceting variables in the panel layout. 93 | Let's look at an example. 94 | 95 | The `Gun` dataset in package [nlme](https://cran.r-project.org/package=nlme) 96 | records the results of an experiment on methods for firing naval guns. 97 | The experiment measured the number of rounds fired per minute. 98 | 9 teams of gunners of 3 physiques (3 teams each) each tested the 2 methods being compared: 99 | 100 | ```{r gun-data} 101 | data(Gun, package = "nlme") 102 | with(Gun, table(Method, Team, Physique)) 103 | ``` 104 | 105 | When visualizing these data, we could facet the plot by team to focus on the 106 | informative within-team differences in each panel. To also communicate 107 | the selection of teams by physique, we could show these data in a 3x3 108 | grid with `facet_grid()`. For that we need a helper variable, 109 | a team identifier nested within physique: 110 | 111 | ```{r gun-grid} 112 | p <- ggplot(Gun, aes(Method, rounds)) + geom_point() 113 | p + facet_grid(vars(Team = substr(Team, 1, 2)), vars(Physique), labeller = label_both) 114 | ``` 115 | 116 | There are some issues with this presentation. 117 | The layout does not clearly communicate the fact that there were 9 separate teams. 118 | It also visually invites you to compare individual teams that happen to be grouped on 119 | the same row across physiques, which is not a particularly interesting comparison. 120 | `facet_ragged_cols()` can help: 121 | 122 | ```{r gun-ragged} 123 | p + facet_ragged_cols(vars(Team), vars(Physique), labeller = label_both) 124 | ``` 125 | 126 | Now each panel is labelled with the original team identifier, the labels clearly 127 | pointing out the 9 teams. The additional strips break up the rows of panels, 128 | visually signaling that comparisons across them are not particularly relevant. 129 | We can also see that the order of the teams (which in the data is not based on 130 | the alphanumeric team identifier, but rather on the rate of rounds fired) 131 | is preserved within physiques. 132 | --------------------------------------------------------------------------------