Skip to content

Commit 7316914

Browse files
committed
also consider layer/stat specific aes mappings
1 parent 563d120 commit 7316914

File tree

4 files changed

+79
-53
lines changed

4 files changed

+79
-53
lines changed

R/ggplotly.R

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -173,43 +173,61 @@ gg2list <- function(p, width = NULL, height = NULL, mapping = "all", source = "A
173173
panel$layout$x_max <- sapply(panel$ranges, function(z) max(z$x.range))
174174
panel$layout$y_min <- sapply(panel$ranges, function(z) min(z$y.range))
175175
panel$layout$y_max <- sapply(panel$ranges, function(z) max(z$y.range))
176-
177-
# use aes mappings for the tooltip default
178-
aesMap <- as.character(p$mapping)
179-
if (!identical(mapping, "all")) {
180-
aesMap <- aesMap[names(aesMap) %in% mapping]
181-
aesMap <- aesMap[mapping]
182-
}
183-
# tooltips for discrete positional scales are misleading
184-
for (xy in c("x", "y")) {
185-
if (scales$get_scales(xy)$is_discrete()) {
186-
aesMap <- aesMap[names(aesMap) != xy]
176+
177+
# --------------------------------------------------------------------
178+
# Use aes mappings for sensible tooltips
179+
# --------------------------------------------------------------------
180+
181+
aesMap <- lapply(p$layers, function(x) {
182+
map <- c(
183+
# plot level aes mappings
184+
as.character(p$mapping),
185+
# layer level mappings
186+
as.character(x$mapping),
187+
# stat specific mappings
188+
as.character(x$stat$default_aes)
189+
)
190+
# remove leading/trailing dots in "hidden" stat aes
191+
map <- sub("^\\.\\.", "", sub("\\.\\.$", "", map))
192+
# TODO: allow users to specify a _list_ of mappings?
193+
if (!identical(mapping, "all")) {
194+
map <- map[names(map) %in% mapping]
187195
}
188-
}
196+
# tooltips for discrete positional scales are misleading
197+
if (scales$get_scales("x")$is_discrete()) {
198+
map <- map[!names(map) %in% "x"]
199+
}
200+
if (scales$get_scales("y")$is_discrete()) {
201+
map <- map[!names(map) %in% "y"]
202+
}
203+
map
204+
})
189205

190-
for (i in seq_along(aesMap)) {
191-
aesName <- names(aesMap)[[i]]
192-
# TODO: should we be getting the name from scale_*(name) first?
193-
varName <- aesMap[[i]]
194-
# by default assume the values don't need any formatting
195-
forMat <- function(x) if (is.numeric(x)) round(x, 2) else x
196-
if (aesName %in% c("x", "y")) {
197-
scaleName <- scales$get_scales(aesName)$scale_name
198-
# convert "milliseconds from the UNIX epoch" back to a date/datetime
199-
# http://stackoverflow.com/questions/13456241/convert-unix-epoch-to-date-object-in-r
200-
if ("date" %in% scaleName) forMat <- function(x) as.Date(as.POSIXct(x / 1000, origin = "1970-01-01"))
201-
if ("datetime" %in% scaleName) forMat <- function(x) as.POSIXct(x / 1000, origin = "1970-01-01")
202-
} else {
203-
if (aesName != "text") aesName <- paste0(aesName, "_plotlyDomain")
206+
# attach a new column (hovertext) to each layer of data that should get mapped
207+
# to the text trace property
208+
data <- Map(function(x, y) {
209+
for (i in seq_along(y)) {
210+
aesName <- names(y)[[i]]
211+
# TODO: should we be getting the name from scale_*(name) first?
212+
varName <- y[[i]]
213+
# by default assume the values don't need any formatting
214+
forMat <- function(x) if (is.numeric(x)) round(x, 2) else x
215+
if (aesName %in% c("x", "y")) {
216+
scaleName <- scales$get_scales(aesName)$scale_name
217+
# convert "milliseconds from the UNIX epoch" back to a date/datetime
218+
# http://stackoverflow.com/questions/13456241/convert-unix-epoch-to-date-object-in-r
219+
if ("date" %in% scaleName) forMat <- function(x) as.Date(as.POSIXct(x / 1000, origin = "1970-01-01"))
220+
if ("datetime" %in% scaleName) forMat <- function(x) as.POSIXct(x / 1000, origin = "1970-01-01")
221+
} else {
222+
if (aesName != "text") aesName <- paste0(aesName, "_plotlyDomain")
223+
}
224+
# add a line break if hovertext already exists
225+
if ("hovertext" %in% names(x)) x$hovertext <- paste0(x$hovertext, "<br>")
226+
x$hovertext <- paste0(x$hovertext, varName, ": ", forMat(x[[aesName]]))
204227
}
205-
206-
data <- lapply(data, function(d) {
207-
if (!is.null(d$hovertext)) d$hovertext <- paste0(d$hovertext, "<br>")
208-
d$hovertext <- paste0(d$hovertext, varName, ": ", forMat(d[[aesName]]))
209-
d
210-
})
211-
}
212-
228+
x
229+
}, data, aesMap)
230+
213231
# layers -> plotly.js traces
214232
traces <- layers2traces(
215233
data, prestats_data, layers, panel$layout, scales, p$labels

R/layers2traces.R

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -202,19 +202,16 @@ to_basic.GeomRect <- function(data, prestats_data, layout, params, ...) {
202202

203203
#' @export
204204
to_basic.GeomRaster <- function(data, prestats_data, layout, params, ...) {
205-
data$z <- prestats_data$fill
206-
if (is.discrete(prestats_data$fill)) {
207-
data <- prefix_class(data, "GeomRect")
208-
to_basic(data, prestats_data, layout, params)
209-
} else {
210-
prefix_class(data, "GeomTile")
211-
}
205+
data <- prefix_class(data, "GeomTile")
206+
to_basic(data, prestats_data, layout, params)
212207
}
213208

214209
#' @export
215210
to_basic.GeomTile <- function(data, prestats_data, layout, params, ...) {
216-
data$z <- prestats_data$fill
217-
if (is.discrete(prestats_data$fill)) {
211+
# geom2trace.GeomTile is a heatmap, which requires continuous fill and
212+
# a complete grid
213+
g <- expand.grid(unique(data$x), unique(data$y))
214+
if (nrow(g) != nrow(data) || is.discrete(prestats_data$fill)) {
218215
data <- prefix_class(data, "GeomRect")
219216
to_basic(data, prestats_data, layout, params)
220217
} else {
@@ -416,14 +413,10 @@ geom2trace.GeomBar <- function(data, params) {
416413
#' @export
417414
geom2trace.GeomPolygon <- function(data, params) {
418415
data <- group2NA(data)
419-
# TODO: do this for more density-like measures??
420-
if ("level" %in% names(data)) {
421-
data$level <- paste("Level:", data$level)
422-
}
423416
L <- list(
424417
x = data$x,
425418
y = data$y,
426-
text = data$hovertext %||% data$level,
419+
text = data$hovertext,
427420
type = "scatter",
428421
mode = "lines",
429422
line = list(
@@ -501,16 +494,24 @@ geom2trace.GeomTile <- function(data, params) {
501494
data <- data[order(data$x, order(data$y, decreasing = T)), ]
502495
x <- sort(unique(data$x))
503496
y <- sort(unique(data$y))
497+
fill <- data$fill_plotlyDomain
504498
colorscale <- cbind(
505499
c(0, 1),
506-
data[c(which.min(data$z), which.max(data$z)), "fill"]
500+
data[c(which.min(fill), which.max(fill)), "fill"]
507501
)
508502
list(
509503
x = x,
510504
y = y,
511-
text = matrix(data$z, nrow = length(y), ncol = length(x)),
512-
hoverinfo = "text",
513-
z = matrix(scales::rescale(data$z), nrow = length(y), ncol = length(x)),
505+
z = matrix(
506+
scales::rescale(fill),
507+
nrow = length(y),
508+
ncol = length(x)
509+
),
510+
text = matrix(
511+
data$hovertext,
512+
nrow = length(y),
513+
ncol = length(x)
514+
),
514515
colorscale = colorscale,
515516
type = "heatmap",
516517
showscale = FALSE,

man/gg2list.Rd

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

tests/testthat/test-ggplot-heatmap.R

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,11 @@ test_that("geom_tile is translated to type=heatmap", {
2020
expect_identical(L$data[[1]]$type, "heatmap")
2121
expect_identical(L$layout$xaxis$ticktext, wdays)
2222
expect_identical(L$layout$yaxis$ticktext, dtimes)
23+
# show bin value on hover (but without x/y since they are discrete)
24+
expect_true(
25+
L$data[[1]]$hoverinfo == "text"
26+
)
27+
expect_true(
28+
all(grepl("^value: [-]?[0-9]+$", c(L$data[[1]]$text)))
29+
)
2330
})

0 commit comments

Comments
 (0)