Skip to content

Commit 81b27ab

Browse files
committed
add back ability to reference non-existant axis-like objects in trace anchors
1 parent 78b779b commit 81b27ab

File tree

4 files changed

+102
-4
lines changed

4 files changed

+102
-4
lines changed

R/coord.R

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#' *** This won't be possible until plotly.js implements aspect ratios... ***
2+
#'
3+
#' #' Force the aspect ratio according to x and y scales
4+
#' #'
5+
#' #' When x and y are numeric variables measured on the same scale,
6+
#' #' or are related in some meaningful way, forcing the aspect ratio of the
7+
#' #' plot to be proportional to the ratio of a unit change in x versus y improves
8+
#' #' our ability to correctly perceive the data.
9+
#' #'
10+
#' #' @param p a plotly object
11+
#' #' @param ratio aspect ratio, expressed as y / x
12+
#' #' @export
13+
#' #' @examples
14+
#' #'
15+
#' #' canada <- map_data("world", "canada")
16+
#' #'
17+
#' #' canada %>%
18+
#' #' group_by(group) %>%
19+
#' #' plot_ly(x = ~long, y = ~lat, alpha = 0.2) %>%
20+
#' #' add_polygons(hoverinfo = "none", color = I("black")) %>%
21+
#' #' coord_fix()
22+
#' #'
23+
#' #' # works on (non-faceted) ggplot2 plots, too
24+
#' #' gg <- ggplot(canada, aes(long, lat, group = group)) +
25+
#' #' geom_polygon() + coord_fixed()
26+
#' #'
27+
#' #' gg %>%
28+
#' #' ggplotly() %>%
29+
#' #' coord_fix()
30+
#' #'
31+
#'
32+
#' coord_fix <- function(p, ratio = 1) {
33+
#' p <- plotly_build(p)
34+
#' # this won't work for subplots, or categorical data
35+
#' x <- grepl("^xaxis", names(p$x$layout))
36+
#' y <- grepl("^yaxis", names(p$x$layout))
37+
#' if (sum(x) > 1 || sum(y) > 1) {
38+
#' stop("Can not impose aspect ratio a plot with more than one x/y axis", call. = FALSE)
39+
#' }
40+
#' xDat <- unlist(lapply(p$x$data, "[[", "x"))
41+
#' yDat <- unlist(lapply(p$x$data, "[[", "y"))
42+
#' if (!is.numeric(xDat) || !is.numeric(yDat)) {
43+
#' stop("Must have numeric data on both x and y axes to enforce aspect ratios", call. = FALSE)
44+
#' }
45+
#'
46+
#' # warn about any pre-populated domains, they will get squashed
47+
#' xDom <- p$x$layout[["xaxis"]]$domain %||% c(0, 1)
48+
#' yDom <- p$x$layout[["yaxis"]]$domain %||% c(0, 1)
49+
#' if (!identical(yDom, c(0, 1)) || !identical(xDom, c(0, 1))) {
50+
#' warning(
51+
#' "coord_fix() won't respect prespecified axis domains (other than the default)",
52+
#' call. = FALSE
53+
#' )
54+
#' }
55+
#'
56+
#' xRng <- range(xDat, na.rm = TRUE)
57+
#' yRng <- range(yDat, na.rm = TRUE)
58+
#' asp <- ratio * diff(yRng) / diff(xRng)
59+
#' if (asp < 1) {
60+
#' p$x$layout[["yaxis"]]$domain <- c(0 + asp / 2, 1 - asp / 2)
61+
#' } else {
62+
#' asp <- 1 / asp
63+
#' p$x$layout[["xaxis"]]$domain <- c(0 + asp / 2, 1 - asp / 2)
64+
#' }
65+
#' p
66+
#' }

R/subplots.R

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,40 @@ subplot <- function(..., nrows = 1, widths = NULL, heights = NULL, margin = 0.02
5050
dotz <- dotz[[1]]
5151
}
5252
# build each plot
53-
plots <- lapply(dotz, function(d) plotly_build(d)[["x"]])
53+
plotz <- lapply(dotz, function(d) plotly_build(d)[["x"]])
54+
55+
# Are any traces referencing "axislike" layout attributes that are missing?
56+
# If so, move those traces to a "new plot", and inherit layout attributes,
57+
# which makes this sort of thing possible:
58+
# https://plot.ly/r/map-subplots-and-small-multiples/
59+
plots <- list()
60+
for (i in seq_along(plotz)) {
61+
p <- plots[[i]] <- plotz[[i]]
62+
layoutAttrs <- c(names(p$layout), c("mapbox", "geo", "xaxis", "yaxis"))
63+
xTraceAttrs <- sub("^x", "xaxis", sapply(p$data, function(tr) tr[["subplot"]] %||% tr[["geo"]] %||% tr[["xaxis"]]))
64+
yTraceAttrs <- sub("^y", "yaxis", sapply(p$data, function(tr) tr[["subplot"]] %||% tr[["geo"]] %||% tr[["yaxis"]]))
65+
missingAttrs <- setdiff(c(xTraceAttrs, yTraceAttrs), layoutAttrs)
66+
# move to next iteration if trace references are complete
67+
if (!length(missingAttrs)) next
68+
# remove each "missing" trace from this plot
69+
missingTraces <- xTraceAttrs %in% missingAttrs | yTraceAttrs %in% missingAttrs
70+
plots[[i]]$data[missingTraces] <- NULL
71+
# move traces with "similar missingness" to a new plot
72+
for (j in missingAttrs) {
73+
newPlot <- list(
74+
data = p$data[xTraceAttrs %in% j | yTraceAttrs %in% j],
75+
layout = p$layout
76+
)
77+
# reset the anchors
78+
newPlot$data <- lapply(newPlot$data, function(tr) {
79+
for (k in c("mapbox", "geo", "xaxis", "yaxis")) {
80+
tr[[k]] <- sub("[0-9]+", "", tr[[k]]) %||% NULL
81+
}
82+
tr
83+
})
84+
plots <- c(plots, list(newPlot))
85+
}
86+
}
5487

5588
# grab main plot objects
5689
traces <- lapply(plots, "[[", "data")

R/utils.R

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,7 @@ supply_defaults <- function(p) {
109109
p$x$data <- lapply(p$x$data, function(tr) {
110110
for (i in seq_along(anchors)) {
111111
nm <- names(anchors)[[i]]
112-
113-
tr[[nm]] <- unique(tr[[nm]]) %||% anchors[[i]]
112+
tr[[nm]] <- sub("^y1$", "y", sub("^x1$", "x", tr[[nm]][1])) %||% anchors[[i]]
114113
}
115114
tr
116115
})

tests/testthat/test-plotly-subplot.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ s <- subplot(
8383
)
8484

8585
test_that("Row/column height/width", {
86-
l <- expect_traces(s, 4, "width-height")
86+
l <- expect_traces(s, 3, "width-height")
8787
expect_equal(diff(l$layout$xaxis$domain), 0.8 - 0.005)
8888
expect_equal(diff(l$layout$xaxis2$domain), 0.2 - 0.005)
8989
expect_equal(diff(l$layout$yaxis$domain), 0.2 - 0.005)

0 commit comments

Comments
 (0)