From 3e97bd9aefb329ebc00e7d3deb823f4426136411 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Wed, 20 Sep 2023 15:55:52 +0200 Subject: [PATCH 1/8] coords append information to layout --- R/coord-.R | 2 ++ R/coord-flip.R | 1 + 2 files changed, 3 insertions(+) diff --git a/R/coord-.R b/R/coord-.R index 8944c812ee..154e8331f1 100644 --- a/R/coord-.R +++ b/R/coord-.R @@ -191,6 +191,8 @@ Coord <- ggproto("Coord", }, setup_layout = function(layout, params) { + scales <- layout[c("SCALE_X", "SCALE_Y")] + layout$COORD <- vec_match(scales, unique0(scales)) layout }, diff --git a/R/coord-flip.R b/R/coord-flip.R index 1f3848fb8a..cef43b3ee1 100644 --- a/R/coord-flip.R +++ b/R/coord-flip.R @@ -86,6 +86,7 @@ CoordFlip <- ggproto("CoordFlip", CoordCartesian, }, setup_layout = function(layout, params) { + layout <- Coord$setup_layout(layout, params) # Switch the scales layout[c("SCALE_X", "SCALE_Y")] <- layout[c("SCALE_Y", "SCALE_X")] layout From ec21dc3667e25d06597f0ac295248686d903a49e Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:45:51 +0200 Subject: [PATCH 2/8] compute panel params/guides once per scale combination --- R/layout.R | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/R/layout.R b/R/layout.R index 6e2124e8be..8f4d75537c 100644 --- a/R/layout.R +++ b/R/layout.R @@ -209,20 +209,27 @@ Layout <- ggproto("Layout", NULL, # scales is not elegant, but it is pragmatic self$coord$modify_scales(self$panel_scales_x, self$panel_scales_y) - scales_x <- self$panel_scales_x[self$layout$SCALE_X] - scales_y <- self$panel_scales_y[self$layout$SCALE_Y] + index <- vec_unique_loc(self$layout$COORD) + order <- vec_match(self$layout$COORD, self$layout$COORD[index]) + + scales_x <- self$panel_scales_x[self$layout$SCALE_X[index]] + scales_y <- self$panel_scales_y[self$layout$SCALE_Y[index]] setup_panel_params <- function(scale_x, scale_y) { self$coord$setup_panel_params(scale_x, scale_y, params = self$coord_params) } - self$panel_params <- Map(setup_panel_params, scales_x, scales_y) + self$panel_params <- Map(setup_panel_params, scales_x, scales_y)[order] invisible() }, setup_panel_guides = function(self, guides, layers) { + + index <- vec_unique_loc(self$layout$COORD) + order <- vec_match(self$layout$COORD, self$layout$COORD[index]) + self$panel_params <- lapply( - self$panel_params, + self$panel_params[index], self$coord$setup_panel_guides, guides, self$coord_params @@ -233,7 +240,7 @@ Layout <- ggproto("Layout", NULL, self$coord$train_panel_guides, layers, self$coord_params - ) + )[order] invisible() }, From b0dec256cc688403b73e954884f717417cc8ba76 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Thu, 21 Sep 2023 09:26:20 +0200 Subject: [PATCH 3/8] Facets render axes once per x/y combination --- R/facet-grid-.R | 15 ++++++++++++++- R/facet-wrap.R | 10 +++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/R/facet-grid-.R b/R/facet-grid-.R index 3fa73f98f3..09ad3a815b 100644 --- a/R/facet-grid-.R +++ b/R/facet-grid-.R @@ -306,9 +306,22 @@ FacetGrid <- ggproto("FacetGrid", Facet, cli::cli_abort("{.fn {snake_class(coord)}} doesn't support free scales") } + # Because within a row or column the scales are static, we only need to + # render one set of axes per row/column. If scales are fixed, we only need + # to render one set of axes, that we can repeat for other panels. cols <- which(layout$ROW == 1) rows <- which(layout$COL == 1) - axes <- render_axes(ranges[cols], ranges[rows], coord, theme, transpose = TRUE) + # Figure out unique x/y scale combinations + col_idx <- cols[!duplicated(layout$COORD[cols])] + row_idx <- rows[!duplicated(layout$COORD[rows])] + # Map all combinations to unique ones + col_ord <- vec_match(layout$COORD[cols], layout$COORD[col_idx]) + row_ord <- vec_match(layout$COORD[rows], layout$COORD[row_idx]) + # Render the axes for unique combinations + axes <- render_axes(ranges[col_idx], ranges[row_idx], coord, theme, transpose = TRUE) + # Repeat axes for all combinations + axes$x <- lapply(axes$x, `[`, i = col_ord) + axes$y <- lapply(axes$y, `[`, i = row_ord) col_vars <- unique0(layout[names(params$cols)]) row_vars <- unique0(layout[names(params$rows)]) diff --git a/R/facet-wrap.R b/R/facet-wrap.R index b7c19a05f3..38f6b4ffcb 100644 --- a/R/facet-wrap.R +++ b/R/facet-wrap.R @@ -241,7 +241,15 @@ FacetWrap <- ggproto("FacetWrap", Facet, panels <- panels[panel_order] panel_pos <- convertInd(layout$ROW, layout$COL, nrow) - axes <- render_axes(ranges, ranges, coord, theme, transpose = TRUE) + # If scales are fixed, we only need to render one set of axes which can be + # repeated. Below, we find the unique combinations of scales, and map all + # combinations to the unique ones. + index <- vec_unique_loc(layout$COORD) + order <- vec_match(layout$COORD, layout$COORD[index]) + + # Render axes and repeat them for all combinations + axes <- render_axes(ranges[index], ranges[index], coord, theme, transpose = TRUE) + axes <- lapply(axes, lapply, function(x) x[order]) if (length(params$facets) == 0) { # Add a dummy label From b819fa90c3dcf09fc21ba9951ed9c306176f7cf1 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Thu, 21 Sep 2023 09:35:31 +0200 Subject: [PATCH 4/8] anonymous function to `MoreArgs` --- R/layout.R | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/R/layout.R b/R/layout.R index 8f4d75537c..245630653f 100644 --- a/R/layout.R +++ b/R/layout.R @@ -215,10 +215,11 @@ Layout <- ggproto("Layout", NULL, scales_x <- self$panel_scales_x[self$layout$SCALE_X[index]] scales_y <- self$panel_scales_y[self$layout$SCALE_Y[index]] - setup_panel_params <- function(scale_x, scale_y) { - self$coord$setup_panel_params(scale_x, scale_y, params = self$coord_params) - } - self$panel_params <- Map(setup_panel_params, scales_x, scales_y)[order] + self$panel_params <- Map( + self$coord$setup_panel_params, + scales_x, scales_y, + MoreArgs = list(params = self$coord_params) + )[order] invisible() }, From c33512fce69edcf6781dcd67cea00eebea506795 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Thu, 21 Sep 2023 09:49:25 +0200 Subject: [PATCH 5/8] Sprinkle some comments --- R/coord-.R | 3 +++ R/layout.R | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/R/coord-.R b/R/coord-.R index 154e8331f1..d83b501cb5 100644 --- a/R/coord-.R +++ b/R/coord-.R @@ -191,6 +191,9 @@ Coord <- ggproto("Coord", }, setup_layout = function(layout, params) { + # We're appending a COORD variable to the layout that determines the + # uniqueness of panel parameters. The layout uses this to prevent redundant + # setups of these parameters. scales <- layout[c("SCALE_X", "SCALE_Y")] layout$COORD <- vec_match(scales, unique0(scales)) layout diff --git a/R/layout.R b/R/layout.R index 245630653f..8661d60e67 100644 --- a/R/layout.R +++ b/R/layout.R @@ -209,6 +209,8 @@ Layout <- ggproto("Layout", NULL, # scales is not elegant, but it is pragmatic self$coord$modify_scales(self$panel_scales_x, self$panel_scales_y) + # We only need to setup panel params once for unique combinations of x/y + # scales. These will be repeated for duplicated combinations. index <- vec_unique_loc(self$layout$COORD) order <- vec_match(self$layout$COORD, self$layout$COORD[index]) @@ -219,13 +221,15 @@ Layout <- ggproto("Layout", NULL, self$coord$setup_panel_params, scales_x, scales_y, MoreArgs = list(params = self$coord_params) - )[order] + )[order] # `[order]` does the repeating invisible() }, setup_panel_guides = function(self, guides, layers) { + # Like in `setup_panel_params`, we only need to setup guides for unique + # combinations of x/y scales. index <- vec_unique_loc(self$layout$COORD) order <- vec_match(self$layout$COORD, self$layout$COORD[index]) From 28b334179508a5505dbebfec4880845499f73fce Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Thu, 21 Sep 2023 09:52:09 +0200 Subject: [PATCH 6/8] Add news bullet --- NEWS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS.md b/NEWS.md index 5b4d074c57..ee5a22ebef 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ # ggplot2 (development version) +* (internal) The plot's layout now has a coord parameters that is used to + prevent setting up identical panel parameters (#5427) + * `ScaleContinuous$get_breaks()` now only calls `scales::zero_range()` on limits in transformed space, rather than in data space (#5304). From 88b61562206da1a5b49fcae102b5e51100c80731 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Thu, 21 Sep 2023 09:56:10 +0200 Subject: [PATCH 7/8] add test --- tests/testthat/test-coord-.R | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/testthat/test-coord-.R b/tests/testthat/test-coord-.R index ca7f62c06e..b372478981 100644 --- a/tests/testthat/test-coord-.R +++ b/tests/testthat/test-coord-.R @@ -53,3 +53,26 @@ test_that("check coord limits errors only on bad inputs", { # Should raise error if vector of wrong length is passed expect_error(check_coord_limits(1:3)) }) + +test_that("coords append a column to the layout correctly", { + layout <- data_frame0(SCALE_X = c(1, 1, 1), SCALE_Y = c(1, 1, 1)) + test <- Coord$setup_layout(layout) + expect_equal(test$COORD, c(1, 1, 1)) + + layout <- data_frame0(SCALE_X = c(1, 1, 1), SCALE_Y = c(1, 2, 2)) + test <- Coord$setup_layout(layout) + expect_equal(test$COORD, c(1, 2, 2)) + + layout <- data_frame0(SCALE_X = c(1, 2, 3), SCALE_Y = c(1, 1, 1)) + test <- Coord$setup_layout(layout) + expect_equal(test$COORD, c(1, 2, 3)) + + layout <- data_frame0(SCALE_X = c(1, 2, 3), SCALE_Y = c(1, 2, 3)) + test <- Coord$setup_layout(layout) + expect_equal(test$COORD, c(1, 2, 3)) + + layout <- data_frame0(SCALE_X = c(1, 1, 1), SCALE_Y = c(1, 2, 1)) + test <- Coord$setup_layout(layout) + expect_equal(test$COORD, c(1, 2, 1)) +}) + From 41cfd5fe12373542aff134b6009144d9084a7a77 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Mon, 20 May 2024 14:08:34 +0200 Subject: [PATCH 8/8] format bullet --- NEWS.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index bb9ed2ee44..01d225cf97 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,9 +2,6 @@ * (internal) The plot's layout now has a coord parameters that is used to prevent setting up identical panel parameters (#5427) - -* Legend titles no longer take up space if they've been removed by setting - `legend.title = element_blank()` (@teunbrand, #3587). * Discrete position scales now expose the `palette` argument, which can be used to customise spacings between levels (@teunbrand, #5770). * The default `se` parameter in layers with `geom = "smooth"` will be `TRUE`