1
1
# layer -> trace conversion
2
2
layers2traces <- function (data , prestats_data , layout , p ) {
3
- # Attach a "geom class" to each layer of data for method dispatch
3
+ # Attach a "geom class" to each layer of data for method dispatch
4
4
data <- Map(function (x , y ) prefix_class(x , class(y $ geom )[1 ]), data , p $ layers )
5
5
# Extract parameters for each layer
6
6
params <- lapply(p $ layers , function (x ) {
@@ -20,7 +20,7 @@ layers2traces <- function(data, prestats_data, layout, p) {
20
20
datz <- list ()
21
21
paramz <- list ()
22
22
for (i in seq_along(data )) {
23
- # This has to be done in a loop, since some layers are really two layers,
23
+ # This has to be done in a loop, since some layers are really two layers,
24
24
# (and we need to replicate the data/params in those cases)
25
25
d <- to_basic(data [[i ]], prestats_data [[i ]], layout , params [[i ]], p )
26
26
if (is.data.frame(d )) d <- list (d )
@@ -29,54 +29,47 @@ layers2traces <- function(data, prestats_data, layout, p) {
29
29
paramz <- c(paramz , params [j ])
30
30
}
31
31
}
32
-
32
+
33
33
# now to the actual layer -> trace conversion
34
34
trace.list <- list ()
35
35
for (i in seq_along(datz )) {
36
36
d <- datz [[i ]]
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)
37
+ # variables that produce multiple traces and deserve their own legend entries
38
+ split_legend <- paste0(names(discreteScales ), " _plotlyDomain" )
39
+ # add variable that produce multiple traces, but do _not_ deserve entries
40
+ split_by <- c(split_legend , " PANEL" , split_on(d ))
41
+ # ensure the factor level orders (which determines traces order)
45
42
# matches the order of the domain values
46
- lvls <- unique(d [idx ])
43
+ split_vars <- intersect(split_by , names(d ))
44
+ lvls <- unique(d [split_vars ])
47
45
lvls <- lvls [do.call(order , lvls ), , drop = FALSE ]
46
+ separator <- new_id()
48
47
fac <- factor (
49
- apply(d [idx ], 1 , paste , collapse = " @%& " ),
50
- levels = apply(lvls , 1 , paste , collapse = " @%& " )
48
+ apply(d [split_vars ], 1 , paste , collapse = separator ),
49
+ levels = apply(lvls , 1 , paste , collapse = separator )
51
50
)
52
51
if (all(is.na(fac ))) fac <- 1
53
52
dl <- split(d , fac , drop = TRUE )
54
53
# list of traces for this layer
55
54
trs <- Map(geom2trace , dl , paramz [i ], list (p ))
56
- # are we splitting by a discrete scale on this layer?
57
- # if so, set name/legendgroup/showlegend
58
- isDiscrete <- names(d ) %in% paste0(names(discreteScales ), " _plotlyDomain" )
59
- if (length(trs ) > 1 && sum(isDiscrete ) > = 1 ) {
60
- nms <- names(trs )
61
- # ignore "non-discrete" scales that we've split on
62
- for (w in seq_len(sum(names(d ) %in% c(" PANEL" , split_on(d ))))) {
63
- nms <- sub(" ^[^@%&]@%&" , " " , nms )
64
- }
65
- nms <- strsplit(nms , " @%&" )
55
+ # if we need a legend, set name/legendgroup/showlegend
56
+ # note: this allows us to control multiple traces from one legend entry
57
+ if (any(split_legend %in% names(d ))) {
58
+ nms <- strsplit(names(trs ), separator , fixed = TRUE )
66
59
nms <- vapply(nms , function (x ) {
67
- if (length( x ) > 1 ) paste0( " ( " , paste0( x , collapse = " ," ), " )" ) else x
60
+ paste(unique( x [seq_along( split_legend )]), collapse = " , " )
68
61
}, character (1 ))
69
62
trs <- Map(function (x , y ) {
70
63
x $ name <- y
71
64
x $ legendgroup <- y
72
- # depending on the geom (e.g. smooth) this may be FALSE already
65
+ # depending on the geom (e.g. smooth) this may be FALSE already
73
66
x $ showlegend <- x $ showlegend %|| % TRUE
74
67
x
75
68
}, trs , nms )
76
69
} else {
77
70
trs <- lapply(trs , function (x ) { x $ showlegend <- FALSE ; x })
78
71
}
79
-
72
+
80
73
# each trace is with respect to which axis?
81
74
for (j in seq_along(trs )) {
82
75
panel <- unique(dl [[j ]]$ PANEL )
@@ -94,12 +87,12 @@ layers2traces <- function(data, prestats_data, layout, p) {
94
87
95
88
96
89
# ' Convert a geom to a "basic" geom.
97
- # '
90
+ # '
98
91
# ' This function makes it possible to convert ggplot2 geoms that
99
- # ' are not included with ggplot2 itself. Users shouldn't need to use
92
+ # ' are not included with ggplot2 itself. Users shouldn't need to use
100
93
# ' this function. It exists purely to allow other package authors to write
101
94
# ' their own conversion method(s).
102
- # '
95
+ # '
103
96
# ' @param data the data returned by \code{ggplot2::ggplot_build()}.
104
97
# ' @param prestats_data the data before statistics are computed.
105
98
# ' @param layout the panel layout.
@@ -133,7 +126,7 @@ to_basic.GeomBoxplot <- function(data, prestats_data, layout, params, p, ...) {
133
126
}
134
127
vars <- c(" PANEL" , " group" , aez , grep(" _plotlyDomain$" , names(data ), value = T ))
135
128
prefix_class(
136
- merge(prestats_data , data [vars ], by = c(" PANEL" , " group" ), sort = FALSE ),
129
+ merge(prestats_data , data [vars ], by = c(" PANEL" , " group" ), sort = FALSE ),
137
130
" GeomBoxplot"
138
131
)
139
132
}
@@ -311,7 +304,7 @@ to_basic.GeomJitter <- function(data, prestats_data, layout, params, p, ...) {
311
304
312
305
# ' @export
313
306
to_basic.GeomErrorbar <- function (data , prestats_data , layout , params , p , ... ) {
314
- # width for ggplot2 means size of the entire bar, on the data scale
307
+ # width for ggplot2 means size of the entire bar, on the data scale
315
308
# (plotly.js wants half, in pixels)
316
309
data <- merge(data , layout , by = " PANEL" , sort = FALSE )
317
310
data $ width <- (data $ xmax - data $ x ) / (data $ x_max - data $ x_min )
@@ -321,7 +314,7 @@ to_basic.GeomErrorbar <- function(data, prestats_data, layout, params, p, ...) {
321
314
322
315
# ' @export
323
316
to_basic.GeomErrorbarh <- function (data , prestats_data , layout , params , p , ... ) {
324
- # height for ggplot2 means size of the entire bar, on the data scale
317
+ # height for ggplot2 means size of the entire bar, on the data scale
325
318
# (plotly.js wants half, in pixels)
326
319
data <- merge(data , layout , by = " PANEL" , sort = FALSE )
327
320
data $ width <- (data $ ymax - data $ y ) / (data $ y_max - data $ y_min )
@@ -350,12 +343,12 @@ to_basic.default <- function(data, prestats_data, layout, params, p, ...) {
350
343
}
351
344
352
345
# ' Convert a "basic" geoms to a plotly.js trace.
353
- # '
346
+ # '
354
347
# ' This function makes it possible to convert ggplot2 geoms that
355
- # ' are not included with ggplot2 itself. Users shouldn't need to use
348
+ # ' are not included with ggplot2 itself. Users shouldn't need to use
356
349
# ' this function. It exists purely to allow other package authors to write
357
350
# ' their own conversion method(s).
358
- # '
351
+ # '
359
352
# ' @param data the data returned by \code{plotly::to_basic}.
360
353
# ' @param params parameters for the geom, statistic, and 'constant' aesthetics
361
354
# ' @param p a ggplot2 object (the conversion may depend on scales, for instance).
@@ -453,10 +446,50 @@ geom2trace.GeomBar <- function(data, params, p) {
453
446
# ' @export
454
447
geom2trace.GeomPolygon <- function (data , params , p ) {
455
448
data <- group2NA(data )
449
+ # find data value(s) mapped to this polygon for the hoverinfo
450
+ # since hoveron=fills, text needs to be of length 1
451
+
452
+ # Generate text field from data when hoveron=fills
453
+ #
454
+ # Text needs to be of length 1 in order for tooltip to work
455
+ #
456
+ # @param data a data frame
457
+ # @param a names character vector. The names should match columns in data
458
+ # values values are used as variable names in the resulting text
459
+ # hovertext_fill <- function(data, labels) {
460
+ # # add the special text field, if it exists
461
+ # if ("text" %in% names(data)) {
462
+ # labels <- c(labels, c("text" = "text"))
463
+ # }
464
+ # labels <- labels[names(labels) %in% names(data)]
465
+ # if (length(labels) == 0) return(NULL)
466
+ # # convert factors to strings so they aren't converted to integers
467
+ # # when we combine values
468
+ # isFactor <- which(vapply(data, is.factor, logical(1)))
469
+ # for (i in isFactor) {
470
+ # data[, i] <- as.character(data[, i])
471
+ # }
472
+ # udata <- unique(data[names(data) %in% names(labels)])
473
+ # if (NROW(udata) > 1) {
474
+ # warning(
475
+ # "When hoveron=fill, can't display more than one value for a given variable",
476
+ # call. = FALSE
477
+ # )
478
+ # }
479
+ # paste(
480
+ # paste0(labels, ":"),
481
+ # as.character(data[1, names(labels)]),
482
+ # collapse = "<br />"
483
+ # )
484
+ # }
485
+ # doms <- grep("_plotlyDomain$", names(data), value = TRUE)
486
+ # p$labels <- c(p$labels, setNames(sub("_plotlyDomain", "", doms), doms))
487
+ # txt <- hovertext_fill(data, p$labels)
488
+
456
489
L <- list (
457
490
x = data $ x ,
458
491
y = data $ y ,
459
- text = data $ hovertext ,
492
+ text = data $ hovertext [ 1 ] ,
460
493
key = data $ key ,
461
494
type = " scatter" ,
462
495
mode = " lines" ,
@@ -472,13 +505,14 @@ geom2trace.GeomPolygon <- function(data, params, p) {
472
505
fillcolor = toRGB(
473
506
aes2plotly(data , params , " fill" ),
474
507
aes2plotly(data , params , " alpha" )
475
- )
508
+ ),
509
+ hoveron = " fills"
476
510
)
477
511
if (inherits(data , " GeomSmooth" )) {
478
512
L $ hoverinfo <- " x+y"
479
513
}
480
514
L
481
-
515
+
482
516
}
483
517
484
518
# ' @export
@@ -542,7 +576,7 @@ geom2trace.GeomTile <- function(data, params, p) {
542
576
g <- g [order(g $ order ), ]
543
577
# put fill domain on 0-1 scale for colorscale purposes
544
578
g $ fill_plotlyDomain <- scales :: rescale(g $ fill_plotlyDomain )
545
- # create the colorscale
579
+ # create the colorscale
546
580
colScale <- unique(g [, c(" fill_plotlyDomain" , " fill" )])
547
581
# colorscale goes crazy if there are NAs
548
582
colScale <- colScale [stats :: complete.cases(colScale ), ]
@@ -598,6 +632,10 @@ split_on <- function(dat) {
598
632
GeomErrorbarh = " colour" ,
599
633
GeomText = " colour"
600
634
)
635
+ for (i in names(lookup )) {
636
+ lookup [[i ]] <- paste0(lookup [[i ]], " _plotlyDomain" )
637
+ }
638
+ lookup $ GeomPolygon <- c(lookup $ GeomPolygon , " hovertext" )
601
639
splits <- lookup [[geom ]]
602
640
# make sure the variable is in the data, and is non-constant
603
641
splits <- splits [splits %in% names(dat )]
@@ -610,7 +648,7 @@ split_on <- function(dat) {
610
648
splits
611
649
}
612
650
613
- # make trace with errorbars
651
+ # make trace with errorbars
614
652
make_error <- function (data , params , xy = " x" ) {
615
653
color <- aes2plotly(data , params , " colour" )
616
654
e <- list (
@@ -670,7 +708,7 @@ aes2plotly <- function(data, params, aes = "size") {
670
708
height = function (x ) { x / 2 }
671
709
)
672
710
if (is.null(converter )) {
673
- warning(" A converter for " , aes , " wasn't found. \n " ,
711
+ warning(" A converter for " , aes , " wasn't found. \n " ,
674
712
" Please report this issue to: \n " ,
675
713
" https://github.com/ropensci/plotly/issues/new" , call. = FALSE )
676
714
converter <- identity
0 commit comments