├── .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 | [](https://lifecycle.r-lib.org/articles/stages.html#stable)
22 | [](https://github.com/mikmart/ggragged/actions/workflows/R-CMD-check.yaml)
23 | [](https://app.codecov.io/gh/mikmart/ggragged?branch=main)
24 | [](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 | [](https://lifecycle.r-lib.org/articles/stages.html#stable)
10 | [](https://github.com/mikmart/ggragged/actions/workflows/R-CMD-check.yaml)
11 | [](https://app.codecov.io/gh/mikmart/ggragged?branch=main)
13 | [](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 |
389 |
--------------------------------------------------------------------------------
/tests/testthat/_snaps/facet_ragged_cols/x-axes-have-varying-ranges-when-free.svg:
--------------------------------------------------------------------------------
1 |
2 |
387 |
--------------------------------------------------------------------------------
/tests/testthat/_snaps/facet_ragged_cols/x-axes-on-both-sides-default.svg:
--------------------------------------------------------------------------------
1 |
2 |
434 |
--------------------------------------------------------------------------------
/tests/testthat/_snaps/facet_ragged_cols/x-axes-on-both-sides-switched.svg:
--------------------------------------------------------------------------------
1 |
2 |
438 |
--------------------------------------------------------------------------------
/tests/testthat/_snaps/facet_ragged_rows/rows-aligned-to-end-in-an-inverted-l-shape.svg:
--------------------------------------------------------------------------------
1 |
2 |
394 |
--------------------------------------------------------------------------------
/tests/testthat/_snaps/facet_ragged_rows/y-axes-on-both-sides-default.svg:
--------------------------------------------------------------------------------
1 |
2 |
443 |
--------------------------------------------------------------------------------
/tests/testthat/_snaps/facet_ragged_rows/y-axes-on-both-sides-switched.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
--------------------------------------------------------------------------------