@@ -17,86 +17,61 @@ layers2traces <- function(data, prestats_data, layers, layout, scales, labels) {
1717 # This may involve preprocessing the data, for example:
1818 # 1. geom_line() is really geom_path() with data sorted by x
1919 # 2. geom_smooth() is really geom_path() + geom_ribbon()
20- #
21- # This has to be done in a loop, since some layers are really two layers,
22- # (and we need to replicate the data/params in those cases)
2320 datz <- list ()
2421 paramz <- list ()
25- keyz <- list ()
2622 for (i in seq_along(data )) {
23+ # This has to be done in a loop, since some layers are really two layers,
24+ # (and we need to replicate the data/params in those cases)
2725 d <- to_basic(data [[i ]], prestats_data [[i ]], layout , params [[i ]])
2826 if (is.data.frame(d )) d <- list (d )
2927 for (j in seq_along(d )) {
3028 datz <- c(datz , d [j ])
31- paramz <- c(paramz , params [i ])
32- # When splitting layers into multiple traces, we need the domain/range of
33- # the scale (for trace naming & legend generation).
34- # if the splitting variables are constant in the data, we don't want to
35- # split on them
36- idx <- vapply(d [[j ]], function (x ) length(unique(x )) > 1 , logical (1 ))
37- # always split on PANEL, discrete scales, and other geom specific aes that
38- # don't translate to a single trace
39- split_by <- c(" PANEL" , names(discreteScales )[names(discreteScales ) %in% names(idx )[idx ]])
40- psd <- prestats_data [[i ]]
41- key <- unique(psd [names(psd ) %in% split_by ])
42- # this order (should) determine the ordering of traces (within layer)
43- key <- key [do.call(order , key ), , drop = FALSE ]
44- split_vars <- setdiff(names(key ), " PANEL" )
45- for (k in split_vars ) {
46- key [[paste0(k , " _domain" )]] <- key [, k ]
47- key [[k ]] <- scales $ get_scales(k )$ map(key [, k ])
48- }
49- keyz <- c(keyz , list (key ))
29+ paramz <- c(paramz , params [j ])
5030 }
5131 }
5232
5333 # now to the actual layer -> trace conversion
5434 trace.list <- list ()
5535 for (i in seq_along(datz )) {
5636 d <- datz [[i ]]
57- # create a factor to split the data on...
58- # by matching the factor levels with the order of the domain (of _discrete_
59- # scales), the trace ordering should be correct
60- key <- keyz [[i ]]
61- split_by <- names(key )[! grepl(" _domain$" , names(key ))]
37+ # always split on discrete scales, and other geom specific aesthetics that
38+ # can't translate to a single trace
39+ split_by <- c(split_on(d ), names(discreteScales ))
40+ # always split on PANEL and domain values (for trace ordering)
41+ split_by <- c(" PANEL" , paste0(split_by , " _plotlyDomain" ))
42+ # split "this layers" data into a list of data frames
43+ idx <- names(d ) %in% split_by
44+ # ensure the factor level orders (which determies traces order)
45+ # matches the order of the domain values
46+ lvls <- unique(d [idx ])
47+ lvls <- lvls [do.call(order , lvls ), , drop = FALSE ]
6248 fac <- factor (
63- apply(d [split_by ], 1 , paste , collapse = " . " ),
64- levels = apply(key [ split_by ] , 1 , paste , collapse = " . " )
49+ apply(d [idx ], 1 , paste , collapse = " @%& " ),
50+ levels = apply(lvls , 1 , paste , collapse = " @%& " )
6551 )
66- # if we split on a variable not in the key, we have no chance
67- # of generating an appropriate legend
68- splitContinuous <- length(setdiff(split_on(d ), split_by )) > 0
69- if (splitContinuous ) {
70- split_by <- c(split_by , split_on(d ))
71- splitDat <- d [names(d ) %in% split_by ]
72- fac <- factor (
73- apply(splitDat , 1 , paste , collapse = " ." ),
74- levels = apply(unique(splitDat ), 1 , paste , collapse = " ." )
75- )
76- }
7752 dl <- split(d , fac , drop = TRUE )
7853 # list of traces for this layer
7954 trs <- Map(geom2trace , dl , paramz [i ])
80- # set name/legendgroup/showlegend, if appropriate
81- legendVars <- setdiff(split_by , " PANEL" )
82- if (! splitContinuous && length(legendVars ) > 0 && length(trs ) > 1 ) {
83- # labels is a list of legend titles, but since we're restricted to
84- # one (merged) legend, I think it only makes since to prefix the variable
85- # name in the legend entries
86- lab <- labels [legendVars ]
87- vals <- key [paste0(legendVars , " _domain" )]
88- valz <- Map(function (x , y ) {
89- if (nchar(x ) > 0 ) paste0(x , " : " , y ) else y
90- }, lab , vals )
91- entries <- Reduce(function (x , y ) {
92- if (identical(x , y )) x else paste0(x , " <br>" , y )
93- }, valz )
94- for (k in seq_along(trs )) {
95- trs [[k ]]$ name <- entries [[k ]]
96- trs [[k ]]$ legendgroup <- entries [[k ]]
97- # depending on the geom (e.g. smooth) this may be FALSE already
98- if (is.null(trs [[k ]]$ showlegend )) trs [[k ]]$ showlegend <- TRUE
55+ # are we splitting by a discrete scale on this layer?
56+ # if so, set name/legendgroup/showlegend
57+ isDiscrete <- names(d ) %in% paste0(names(discreteScales ), " _plotlyDomain" )
58+ if (length(trs ) > 1 && sum(isDiscrete ) > = 1 ) {
59+ nms <- names(trs )
60+ # ignore "non-discrete" scales that we've split on
61+ for (w in seq_len(sum(names(d ) %in% c(" PANEL" , split_on(d ))))) {
62+ nms <- sub(" ^[^@%&]@%&" , " " , nms )
9963 }
64+ nms <- strsplit(nms , " @%&" )
65+ nms <- vapply(nms , function (x ) {
66+ if (length(x ) > 1 ) paste0(" (" , paste0(x , collapse = " ," ), " )" ) else x
67+ }, character (1 ))
68+ trs <- Map(function (x , y ) {
69+ x $ name <- y
70+ x $ legendgroup <- y
71+ # depending on the geom (e.g. smooth) this may be FALSE already
72+ x $ showlegend <- x $ showlegend %|| % TRUE
73+ x
74+ }, trs , nms )
10075 } else {
10176 trs <- lapply(trs , function (x ) { x $ showlegend <- FALSE ; x })
10277 }
@@ -111,10 +86,8 @@ layers2traces <- function(data, prestats_data, layers, layout, scales, labels) {
11186 if (inherits(d , " GeomBar" ) && paramz [[i ]]$ position == " identity" ) {
11287 trs <- rev(trs )
11388 }
114-
11589 trace.list <- c(trace.list , trs )
11690 }
117-
11891 trace.list
11992}
12093
@@ -138,24 +111,29 @@ to_basic <- function(data, prestats_data, layout, params, ...) {
138111
139112# ' @export
140113to_basic.GeomViolin <- function (data , prestats_data , layout , params , ... ) {
141- # TODO: it should be possible to implement this via GeomPolygon
142- # just need preprocess the data, then:
143- # replace_class(data, "GeomPolygon", "GeomViolin")
144- warning(
145- " plotly.js does not yet support violin plots. \n " ,
146- " Converting to boxplot instead." ,
147- call. = FALSE
114+ n <- nrow(data )
115+ revData <- data [order(data $ y , decreasing = TRUE ), ]
116+ idx <- ! names(data ) %in% c(" x" , " xmin" , " xmax" )
117+ data <- rbind(
118+ cbind(x = data $ x - data $ violinwidth / 2 , data [, idx ]),
119+ cbind(x = revData $ x + revData $ violinwidth / 2 , revData [, idx ])
148120 )
149- to_basic.GeomBoxplot(data , prestats_data )
121+ if (! is.null(data $ hovertext )) data $ hovertext <- paste0(data $ hovertext , " <br>" )
122+ data $ hovertext <- paste0(data $ hovertext , " density: " , round(data $ density , 3 ))
123+ prefix_class(data , c(" GeomPolygon" , " GeomViolin" ))
150124}
151125
152126# ' @export
153127to_basic.GeomBoxplot <- function (data , prestats_data , layout , params , ... ) {
154- # 'trained' aesthetics that we're interested in mapping from data to prestats
155- aez <- c(" fill" , " colour" , " size" , " alpha" , " linetype" , " shape" , " x" )
156- dat <- data [names(data ) %in% c(aez , " group" )]
157- pre <- prestats_data [! names(prestats_data ) %in% aez ]
158- prefix_class(merge(pre , dat , by = " group" , sort = FALSE ), " GeomBoxplot" )
128+ aez <- names(GeomBoxplot $ default_aes )
129+ for (i in aez ) {
130+ prestats_data [[i ]] <- NULL
131+ }
132+ vars <- c(" PANEL" , " group" , aez , grep(" _plotlyDomain$" , names(data ), value = T ))
133+ prefix_class(
134+ merge(prestats_data , data [vars ], by = c(" PANEL" , " group" ), sort = FALSE ),
135+ " GeomBoxplot"
136+ )
159137}
160138
161139# ' @export
@@ -224,20 +202,16 @@ to_basic.GeomRect <- function(data, prestats_data, layout, params, ...) {
224202
225203# ' @export
226204to_basic.GeomRaster <- function (data , prestats_data , layout , params , ... ) {
227- # TODO: what if nrow(data) != nrow(prestats_data)?
228- data $ z <- prestats_data $ fill
229- if (is.discrete(prestats_data $ fill )) {
230- data <- prefix_class(data , " GeomRect" )
231- to_basic(data , prestats_data , layout , params )
232- } else {
233- prefix_class(data , " GeomTile" )
234- }
205+ data <- prefix_class(data , " GeomTile" )
206+ to_basic(data , prestats_data , layout , params )
235207}
236208
237209# ' @export
238210to_basic.GeomTile <- function (data , prestats_data , layout , params , ... ) {
239- data $ z <- prestats_data $ fill
240- 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 )) {
241215 data <- prefix_class(data , " GeomRect" )
242216 to_basic(data , prestats_data , layout , params )
243217 } else {
@@ -259,8 +233,10 @@ to_basic.GeomDensity2d <- function(data, prestats_data, layout, params, ...) {
259233
260234# ' @export
261235to_basic.GeomAbline <- function (data , prestats_data , layout , params , ... ) {
262- data <- unique(data [c(" PANEL" , " intercept" , " slope" , " group" )])
263- data $ group <- seq_len(nrow(data ))
236+ # ugh, we can't trust the group here
237+ data $ group <- interaction(
238+ data [! grepl(" group" , names(data )) & ! vapply(data , anyNA , logical (1 ))]
239+ )
264240 lay <- tidyr :: gather_(layout , " variable" , " x" , c(" x_min" , " x_max" ))
265241 data <- merge(lay [c(" PANEL" , " x" )], data , by = " PANEL" )
266242 data $ y <- with(data , intercept + slope * x )
@@ -269,8 +245,10 @@ to_basic.GeomAbline <- function(data, prestats_data, layout, params, ...) {
269245
270246# ' @export
271247to_basic.GeomHline <- function (data , prestats_data , layout , params , ... ) {
272- data <- unique(data [c(" PANEL" , " yintercept" , " group" )])
273- data $ group <- seq_len(nrow(data ))
248+ # ugh, we can't trust the group here
249+ data $ group <- interaction(
250+ data [! grepl(" group" , names(data )) & ! vapply(data , anyNA , logical (1 ))]
251+ )
274252 lay <- tidyr :: gather_(layout , " variable" , " x" , c(" x_min" , " x_max" ))
275253 data <- merge(lay [c(" PANEL" , " x" )], data , by = " PANEL" )
276254 data $ y <- data $ yintercept
@@ -279,8 +257,10 @@ to_basic.GeomHline <- function(data, prestats_data, layout, params, ...) {
279257
280258# ' @export
281259to_basic.GeomVline <- function (data , prestats_data , layout , params , ... ) {
282- data <- unique(data [c(" PANEL" , " xintercept" , " group" )])
283- data $ group <- seq_len(nrow(data ))
260+ # ugh, we can't trust the group here
261+ data $ group <- interaction(
262+ data [! grepl(" group" , names(data )) & ! vapply(data , anyNA , logical (1 ))]
263+ )
284264 lay <- tidyr :: gather_(layout , " variable" , " y" , c(" y_min" , " y_max" ))
285265 data <- merge(lay [c(" PANEL" , " y" )], data , by = " PANEL" )
286266 data $ x <- data $ xintercept
@@ -357,7 +337,8 @@ geom2trace.GeomPath <- function(data, params) {
357337 L <- list (
358338 x = data $ x ,
359339 y = data $ y ,
360- text = data $ text ,
340+ text = data $ hovertext ,
341+ hoverinfo = " text" ,
361342 type = " scatter" ,
362343 mode = " lines" ,
363344 name = if (inherits(data , " GeomSmooth" )) " fitted values" ,
@@ -378,13 +359,10 @@ geom2trace.GeomPath <- function(data, params) {
378359# ' @export
379360geom2trace.GeomPoint <- function (data , params ) {
380361 shape <- aes2plotly(data , params , " shape" )
381- if (length(unique(data $ size )) > 1 && is.null(data $ text )) {
382- data $ text <- paste(" size:" , data $ size )
383- }
384362 L <- list (
385363 x = data $ x ,
386364 y = data $ y ,
387- text = data $ text ,
365+ text = data $ hovertext ,
388366 key = data $ key ,
389367 type = " scatter" ,
390368 mode = " markers" ,
@@ -416,7 +394,7 @@ geom2trace.GeomBar <- function(data, params) {
416394 list (
417395 x = data $ x ,
418396 y = data $ y ,
419- text = data $ text ,
397+ text = data $ hovertext ,
420398 type = " bar" ,
421399 marker = list (
422400 autocolorscale = FALSE ,
@@ -435,14 +413,10 @@ geom2trace.GeomBar <- function(data, params) {
435413# ' @export
436414geom2trace.GeomPolygon <- function (data , params ) {
437415 data <- group2NA(data )
438- # TODO: do this for more density-like measures??
439- if (" level" %in% names(data )) {
440- data $ level <- paste(" Level:" , data $ level )
441- }
442416 L <- list (
443417 x = data $ x ,
444418 y = data $ y ,
445- text = data $ text % || % data $ level ,
419+ text = data $ hovertext ,
446420 type = " scatter" ,
447421 mode = " lines" ,
448422 line = list (
@@ -471,6 +445,7 @@ geom2trace.GeomBoxplot <- function(data, params) {
471445 x = data $ x ,
472446 y = data $ y ,
473447 type = " box" ,
448+ hoverinfo = " y" ,
474449 fillcolor = toRGB(
475450 aes2plotly(data , params , " fill" ),
476451 aes2plotly(data , params , " alpha" )
@@ -519,16 +494,24 @@ geom2trace.GeomTile <- function(data, params) {
519494 data <- data [order(data $ x , order(data $ y , decreasing = T )), ]
520495 x <- sort(unique(data $ x ))
521496 y <- sort(unique(data $ y ))
497+ fill <- data $ fill_plotlyDomain
522498 colorscale <- cbind(
523499 c(0 , 1 ),
524- data [c(which.min(data $ z ), which.max(data $ z )), " fill" ]
500+ data [c(which.min(fill ), which.max(fill )), " fill" ]
525501 )
526502 list (
527503 x = x ,
528504 y = y ,
529- text = matrix (data $ z , nrow = length(y ), ncol = length(x )),
530- hoverinfo = " text" ,
531- 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+ ),
532515 colorscale = colorscale ,
533516 type = " heatmap" ,
534517 showscale = FALSE ,
0 commit comments