Skip to content

Commit 2b5a82d

Browse files
authored
Merge pull request #1188 from ropensci/sf-grob
fortify sf objects in a similar manner to sf::st_as_grob
2 parents 89e0cd8 + cae36fa commit 2b5a82d

File tree

11 files changed

+195
-191
lines changed

11 files changed

+195
-191
lines changed

NAMESPACE

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,6 @@ S3method(geom2trace,GeomPolygon)
2020
S3method(geom2trace,GeomText)
2121
S3method(geom2trace,GeomTile)
2222
S3method(geom2trace,default)
23-
S3method(get_l,LINESTRING)
24-
S3method(get_l,MULTILINESTRING)
25-
S3method(get_l,MULTIPOINT)
26-
S3method(get_l,MULTIPOLYGON)
27-
S3method(get_l,POINT)
28-
S3method(get_l,POLYGON)
29-
S3method(get_x,LINESTRING)
30-
S3method(get_x,MULTILINESTRING)
31-
S3method(get_x,MULTIPOINT)
32-
S3method(get_x,MULTIPOLYGON)
33-
S3method(get_x,POINT)
34-
S3method(get_x,POLYGON)
35-
S3method(get_y,LINESTRING)
36-
S3method(get_y,MULTILINESTRING)
37-
S3method(get_y,MULTIPOINT)
38-
S3method(get_y,MULTIPOLYGON)
39-
S3method(get_y,POINT)
40-
S3method(get_y,POLYGON)
4123
S3method(ggplot,plotly)
4224
S3method(ggplotly,ggmatrix)
4325
S3method(ggplotly,ggplot)
@@ -58,6 +40,17 @@ S3method(print,api_plot)
5840
S3method(rename_,plotly)
5941
S3method(select_,plotly)
6042
S3method(slice_,plotly)
43+
S3method(st_as_plotly,CIRCULARSTRING)
44+
S3method(st_as_plotly,COMPOUNDCURVE)
45+
S3method(st_as_plotly,CURVEPOLYGON)
46+
S3method(st_as_plotly,GEOMETRYCOLLECTION)
47+
S3method(st_as_plotly,LINESTRING)
48+
S3method(st_as_plotly,MULTILINESTRING)
49+
S3method(st_as_plotly,MULTIPOINT)
50+
S3method(st_as_plotly,MULTIPOLYGON)
51+
S3method(st_as_plotly,MULTISURFACE)
52+
S3method(st_as_plotly,POINT)
53+
S3method(st_as_plotly,POLYGON)
6154
S3method(summarise_,plotly)
6255
S3method(to_basic,GeomAbline)
6356
S3method(to_basic,GeomAnnotationMap)
@@ -145,9 +138,6 @@ export(filter)
145138
export(filter_)
146139
export(geom2trace)
147140
export(get_figure)
148-
export(get_l)
149-
export(get_x)
150-
export(get_y)
151141
export(gg2list)
152142
export(ggplotly)
153143
export(group2NA)

