Skip to content

Commit 9a87145

Browse files
authored
Holed polygons (#3128)
1 parent 7389a3f commit 9a87145

File tree

5 files changed

+120
-22
lines changed

5 files changed

+120
-22
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@
5454

5555
* `stat_bin()` now handles data with only one unique value (@yutannihilation #3047).
5656

57+
* `geom_polygon()` can now draw polygons with holes using the new `subgroup`
58+
aesthetic. This functionality requires R 3.6 (@thomasp85, #3128)
59+
5760
# ggplot2 3.1.0
5861

5962
## Breaking changes

R/geom-polygon.r

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
#' Polygons are very similar to paths (as drawn by [geom_path()])
44
#' except that the start and end points are connected and the inside is
55
#' coloured by `fill`. The `group` aesthetic determines which cases
6-
#' are connected together into a polygon.
6+
#' are connected together into a polygon. From R 3.6 and onwards it is possible
7+
#' to draw polygons with holes by providing a subgroup aesthetic that
8+
#' differentiates the outer ring points from those describing holes in the
9+
#' polygon.
710
#'
811
#' @eval rd_aesthetics("geom", "polygon")
912
#' @seealso
@@ -12,6 +15,10 @@
1215
#' @export
1316
#' @inheritParams layer
1417
#' @inheritParams geom_point
18+
#' @param rule Either `"evenodd"` or `"winding"`. If polygons with holes are
19+
#' being drawn (using the `subgroup` aesthetic) this argument defines how the
20+
#' hole coordinates are interpreted. See the examples in [grid::pathGrob()] for
21+
#' an explanation.
1522
#' @examples
1623
#' # When using geom_polygon, you will typically need two data frames:
1724
#' # one contains the coordinates of each polygon (positions), and the
@@ -52,8 +59,28 @@
5259
#'
5360
#' # And if the positions are in longitude and latitude, you can use
5461
#' # coord_map to produce different map projections.
62+
#'
63+
#' if (packageVersion("grid") >= "3.6") {
64+
#' # As of R version 3.6 geom_polygon() supports polygons with holes
65+
#' # Use the subgroup aesthetic to differentiate holes from the main polygon
66+
#'
67+
#' holes <- do.call(rbind, lapply(split(datapoly, datapoly$id), function(df) {
68+
#' df$x <- df$x + 0.5 * (mean(df$x) - df$x)
69+
#' df$y <- df$y + 0.5 * (mean(df$y) - df$y)
70+
#' df
71+
#' }))
72+
#' datapoly$subid <- 1L
73+
#' holes$subid <- 2L
74+
#' datapoly <- rbind(datapoly, holes)
75+
#'
76+
#' p <- ggplot(datapoly, aes(x = x, y = y)) +
77+
#' geom_polygon(aes(fill = value, group = id, subgroup = subid))
78+
#' p
79+
#' }
80+
#'
5581
geom_polygon <- function(mapping = NULL, data = NULL,
5682
stat = "identity", position = "identity",
83+
rule = "evenodd",
5784
...,
5885
na.rm = FALSE,
5986
show.legend = NA,
@@ -68,6 +95,7 @@ geom_polygon <- function(mapping = NULL, data = NULL,
6895
inherit.aes = inherit.aes,
6996
params = list(
7097
na.rm = na.rm,
98+
rule = rule,
7199
...
72100
)
73101
)
@@ -78,35 +106,69 @@ geom_polygon <- function(mapping = NULL, data = NULL,
78106
#' @usage NULL
79107
#' @export
80108
GeomPolygon <- ggproto("GeomPolygon", Geom,
81-
draw_panel = function(data, panel_params, coord) {
109+
draw_panel = function(data, panel_params, coord, rule = "evenodd") {
82110
n <- nrow(data)
83111
if (n == 1) return(zeroGrob())
84112

85113
munched <- coord_munch(coord, data, panel_params)
86-
# Sort by group to make sure that colors, fill, etc. come in same order
87-
munched <- munched[order(munched$group), ]
88114

89-
# For gpar(), there is one entry per polygon (not one entry per point).
90-
# We'll pull the first value from each group, and assume all these values
91-
# are the same within each group.
92-
first_idx <- !duplicated(munched$group)
93-
first_rows <- munched[first_idx, ]
115+
if (is.null(munched$subgroup)) {
116+
# Sort by group to make sure that colors, fill, etc. come in same order
117+
munched <- munched[order(munched$group), ]
118+
119+
# For gpar(), there is one entry per polygon (not one entry per point).
120+
# We'll pull the first value from each group, and assume all these values
121+
# are the same within each group.
122+
first_idx <- !duplicated(munched$group)
123+
first_rows <- munched[first_idx, ]
94124

95-
ggname("geom_polygon",
96-
polygonGrob(munched$x, munched$y, default.units = "native",
97-
id = munched$group,
98-
gp = gpar(
99-
col = first_rows$colour,
100-
fill = alpha(first_rows$fill, first_rows$alpha),
101-
lwd = first_rows$size * .pt,
102-
lty = first_rows$linetype
125+
ggname(
126+
"geom_polygon",
127+
polygonGrob(
128+
munched$x, munched$y, default.units = "native",
129+
id = munched$group,
130+
gp = gpar(
131+
col = first_rows$colour,
132+
fill = alpha(first_rows$fill, first_rows$alpha),
133+
lwd = first_rows$size * .pt,
134+
lty = first_rows$linetype
135+
)
103136
)
104137
)
105-
)
138+
} else {
139+
if (utils::packageVersion('grid') < "3.6") {
140+
stop("Polygons with holes requires R 3.6 or above", call. = FALSE)
141+
}
142+
# Sort by group to make sure that colors, fill, etc. come in same order
143+
munched <- munched[order(munched$group, munched$subgroup), ]
144+
id <- match(munched$subgroup, unique(munched$subgroup))
145+
146+
# For gpar(), there is one entry per polygon (not one entry per point).
147+
# We'll pull the first value from each group, and assume all these values
148+
# are the same within each group.
149+
first_idx <- !duplicated(munched$group)
150+
first_rows <- munched[first_idx, ]
151+
152+
ggname(
153+
"geom_polygon",
154+
pathGrob(
155+
munched$x, munched$y, default.units = "native",
156+
id = id, pathId = munched$group,
157+
rule = rule,
158+
gp = gpar(
159+
col = first_rows$colour,
160+
fill = alpha(first_rows$fill, first_rows$alpha),
161+
lwd = first_rows$size * .pt,
162+
lty = first_rows$linetype
163+
)
164+
)
165+
)
166+
}
167+
106168
},
107169

108170
default_aes = aes(colour = "NA", fill = "grey20", size = 0.5, linetype = 1,
109-
alpha = NA),
171+
alpha = NA, subgroup = NULL),
110172

111173
handle_na = function(data, params) {
112174
data

man/borders.Rd

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/geom_map.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/geom_polygon.Rd

Lines changed: 31 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)