Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Holed polygons #3128

Merged
merged 7 commits into from Feb 12, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
89 changes: 71 additions & 18 deletions R/geom-polygon.r
Expand Up @@ -3,7 +3,10 @@
#' Polygons are very similar to paths (as drawn by [geom_path()])
#' except that the start and end points are connected and the inside is
#' coloured by `fill`. The `group` aesthetic determines which cases
#' are connected together into a polygon.
#' are connected together into a polygon. From R 3.6 and onwards it is possible
#' to draw polygons with holes by providing a subgroup aesthetic that
#' differentiates the outer ring points from those describing holes in the
#' polygon.
#'
#' @eval rd_aesthetics("geom", "polygon")
#' @seealso
Expand Down Expand Up @@ -52,6 +55,22 @@
#'
#' # And if the positions are in longitude and latitude, you can use
#' # coord_map to produce different map projections.
#'
#' # As of R version 3.6 geom_polygon() supports polygons with holes
#' # Use the subgroup aesthetic to differentiate holes from the main polygon
#'
#' holes <- do.call(rbind, lapply(split(datapoly, datapoly$id), function(df) {
#' df$x <- df$x + 0.5 * (mean(df$x) - df$x)
#' df$y <- df$y + 0.5 * (mean(df$y) - df$y)
#' df
#' }))
#' datapoly$subid <- 1L
#' holes$subid <- 2L
#' datapoly <- rbind(datapoly, holes)
#'
#' p <- ggplot(datapoly, aes(x = x, y = y)) +
#' geom_polygon(aes(fill = value, group = id, subgroup = subid))
#' p
geom_polygon <- function(mapping = NULL, data = NULL,
stat = "identity", position = "identity",
...,
Expand Down Expand Up @@ -83,30 +102,64 @@ GeomPolygon <- ggproto("GeomPolygon", Geom,
if (n == 1) return(zeroGrob())

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

# For gpar(), there is one entry per polygon (not one entry per point).
# We'll pull the first value from each group, and assume all these values
# are the same within each group.
first_idx <- !duplicated(munched$group)
first_rows <- munched[first_idx, ]
if (is.null(munched$subgroup)) {
# Sort by group to make sure that colors, fill, etc. come in same order
munched <- munched[order(munched$group), ]

# For gpar(), there is one entry per polygon (not one entry per point).
# We'll pull the first value from each group, and assume all these values
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other geoms check this assumption and issue a warning if an aesthetic changes where it’s not supposed to. See e.g. geom_area(). Needed here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that may become prohibitly expensive to check for the kind of data that people will throw at this... Maybe make it more clear in the docs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think a single call to unique() is that much more expensive than, say, the ordering of the data that is also happening?

ggplot2/R/geom-ribbon.r

Lines 80 to 83 in 18be30e

aes <- unique(data[c("colour", "fill", "size", "linetype", "alpha")])
if (nrow(aes) > 1) {
stop("Aesthetics can not vary with a ribbon")
}

At a minimum, this might be worth a careful benchmark on a very large dataset, such as the output from isobands() on a large raster image.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ll happily do the benchmark, but the big difference is that geom_ribbon uses a draw_group method whereas geom_polygon uses draw_panel. This means that geom_polygon would have to split up the data and call unique on each sub-data.frame all for the sake of a possible warning. For geom ribbon the split has already been done so it is rather cheap

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. In any case, it's your call. I won't insist.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may wanted to eventually tackle this problem by providing a new geom where there's one row per polygon and the vertices are stored in a nested data frame.

# are the same within each group.
first_idx <- !duplicated(munched$group)
first_rows <- munched[first_idx, ]

ggname("geom_polygon",
polygonGrob(munched$x, munched$y, default.units = "native",
id = munched$group,
gp = gpar(
col = first_rows$colour,
fill = alpha(first_rows$fill, first_rows$alpha),
lwd = first_rows$size * .pt,
lty = first_rows$linetype
ggname(
"geom_polygon",
polygonGrob(
munched$x, munched$y, default.units = "native",
id = munched$group,
gp = gpar(
col = first_rows$colour,
fill = alpha(first_rows$fill, first_rows$alpha),
lwd = first_rows$size * .pt,
lty = first_rows$linetype
)
)
)
)
} else {
if (!grid_has_multipath) {
stop("Polygons with holes are only supported in R versions from 3.6 and onwards", call. = FALSE)
}
# Sort by group to make sure that colors, fill, etc. come in same order
munched <- munched[order(munched$group, munched$subgroup), ]
id <- match(munched$subgroup, unique(munched$subgroup))

# For gpar(), there is one entry per polygon (not one entry per point).
# We'll pull the first value from each group, and assume all these values
# are the same within each group.
first_idx <- !duplicated(munched$group)
first_rows <- munched[first_idx, ]

ggname(
"geom_polygon",
pathGrob(
munched$x, munched$y, default.units = "native",
id = id, pathId = munched$group,
rule = "evenodd",
gp = gpar(
col = first_rows$colour,
fill = alpha(first_rows$fill, first_rows$alpha),
lwd = first_rows$size * .pt,
lty = first_rows$linetype
)
)
)
thomasp85 marked this conversation as resolved.
Show resolved Hide resolved
}

},

default_aes = aes(colour = "NA", fill = "grey20", size = 0.5, linetype = 1,
alpha = NA),
alpha = NA, subgroup = NULL),

handle_na = function(data, params) {
data
Expand Down
2 changes: 2 additions & 0 deletions R/ggplot-global.R
Expand Up @@ -44,3 +44,5 @@ ggplot_global$all_aesthetics <- .all_aesthetics
)

ggplot_global$base_to_ggplot <- .base_to_ggplot

grid_has_multipath <- packageVersion('grid') >= "3.6"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think computing this value at build time is correct. Instead, I think you should just inline into geom_polygon().

1 change: 1 addition & 0 deletions man/geom_map.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 21 additions & 1 deletion man/geom_polygon.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.