Skip to content

Commit

Permalink
🐛 Deal with empty radial axes (#6272)
Browse files Browse the repository at this point in the history
* Avoid assigning NULL to list, thereby deleting the element

* protect theta guide better against empty keys

* add test

* add news bullet

* Fix partial match

* fix another partial match
  • Loading branch information
teunbrand authored Jan 28, 2025
1 parent 12d1c98 commit 09776db
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 30 deletions.
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# ggplot2 (development version)

* `coord_radial()` now displays no axis instead of throwing an error when
a scale has no breaks (@teunbrand, #6271).
* The `fatten` argument has been deprecated in `geom_boxplot()`,
`geom_crossbar()` and `geom_pointrange()` (@teunbrand, #4881).
* Axis labels are now preserved better when using `coord_sf(expand = TRUE)` and
Expand Down
40 changes: 24 additions & 16 deletions R/coord-radial.R
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,18 @@ CoordRadial <- ggproto("CoordRadial", Coord,
names(gdefs) <- aesthetics

# Train theta guide
for (t in intersect(c("theta", "theta.sec"), aesthetics[!empty])) {
gdefs[[t]] <- guides[[t]]$train(gdefs[[t]], panel_params[[t]])
gdefs[[t]] <- guides[[t]]$transform(gdefs[[t]], self, panel_params)
gdefs[[t]] <- guides[[t]]$get_layer_key(gdefs[[t]], layers)
}
t <- intersect(c("theta", "theta.sec"), aesthetics[!empty])
gdefs[t] <- Map(
function(guide, guide_param, scale) {
guide_param$theme_suffix <- "theta"
guide_param <- guide$train(guide_param, scale)
guide_param <- guide$transform(guide_param, self, panel_params)
guide_param <- guide$get_layer_key(guide_param, layers)
},
guide = guides[t],
guide_param = gdefs[t],
scale = panel_params[t]
)

if (!isFALSE(self$r_axis_inside)) {
# For radial axis, we need to pretend that rotation starts at 0 and
Expand All @@ -269,17 +276,18 @@ CoordRadial <- ggproto("CoordRadial", Coord,
temp <- modify_list(panel_params, mod)

# Train radial guide
for (r in intersect(c("r", "r.sec"), aesthetics[!empty])) {
gdefs[[r]] <- guides[[r]]$train(gdefs[[r]], panel_params[[r]])
gdefs[[r]] <- guides[[r]]$transform(gdefs[[r]], self, temp) # Use temp
gdefs[[r]] <- guides[[r]]$get_layer_key(gdefs[[r]], layers)
}

# Set theme suffixes
gdefs$theta$theme_suffix <- "theta"
gdefs$theta.sec$theme_suffix <- "theta"
gdefs$r$theme_suffix <- "r"
gdefs$r.sec$theme_suffix <- "r"
r <- intersect(c("r", "r.sec"), aesthetics[!empty])
gdefs[r] <- Map(
function(guide, guide_param, scale) {
guide_param$theme_suffix <- "r"
guide_param <- guide$train(guide_param, scale)
guide_param <- guide$transform(guide_param, self, temp)
guide_param <- guide$get_layer_key(guide_param, layers)
},
guide = guides[r],
guide_param = gdefs[r],
scale = panel_params[r]
)

panel_params$guides$update_params(gdefs)
panel_params
Expand Down
26 changes: 15 additions & 11 deletions R/guide-axis-theta.R
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,26 @@ GuideAxisTheta <- ggproto(

transform = function(params, coord, panel_params) {

opposite_var <- setdiff(c("x", "y"), params$aesthetic)
opposite_value <- switch(params$position, top = , right = , theta.sec = -Inf, Inf)
if (is.unsorted(panel_params$inner_radius %||% NA)) {
opposite_value <- -opposite_value
}
if (nrow(params$key) > 0) {
params$key[[opposite_var]] <- opposite_value
}
if (nrow(params$decor) > 0) {
params$decor[[opposite_var]] <- opposite_value
position <- params$position

if (!is.null(position)) {
opposite_var <- setdiff(c("x", "y"), params$aesthetic)
opposite_value <- switch(position, top = , right = , theta.sec = -Inf, Inf)
if (is.unsorted(panel_params$inner_radius %||% NA)) {
opposite_value <- -opposite_value
}
if (nrow(params$key) > 0) {
params$key[[opposite_var]] <- opposite_value
}
if (nrow(params$decor) > 0) {
params$decor[[opposite_var]] <- opposite_value
}
}

params <- GuideAxis$transform(params, coord, panel_params)

key <- params$key
n <- nrow(key)
n <- vec_size(key)
if (n < 1) {
return(params)
}
Expand Down
6 changes: 3 additions & 3 deletions R/guides-.R
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ Guides <- ggproto(
coord <- coord %||% default_inside_position %||% just

groups$justs[[i]] <- just
groups$coord[[i]] <- coord
groups$coords[[i]] <- coord
}

groups <- vec_group_loc(vec_slice(groups, keep))
Expand All @@ -540,10 +540,10 @@ Guides <- ggproto(
# prepare output
for (i in vec_seq_along(groups)) {
adjust <- NULL
position <- groups$key$position[i]
position <- groups$key$positions[i]
if (position == "inside") {
adjust <- theme(
legend.position.inside = groups$key$coord[[i]],
legend.position.inside = groups$key$coords[[i]],
legend.justification.inside = groups$key$justs[[i]]
)
}
Expand Down
12 changes: 12 additions & 0 deletions tests/testthat/test-coord-polar.R
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,18 @@ test_that("radial coords can be reversed", {
expect_equal(as.numeric(fwd$y), rev(as.numeric(rev$y)))
})

test_that("coord_radial can deal with empty breaks (#6271)", {
p <- ggplot_build(
ggplot(mtcars, aes(mpg, disp)) +
geom_point() +
coord_radial() +
scale_x_continuous(breaks = numeric()) +
scale_y_continuous(breaks = numeric())
)
guides <- p$layout$panel_params[[1]]$guides$guides
is_none <- vapply(guides, inherits, logical(1), what = "GuideNone")
expect_true(all(is_none))
})

# Visual tests ------------------------------------------------------------

Expand Down

0 comments on commit 09776db

Please sign in to comment.