Skip to content

Commit aa2ddbe

Browse files
authored
coord_munch can convert closed shapes to closed form (#5081)
* `coord_munch()` can close polygons * Polygon-like Geoms use the `make_closed` argument * Speed up rectangles to polygons * Accept invisible visual changes * Add NEWS bullet * Document `make_closed` argument * Fix order issue * make_closed -> is_closed * improve `close_poly()` * Remove closing point after munching * Accept snapshots * Add munching test
1 parent d4db080 commit aa2ddbe

13 files changed

+136
-41
lines changed

NEWS.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ggplot2 (development version)
22

3+
* `coord_munch()` can now close polygon shapes (@teunbrand, #3271)
4+
35
* You can now omit either `xend` or `yend` from `geom_segment()` as only one
46
of these is now required. If one is missing, it will be filled from the `x`
57
and `y` aesthetics respectively. This makes drawing horizontal or vertical

R/annotation-map.R

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ GeomAnnotationMap <- ggproto("GeomAnnotationMap", GeomMap,
8989
draw_panel = function(data, panel_params, coord, map) {
9090
# Munch, then set up id variable for polygonGrob -
9191
# must be sequential integers
92-
coords <- coord_munch(coord, map, panel_params)
92+
coords <- coord_munch(coord, map, panel_params, is_closed = TRUE)
9393
coords$group <- coords$group %||% coords$id
9494
grob_id <- match(coords$group, unique0(coords$group))
9595

R/coord-munch.R

+33-1
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@
99
#' All other variables are duplicated as needed.
1010
#' @param range Panel range specification.
1111
#' @param segment_length Target segment length
12+
#' @param is_closed Whether data should be considered as a closed polygon.
1213
#' @keywords internal
1314
#' @export
14-
coord_munch <- function(coord, data, range, segment_length = 0.01) {
15+
coord_munch <- function(coord, data, range, segment_length = 0.01, is_closed = FALSE) {
1516
if (coord$is_linear()) return(coord$transform(data, range))
1617

18+
if (is_closed) {
19+
data <- close_poly(data)
20+
}
21+
1722
# range has theta and r values; get corresponding x and y values
1823
ranges <- coord$backtransform_range(range)
1924

@@ -34,6 +39,11 @@ coord_munch <- function(coord, data, range, segment_length = 0.01) {
3439

3540
# Munch and then transform result
3641
munched <- munch_data(data, dist, segment_length)
42+
if (is_closed) {
43+
group_cols <- intersect(c("group", "subgroup"), names(munched))
44+
runs <- vec_run_sizes(munched[, group_cols, drop = FALSE])
45+
munched <- vec_slice(munched, -(cumsum(runs)))
46+
}
3747
coord$transform(munched, range)
3848
}
3949

@@ -204,3 +214,25 @@ spiral_arc_length <- function(a, theta1, theta2) {
204214
(theta1 * sqrt(1 + theta1 * theta1) + asinh(theta1)) -
205215
(theta2 * sqrt(1 + theta2 * theta2) + asinh(theta2)))
206216
}
217+
218+
# Closes a polygon type data structure by repeating the first-in-group after
219+
# the last-in-group
220+
close_poly <- function(data) {
221+
# Sort by group
222+
groups <- data[, intersect(c("group", "subgroup"), names(data)), drop = FALSE]
223+
ord <- vec_order(groups)
224+
225+
# Run length encoding stats
226+
runs <- vec_run_sizes(vec_slice(groups, ord))
227+
ends <- cumsum(runs)
228+
starts <- ends - runs + 1
229+
230+
# Repeat 1st row of group after every group
231+
index <- seq_len(nrow(data))
232+
insert <- ends + seq_along(ends)
233+
new_index <- integer(length(index) + length(runs))
234+
new_index[-insert] <- index
235+
new_index[insert] <- starts
236+
237+
vec_slice(data, ord[new_index])
238+
}

R/geom-map.R

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ GeomMap <- ggproto("GeomMap", GeomPolygon,
135135

136136
# Munch, then set up id variable for polygonGrob -
137137
# must be sequential integers
138-
coords <- coord_munch(coord, map, panel_params)
138+
coords <- coord_munch(coord, map, panel_params, is_closed = TRUE)
139139
coords$group <- coords$group %||% coords$id
140140
grob_id <- match(coords$group, unique0(coords$group))
141141

R/geom-polygon.R

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ GeomPolygon <- ggproto("GeomPolygon", Geom,
113113
n <- nrow(data)
114114
if (n == 1) return(zeroGrob())
115115

116-
munched <- coord_munch(coord, data, panel_params)
116+
munched <- coord_munch(coord, data, panel_params, is_closed = TRUE)
117117

118118
if (is.null(munched$subgroup)) {
119119
# Sort by group to make sure that colors, fill, etc. come in same order

R/geom-rect.R

+8-22
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,16 @@ GeomRect <- ggproto("GeomRect", Geom,
3939
aesthetics <- setdiff(
4040
names(data), c("x", "y", "xmin", "xmax", "ymin", "ymax")
4141
)
42+
index <- rep(seq_len(nrow(data)), each = 4)
4243

43-
polys <- lapply(split(data, seq_len(nrow(data))), function(row) {
44-
poly <- rect_to_poly(row$xmin, row$xmax, row$ymin, row$ymax)
45-
aes <- row[rep(1,5), aesthetics]
44+
new <- data[index, aesthetics, drop = FALSE]
45+
new$x <- vec_interleave(data$xmin, data$xmax, data$xmax, data$xmin)
46+
new$y <- vec_interleave(data$ymax, data$ymax, data$ymin, data$ymin)
47+
new$group <- index
4648

47-
GeomPolygon$draw_panel(vec_cbind(poly, aes), panel_params, coord, lineend = lineend, linejoin = linejoin)
48-
})
49-
50-
ggname("geom_rect", inject(grobTree(!!!polys)))
49+
ggname("geom_rect", GeomPolygon$draw_panel(
50+
new, panel_params, coord, lineend = lineend, linejoin = linejoin
51+
))
5152
} else {
5253
coords <- coord$transform(data, panel_params)
5354
ggname("geom_rect", rectGrob(
@@ -72,18 +73,3 @@ GeomRect <- ggproto("GeomRect", Geom,
7273

7374
rename_size = TRUE
7475
)
75-
76-
77-
# Convert rectangle to polygon
78-
# Useful for non-Cartesian coordinate systems where it's easy to work purely in
79-
# terms of locations, rather than locations and dimensions. Note that, though
80-
# `polygonGrob()` expects an open form, closed form is needed for correct
81-
# munching (c.f. https://github.com/tidyverse/ggplot2/issues/3037#issuecomment-458406857).
82-
#
83-
# @keyword internal
84-
rect_to_poly <- function(xmin, xmax, ymin, ymax) {
85-
data_frame0(
86-
y = c(ymax, ymax, ymin, ymin, ymax),
87-
x = c(xmin, xmax, xmax, xmin, xmin)
88-
)
89-
}

man/coord_munch.Rd

+3-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/_snaps/coord-polar/racetrack-plot-closed-and-has-center-hole.svg

+3-3
Loading

0 commit comments

Comments
 (0)