R/ggplotly.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ gg2list <- function(p, width = NULL, height = NULL,
617617

618618
if (sum(isGrill) == 0) {
619619
# TODO: reduce the number of points (via coord_munch?)
620-
d <- expand(rng$graticule)
620+
d <- fortify_sf(rng$graticule)
621621
d$x <- scales::rescale(d$x, rng$x_range, from = c(0, 1))
622622
d$y <- scales::rescale(d$y, rng$y_range, from = c(0, 1))
623623
params <- list(

R/group2NA.R

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ group2NA <- function(data, groupNames = "group", nested = NULL, ordered = NULL,
4545

4646
if (NROW(data) == 0) return(data)
4747

48+
# for restoring class information on exit
49+
datClass <- oldClass(data)
50+
51+
# data.table doesn't play nice with list-columns
52+
if (inherits(data, "sf")) data <- fortify_sf(data)
53+
4854
# evaluate this lazy argument now (in case we change class of data)
4955
retrace <- force(retrace.first)
5056

@@ -53,9 +59,6 @@ group2NA <- function(data, groupNames = "group", nested = NULL, ordered = NULL,
5359
nested <- nested[nested %in% names(data)]
5460
ordered <- ordered[ordered %in% names(data)]
5561

56-
# for restoring class information on exit
57-
datClass <- oldClass(data)
58-
5962
dt <- data.table::as.data.table(data)
6063

6164
# if group doesn't exist, just order the rows and exit

R/layers2traces.R

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ layers2traces <- function(data, prestats_data, layout, p) {
111111
separator <- new_id()
112112
fac <- factor(
113113
apply(d[split_vars], 1, paste, collapse = separator),
114-
levels = apply(lvls, 1, paste, collapse = separator)
114+
levels = unique(apply(lvls, 1, paste, collapse = separator))
115115
)
116116
if (all(is.na(fac))) fac <- 1
117117
dl <- split(d, fac, drop = TRUE)
@@ -270,22 +270,46 @@ to_basic.GeomRect <- function(data, prestats_data, layout, params, p, ...) {
270270
prefix_class(dat, c("GeomPolygon", "GeomRect"))
271271
}
272272

273-
#' @export
273+
#' @export
274274
to_basic.GeomSf <- function(data, prestats_data, layout, params, p, ...) {
275275

276-
data <- expand(data)
276+
data <- sf::st_as_sf(data)
277+
geom_type <- sf::st_geometry_type(data)
278+
# st_cast should "expand" a collection into multiple rows (one per feature)
279+
if ("GEOMETRYCOLLECTION" %in% geom_type) {
280+
data <- sf::st_cast(data)
281+
geom_type <- sf::st_geometry_type(data)
282+
}
283+
data <- remove_class(data, "sf")
277284

278-
# logic based on GeomSf$draw_key
279-
geomBasic <- switch(
280-
params$legend %||% "",
281-
point = "GeomPoint",
282-
line = "GeomPath",
283-
"GeomPolygon"
285+
basic_type <- dplyr::recode(
286+
as.character(geom_type),
287+
TRIANGLE = "GeomPolygon",
288+
TIN = "GeomPolygon",
289+
POLYHEDRALSURFACE = "GeomPolygon",
290+
SURFACE = "GeomPolygon",
291+
CURVE = "GeomPath",
292+
MULTISURFACE = "GeomPolygon",
293+
MULTICURVE = "GeomPath",
294+
CURVEPOLYGON = "GeomPolygon",
295+
COMPOUNDCURVE = "GeomPath",
296+
CIRCULARSTRING = "GeomPath",
297+
MULTIPOLYGON = "GeomPolygon",
298+
MULTILINESTRING = "GeomPath",
299+
MULTIPOINT = "GeomPoint",
300+
POLYGON = "GeomPolygon",
301+
LINESTRING = "GeomPath",
302+
POINT = "GeomPoint"
284303
)
285304

286-
# determine the type of simple feature for each row
287-
# recode the simple feature with the type of geometry used to render it
288-
prefix_class(data, c("GeomSf", geomBasic))
305+
# return a list of data frames...one for every geometry (a la, GeomSmooth)
306+
d <- split(data, basic_type)
307+
for (i in seq_along(d)) {
308+
d[[i]] <- prefix_class(
309+
fortify_sf(d[[i]]), c(names(d)[[i]], "GeomSf")
310+
)
311+
}
312+
if (length(d) == 1) d[[1]] else d
289313
}
290314

291315
#' @export
@@ -498,7 +522,7 @@ to_basic.GeomSpoke <- function(data, prestats_data, layout, params, p, ...) {
498522
#' @export
499523
to_basic.GeomCrossbar <- function(data, prestats_data, layout, params, p, ...) {
500524
# from GeomCrossbar$draw_panel()
501-
middle <- transform(data, x = xmin, xend = xmax, yend = y, size = size * params$fatten, alpha = NA)
525+
middle <- base::transform(data, x = xmin, xend = xmax, yend = y, size = size * params$fatten, alpha = NA)
502526
list(
503527
prefix_class(to_basic.GeomRect(data), "GeomCrossbar"),
504528
prefix_class(to_basic.GeomSegment(middle), "GeomCrossbar")
@@ -715,7 +739,7 @@ geom2trace.GeomBar <- function(data, params, p) {
715739

716740
#' @export
717741
geom2trace.GeomPolygon <- function(data, params, p) {
718-
742+
719743
data <- group2NA(data)
720744

721745
L <- list(
@@ -964,7 +988,7 @@ aes2plotly <- function(data, params, aes = "size") {
964988

965989
# Hack to support this geom_sf hack
966990
# https://github.com/tidyverse/ggplot2/blob/505e4bfb/R/sf.R#L179-L187
967-
defaults <- if (identical(geom, "GeomSf")) {
991+
defaults <- if (inherits(data, "GeomSf")) {
968992
type <- if (any(grepl("point", class(data)))) "point" else if (any(grepl("line", class(data)))) "line" else ""
969993
ggfun("default_aesthetics")(type)
970994
} else {

R/sf.R

Lines changed: 54 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,141 +1,91 @@
1-
# take a "tidy" sf data frame and "expand it" so every row represents a point
2-
# rather than geometry/geometries
3-
expand <- function(data) {
4-
xs <- lapply(data$geometry, get_x)
5-
ys <- lapply(data$geometry, get_y)
6-
ns <- lapply(data$geometry, get_l)
7-
ids <- lapply(ns, function(x) rep(seq_len(length(x)), x))
8-
dats <- Map(function(x, y, z, w) {
9-
data.frame(x, y, group = paste(z, w, sep = "-"), `.plotlyMergeID` = w, stringsAsFactors = FALSE)
10-
}, xs, ys, ids, seq_along(data$geometry))
11-
# merge this "expanded" geometry data back with original data
12-
ids <- seq_len(nrow(data))
13-
data[[".plotlyMergeID"]] <- ids
14-
data[["group"]] <- NULL
15-
dplyr::left_join(dplyr::bind_rows(dats), data, by = ".plotlyMergeID")
1+
fortify_sf <- function(model, ...) {
2+
# TODO:
3+
# (1) avoid converting redundant features
4+
# (2) warn/error if data already contains x/y
5+
geoms <- sf::st_geometry(sf::st_as_sf(model))
6+
xy <- lapply(geoms, st_as_plotly)
7+
ids <- rep(seq_len(nrow(model)), sapply(xy, nrow))
8+
# TODO: faster way to row bind matrices?
9+
xy_all <- cbind(do.call(rbind, xy), ids)
10+
xy_dat <- setNames(as.data.frame(xy_all), c("x", "y", sf_key()))
11+
12+
d <- as.data.frame(model)
13+
d$geometry <- NULL
14+
d[[sf_key()]] <- seq_len(nrow(d))
15+
xy_dat <- dplyr::left_join(xy_dat, d, by = sf_key())
16+
xy_dat[[sf_key()]] <- NULL
17+
xy_dat
1618
}
1719

18-
# ------------------------------------------------------------------
19-
# these helper functions are adapted from methods(st_as_grob)
20-
# see, for example, getS3method("st_as_grob", "MULTIPOLYGON")
21-
# ------------------------------------------------------------------
20+
sf_key <- function() ".sf-group-id"
2221

23-
#' Obtain x coordinates of sf geometry/geometries
24-
#'
25-
#' Exported for internal reasons. Not intended for general use.
26-
#'
27-
#' @param g an sf geometry
28-
#' @export
29-
get_x <- function(g) {
30-
UseMethod("get_x")
31-
}
32-
33-
#' Obtain y coordinates of sf geometry/geometries
34-
#'
35-
#' Exported for internal reasons. Not intended for general use.
36-
#'
37-
#' @param g an sf geometry
38-
#' @export
39-
get_y <- function(g) {
40-
UseMethod("get_y")
41-
}
42-
43-
#' Obtain number of points comprising a geometry
44-
#'
45-
#' Exported for internal reasons. Not intended for general use.
46-
#'
47-
#' @param g an sf geometry
48-
#' @export
49-
get_l <- function(g) {
50-
UseMethod("get_l")
51-
}
52-
53-
#' @export
54-
get_x.MULTIPOLYGON <- function(g) {
55-
unlist(sapply(g, function(v) sapply(v, function(z) z[, 1])))
56-
}
57-
58-
#' @export
59-
get_y.MULTIPOLYGON <- function(g) {
60-
unlist(sapply(g, function(v) sapply(v, function(z) z[, 2])))
61-
}
62-
63-
#' @export
64-
get_l.MULTIPOLYGON <- function(g) {
65-
unlist(sapply(g, function(v) sapply(v, nrow)))
66-
}
67-
68-
#' @export
69-
get_x.POLYGON <- function(g) {
70-
unlist(sapply(g, function(y) y[, 1]))
71-
}
72-
73-
#' @export
74-
get_y.POLYGON <- function(g) {
75-
unlist(sapply(g, function(y) y[, 2]))
22+
# kind of like sf_as_grob(), but maps to a plotly data structure, rather than a grob
23+
st_as_plotly <- function(x, ...) {
24+
UseMethod("st_as_plotly")
7625
}
7726

7827
#' @export
79-
get_l.POLYGON <- function(g) {
80-
sapply(g, nrow)
28+
st_as_plotly.POINT = function(x, ...) {
29+
matrix(c(x[1], x[2]), ncol = 2)
8130
}
8231

8332
#' @export
84-
get_x.MULTILINESTRING <- function(g) {
85-
unlist(sapply(g, function(y) y[, 1]))
33+
st_as_plotly.MULTIPOINT = function(x, ...) {
34+
if (nrow(x) == 0) return(empty_xy())
35+
matrix(c(x[, 1], x[, 2]), ncol = 2)
8636
}
8737

8838
#' @export
89-
get_y.MULTILINESTRING <- function(g) {
90-
unlist(sapply(g, function(y) y[, 2]))
39+
st_as_plotly.LINESTRING = function(x, ...) {
40+
if (nrow(x) == 0) return(empty_xy())
41+
matrix(c(x[, 1], x[, 2]), ncol = 2)
9142
}
9243

9344
#' @export
94-
get_l.MULTILINESTRING <- function(g) {
95-
sapply(g, nrow)
45+
st_as_plotly.CIRCULARSTRING = function(x, y, ...) {
46+
st_as_plotly(st_cast(x, "LINESTRING"), ...)
9647
}
9748

9849
#' @export
99-
get_x.LINESTRING <- function(g) {
100-
g[, 1]
50+
st_as_plotly.MULTILINESTRING = function(x, ...) {
51+
if (length(x) == 0) return(empty_xy())
52+
xvals <- unlist(rbind(sapply(x, function(y) y[,1]), NA))
53+
yvals <- unlist(rbind(sapply(x, function(y) y[,2]), NA))
54+
matrix(c(xvals, yvals), ncol = 2)
10155
}
10256

10357
#' @export
104-
get_y.LINESTRING <- function(g) {
105-
g[, 2]
58+
st_as_plotly.POLYGON = function(x, ..., rule = "evenodd") {
59+
if (length(x) == 0) return(empty_xy())
60+
xvals <- unlist(rbind(sapply(x, function(y) y[,1]), NA))
61+
yvals <- unlist(rbind(sapply(x, function(y) y[,2]), NA))
62+
matrix(c(xvals, yvals), ncol = 2)
10663
}
10764

10865
#' @export
109-
get_l.LINESTRING <- function(g) {
110-
nrow(g)
66+
st_as_plotly.MULTIPOLYGON = function(x, ..., rule = "evenodd") {
67+
if (length(x) == 0) return(empty_xy())
68+
xvals <- unlist(sapply(x, function(y) rbind(sapply(y, function(z) z[,1]), NA)))
69+
yvals <- unlist(sapply(x, function(y) rbind(sapply(y, function(z) z[,2]), NA)))
70+
matrix(c(xvals, yvals), ncol = 2)
11171
}
11272

11373
#' @export
114-
get_x.MULTIPOINT <- function(g) {
115-
g[, 1]
74+
st_as_plotly.GEOMETRYCOLLECTION = function(x, ...) {
75+
if (length(x) == 0) return(empty_xy())
76+
lapply(x, st_as_plotly)
11677
}
11778

11879
#' @export
119-
get_y.MULTIPOINT <- function(g) {
120-
g[, 2]
121-
}
80+
st_as_plotly.MULTISURFACE = st_as_plotly.GEOMETRYCOLLECTION
12281

12382
#' @export
124-
get_l.MULTIPOINT <- function(g) {
125-
nrow(g)
126-
}
83+
st_as_plotly.CURVEPOLYGON = st_as_plotly.GEOMETRYCOLLECTION
12784

12885
#' @export
129-
get_x.POINT <- function(g) {
130-
g[1]
131-
}
86+
st_as_plotly.COMPOUNDCURVE = st_as_plotly.GEOMETRYCOLLECTION
13287

133-
#' @export
134-
get_y.POINT <- function(g) {
135-
g[2]
136-
}
13788

138-
#' @export
139-
get_l.POINT <- function(g) {
140-
nrow(g)
89+
empty_xy <- function() {
90+
matrix(rep(NA, 2), ncol = 2)
14191
}

0 commit comments

Comments
 (0)