Skip to content

Commit e57d6b8

Browse files
authored
Reversal of coordinates (#6070)
* remove vestigial code * reverse mechanism for cartesian coords * reverse mechanism for sf coord * simplify `guide_axis_theta()` decor stuff * replace `direction` with `reverse = "theta"` * swap `inner_radius` when `reverse = "r"` * reverse for coord_transform * rewrite `guide_grid()` to work with coord transforms * new ViewScale methods for mapped (but not rescaled) breaks * adopt new `guide_grid()` * accept that `coord_radial()` now renders minor r gridlines * document * internally sort ranges * add tests * scales sort limits upon construction * add news bullet * fix merge misstep
1 parent cc98fd1 commit e57d6b8

28 files changed

+421
-296
lines changed

NEWS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# ggplot2 (development version)
22

3+
* Reversal of a dimension, typically 'x' or 'y', is now controlled by the
4+
`reverse` argument in `coord_cartesian()`, `coord_fixed()`, `coord_radial()`
5+
and `coord_sf()`. In `coord_radial()`, this replaces the older `direction`
6+
argument (#4021, @teunbrand).
7+
* `coord_radial()` displays minor gridlines now (@teunbrand).
8+
* (internal) `continuous_scale()` and `binned_scale()` sort the `limits`
9+
argument internally (@teunbrand).
310
* Theme margins can have NA-units to inherit from parent elements. The new
411
function `margin_part()` has NA-units as default (@teunbrand, #6115)
512
* New `margin_auto()` specification for theme margins.

R/coord-.R

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ Coord <- ggproto("Coord",
5959
# "on" = yes, "off" = no
6060
clip = "on",
6161

62+
# Should any of the scales be reversed?
63+
reverse = "none",
64+
6265
aspect = function(ranges) NULL,
6366

6467
labels = function(self, labels, panel_params) {
@@ -185,11 +188,7 @@ Coord <- ggproto("Coord",
185188
is_free = function() FALSE,
186189

187190
setup_params = function(self, data) {
188-
list(
189-
guide_default = guide_axis(),
190-
guide_missing = guide_none(),
191-
expand = parse_coord_expand(self$expand %||% TRUE)
192-
)
191+
list(expand = parse_coord_expand(self$expand %||% TRUE))
193192
},
194193

195194
setup_data = function(data, params = list()) {

R/coord-cartesian-.R

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
#' limits are set via `xlim` and `ylim` and some data points fall outside those
2626
#' limits, then those data points may show up in places such as the axes, the
2727
#' legend, the plot title, or the plot margins.
28+
#' @param reverse A string giving which directions to reverse. `"none"`
29+
#' (default) keeps directions as is. `"x"` and `"y"` can be used to reverse
30+
#' their respective directions. `"xy"` can be used to reverse both
31+
#' directions.
2832
#' @export
2933
#' @examples
3034
#' # There are two ways of zooming the plot display: with scales or
@@ -64,11 +68,12 @@
6468
#' # displayed bigger
6569
#' d + coord_cartesian(xlim = c(0, 1))
6670
coord_cartesian <- function(xlim = NULL, ylim = NULL, expand = TRUE,
67-
default = FALSE, clip = "on") {
71+
default = FALSE, clip = "on", reverse = "none") {
6872
check_coord_limits(xlim)
6973
check_coord_limits(ylim)
7074
ggproto(NULL, CoordCartesian,
7175
limits = list(x = xlim, y = ylim),
76+
reverse = reverse,
7277
expand = expand,
7378
default = default,
7479
clip = clip
@@ -97,8 +102,11 @@ CoordCartesian <- ggproto("CoordCartesian", Coord,
97102
self$range(panel_params)
98103
},
99104

100-
transform = function(data, panel_params) {
101-
data <- transform_position(data, panel_params$x$rescale, panel_params$y$rescale)
105+
transform = function(self, data, panel_params) {
106+
reverse <- self$reverse %||% "none"
107+
x <- panel_params$x[[switch(reverse, xy = , x = "reverse", "rescale")]]
108+
y <- panel_params$y[[switch(reverse, xy = , y = "reverse", "rescale")]]
109+
data <- transform_position(data, x, y)
102110
transform_position(data, squish_infinite, squish_infinite)
103111
},
104112

@@ -109,14 +117,8 @@ CoordCartesian <- ggproto("CoordCartesian", Coord,
109117
)
110118
},
111119

112-
render_bg = function(panel_params, theme) {
113-
guide_grid(
114-
theme,
115-
panel_params$x$break_positions_minor(),
116-
panel_params$x$break_positions(),
117-
panel_params$y$break_positions_minor(),
118-
panel_params$y$break_positions()
119-
)
120+
render_bg = function(self, panel_params, theme) {
121+
guide_grid(theme, panel_params, self)
120122
},
121123

122124
render_axis_h = function(panel_params, theme) {

R/coord-fixed.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@
2222
#' p + coord_fixed(xlim = c(15, 30))
2323
#'
2424
#' # Resize the plot to see that the specified aspect ratio is maintained
25-
coord_fixed <- function(ratio = 1, xlim = NULL, ylim = NULL, expand = TRUE, clip = "on") {
25+
coord_fixed <- function(ratio = 1, xlim = NULL, ylim = NULL, expand = TRUE,
26+
clip = "on", reverse = "none") {
2627
check_coord_limits(xlim)
2728
check_coord_limits(ylim)
2829
ggproto(NULL, CoordFixed,
2930
limits = list(x = xlim, y = ylim),
3031
ratio = ratio,
3132
expand = expand,
33+
reverse = reverse,
3234
clip = clip
3335
)
3436
}

R/coord-radial.R

Lines changed: 40 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@
1919
#' in accordance with the computed `theta` position. If `FALSE` (default),
2020
#' no such transformation is performed. Can be useful to rotate text geoms in
2121
#' alignment with the coordinates.
22-
#' @param inner.radius A `numeric` between 0 and 1 setting the size of a inner.radius hole.
22+
#' @param inner.radius A `numeric` between 0 and 1 setting the size of a
23+
#' inner radius hole.
24+
#' @param reverse A string giving which directions to reverse. `"none"`
25+
#' (default) keep directions as is. `"theta"` reverses the angle and `"r"`
26+
#' reverses the radius. `"thetar"` reverses both the angle and the radius.
2327
#' @param r_axis_inside,rotate_angle `r lifecycle::badge("deprecated")`
2428
#'
2529
#' @note
@@ -39,11 +43,12 @@
3943
coord_radial <- function(theta = "x",
4044
start = 0, end = NULL,
4145
expand = TRUE,
42-
direction = 1,
46+
direction = deprecated(),
4347
clip = "off",
4448
r.axis.inside = NULL,
4549
rotate.angle = FALSE,
4650
inner.radius = 0,
51+
reverse = "none",
4752
r_axis_inside = deprecated(),
4853
rotate_angle = deprecated()) {
4954

@@ -59,34 +64,46 @@ coord_radial <- function(theta = "x",
5964
)
6065
rotate.angle <- rotate_angle
6166
}
67+
if (lifecycle::is_present(direction)) {
68+
deprecate_warn0(
69+
"3.5.2", "coord_radial(direction)", "coord_radial(reverse)"
70+
)
71+
reverse <- switch(reverse, "r" = "thetar", "theta")
72+
}
6273

6374
theta <- arg_match0(theta, c("x", "y"))
6475
r <- if (theta == "x") "y" else "x"
6576
if (!is.numeric(r.axis.inside)) {
6677
check_bool(r.axis.inside, allow_null = TRUE)
6778
}
79+
reverse <- arg_match0(reverse, c("theta", "thetar", "r", "none"))
6880

6981
check_bool(rotate.angle)
7082
check_number_decimal(start, allow_infinite = FALSE)
7183
check_number_decimal(end, allow_infinite = FALSE, allow_null = TRUE)
7284
check_number_decimal(inner.radius, min = 0, max = 1, allow_infinite = FALSE)
7385

74-
end <- end %||% (start + 2 * pi)
75-
if (start > end) {
76-
n_rotate <- ((start - end) %/% (2 * pi)) + 1
77-
start <- start - n_rotate * 2 * pi
86+
arc <- c(start, end %||% (start + 2 * pi))
87+
if (arc[1] > arc[2]) {
88+
n_rotate <- ((arc[1] - arc[2]) %/% (2 * pi)) + 1
89+
arc[1] <- arc[1] - n_rotate * 2 * pi
7890
}
79-
r.axis.inside <- r.axis.inside %||% !(abs(end - start) >= 1.999 * pi)
91+
arc <- switch(reverse, thetar = , theta = rev(arc), arc)
92+
93+
r.axis.inside <- r.axis.inside %||% !(abs(arc[2] - arc[1]) >= 1.999 * pi)
94+
95+
inner.radius <- c(inner.radius, 1) * 0.4
96+
inner.radius <- switch(reverse, thetar = , r = rev, identity)(inner.radius)
8097

8198
ggproto(NULL, CoordRadial,
8299
theta = theta,
83100
r = r,
84-
arc = c(start, end),
101+
arc = arc,
85102
expand = expand,
86-
direction = sign(direction),
103+
reverse = reverse,
87104
r_axis_inside = r.axis.inside,
88105
rotate_angle = rotate.angle,
89-
inner_radius = c(inner.radius, 1) * 0.4,
106+
inner_radius = inner.radius,
90107
clip = clip
91108
)
92109
}
@@ -107,16 +124,10 @@ CoordRadial <- ggproto("CoordRadial", Coord,
107124
arc <- details$arc %||% c(0, 2 * pi)
108125
if (self$theta == "x") {
109126
r <- rescale(y, from = details$r.range, to = self$inner_radius / 0.4)
110-
theta <- theta_rescale_no_clip(
111-
x, details$theta.range,
112-
arc, self$direction
113-
)
127+
theta <- theta_rescale_no_clip(x, details$theta.range, arc)
114128
} else {
115129
r <- rescale(x, from = details$r.range, to = self$inner_radius / 0.4)
116-
theta <- theta_rescale_no_clip(
117-
y, details$theta.range,
118-
arc, self$direction
119-
)
130+
theta <- theta_rescale_no_clip(y, details$theta.range, arc)
120131
}
121132

122133
dist_polar(r, theta)
@@ -200,10 +211,10 @@ CoordRadial <- ggproto("CoordRadial", Coord,
200211

201212
r_position <- c("left", "right")
202213
# If both opposite direction and opposite position, don't flip
203-
if (xor(self$direction == -1, opposite_r)) {
214+
if (xor(self$reverse %in% c("thetar", "theta"), opposite_r)) {
204215
r_position <- rev(r_position)
205216
}
206-
arc <- rad2deg(panel_params$axis_rotation) * self$direction
217+
arc <- rad2deg(panel_params$axis_rotation)
207218
if (opposite_r) {
208219
arc <- rev(arc)
209220
}
@@ -284,10 +295,7 @@ CoordRadial <- ggproto("CoordRadial", Coord,
284295
arc <- panel_params$arc %||% c(0, 2 * pi)
285296

286297
data$r <- r_rescale(data$r, panel_params$r.range, panel_params$inner_radius)
287-
data$theta <- theta_rescale(
288-
data$theta, panel_params$theta.range,
289-
arc, self$direction
290-
)
298+
data$theta <- theta_rescale(data$theta, panel_params$theta.range, arc)
291299
data$x <- rescale(data$r * sin(data$theta) + 0.5, from = bbox$x)
292300
data$y <- rescale(data$r * cos(data$theta) + 0.5, from = bbox$y)
293301

@@ -313,70 +321,12 @@ CoordRadial <- ggproto("CoordRadial", Coord,
313321
},
314322

315323
render_bg = function(self, panel_params, theme) {
316-
317-
bbox <- panel_params$bbox %||% list(x = c(0, 1), y = c(0, 1))
318-
arc <- panel_params$arc %||% c(0, 2 * pi)
319-
dir <- self$direction
320-
inner_radius <- panel_params$inner_radius
321-
322-
theta_lim <- panel_params$theta.range
323-
theta_maj <- panel_params$theta.major
324-
theta_min <- setdiff(panel_params$theta.minor, theta_maj)
325-
326-
if (length(theta_maj) > 0) {
327-
theta_maj <- theta_rescale(theta_maj, theta_lim, arc, dir)
328-
}
329-
if (length(theta_min) > 0) {
330-
theta_min <- theta_rescale(theta_min, theta_lim, arc, dir)
331-
}
332-
333-
theta_fine <- theta_rescale(seq(0, 1, length.out = 100), c(0, 1), arc, dir)
334-
r_fine <- r_rescale(panel_params$r.major, panel_params$r.range,
335-
panel_params$inner_radius)
336-
337-
# This gets the proper theme element for theta and r grid lines:
338-
# panel.grid.major.x or .y
339-
grid_elems <- paste(
340-
c("panel.grid.major.", "panel.grid.minor.", "panel.grid.major."),
341-
c(self$theta, self$theta, self$r), sep = ""
324+
panel_params <- switch(
325+
self$theta,
326+
x = rename(panel_params, c(theta = "x", r = "y")),
327+
y = rename(panel_params, c(theta = "y", r = "x"))
342328
)
343-
grid_elems <- lapply(grid_elems, calc_element, theme = theme)
344-
majortheta <- paste("panel.grid.major.", self$theta, sep = "")
345-
minortheta <- paste("panel.grid.minor.", self$theta, sep = "")
346-
majorr <- paste("panel.grid.major.", self$r, sep = "")
347-
348-
bg_element <- calc_element("panel.background", theme)
349-
if (!inherits(bg_element, "element_blank")) {
350-
background <- data_frame0(
351-
x = c(Inf, Inf, -Inf, -Inf),
352-
y = c(Inf, -Inf, -Inf, Inf)
353-
)
354-
background <- coord_munch(self, background, panel_params, is_closed = TRUE)
355-
bg_gp <- gg_par(
356-
lwd = bg_element$linewidth,
357-
col = bg_element$colour, fill = bg_element$fill,
358-
lty = bg_element$linetype
359-
)
360-
background <- polygonGrob(
361-
x = background$x, y = background$y,
362-
gp = bg_gp
363-
)
364-
} else {
365-
background <- zeroGrob()
366-
}
367-
368-
ggname("grill", grobTree(
369-
background,
370-
theta_grid(theta_maj, grid_elems[[1]], inner_radius, bbox),
371-
theta_grid(theta_min, grid_elems[[2]], inner_radius, bbox),
372-
element_render(
373-
theme, majorr, name = "radius",
374-
x = rescale(outer(sin(theta_fine), r_fine) + 0.5, from = bbox$x),
375-
y = rescale(outer(cos(theta_fine), r_fine) + 0.5, from = bbox$y),
376-
id.lengths = rep(length(theta_fine), length(r_fine)),
377-
default.units = "native"
378-
)
379-
))
329+
guide_grid(theme, panel_params, self, square = FALSE)
380330
},
381331

382332
render_fg = function(self, panel_params, theme) {
@@ -395,8 +345,8 @@ CoordRadial <- ggproto("CoordRadial", Coord,
395345
bbox <- panel_params$bbox
396346
dir <- self$direction
397347
rot <- panel_params$axis_rotation
398-
rot <- if (dir == 1) rot else rev(rot)
399-
rot <- dir * rad2deg(-rot)
348+
rot <- switch(self$reverse, thetar = , theta = rev(rot), rot)
349+
rot <- rad2deg(-rot)
400350

401351
left <- panel_guides_grob(panel_params$guides, position = "left", theme)
402352
left <- rotate_r_axis(left, rot[1], bbox, "left")
@@ -540,6 +490,7 @@ polar_bbox <- function(arc, margin = c(0.05, 0.05, 0.05, 0.05),
540490
if (abs(diff(arc)) >= 2 * pi) {
541491
return(list(x = c(0, 1), y = c(0, 1)))
542492
}
493+
arc <- sort(arc)
543494

544495
# X and Y position of the sector arc ends
545496
xmax <- 0.5 * sin(arc) + 0.5

0 commit comments

Comments
 (0)