@@ -30,19 +30,21 @@ layers2traces <- function(data, prestats_data, layers, layout, scales, labels) {
30
30
datz <- c(datz , d [j ])
31
31
paramz <- c(paramz , params [i ])
32
32
# When splitting layers into multiple traces, we need the domain/range of
33
- # the scale (for trace naming & legend generation)
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 ]])
34
40
psd <- prestats_data [[i ]]
35
- idx <- names(psd ) %in% c(names(discreteScales ), " PANEL" )
36
- key <- unique(psd [, idx , drop = FALSE ])
41
+ key <- unique(psd [names(psd ) %in% split_by ])
37
42
# this order (should) determine the ordering of traces (within layer)
38
43
key <- key [do.call(order , key ), , drop = FALSE ]
39
- nms <- names(key )
40
- idx <- nms %in% names(discreteScales )
41
- nms [idx ] <- paste0(nms [idx ], " _domain" )
42
- key <- setNames(key , nms )
43
- for (k in setdiff(nms [idx ], " PANEL" )) {
44
- scaleName <- sub(" _domain" , " " , k )
45
- key [[scaleName ]] <- scales $ get_scales(scaleName )$ map(key [, k ])
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 ])
46
48
}
47
49
keyz <- c(keyz , list (key ))
48
50
}
@@ -52,62 +54,65 @@ layers2traces <- function(data, prestats_data, layers, layout, scales, labels) {
52
54
trace.list <- list ()
53
55
for (i in seq_along(datz )) {
54
56
d <- datz [[i ]]
55
- # what aesthetics do we split on for this geom?
56
- split_by <- split_on(class(d )[1 ])
57
- # always split on PANEL
58
- idx <- names(d ) %in% c(split_by , " PANEL" )
59
- # if the variable is constant, don't split on it
60
- idx <- idx & vapply(d , function (x ) length(unique(x )) > 1 , logical (1 ))
61
57
# create a factor to split the data on...
62
58
# by matching the factor levels with the order of the domain (of _discrete_
63
59
# scales), the trace ordering should be correct
64
60
key <- keyz [[i ]]
65
- idx2 <- names(key ) %in% names(d [idx ])
66
- fac <- if (sum(idx ) > = 1 ) {
67
- if (sum(idx ) == sum(idx2 )) {
68
- factor (
69
- apply(d [idx ], 1 , paste , collapse = " ." ),
70
- levels = apply(key [names(d [idx ])], 1 , paste , collapse = " ." )
71
- )
72
- } else {
73
- d [idx ]
74
- }
75
- } else {
76
- 1
61
+ split_by <- names(key )[! grepl(" _domain$" , names(key ))]
62
+ fac <- factor (
63
+ apply(d [split_by ], 1 , paste , collapse = " ." ),
64
+ levels = apply(key [split_by ], 1 , paste , collapse = " ." )
65
+ )
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
+ )
77
76
}
78
77
dl <- split(d , fac , drop = TRUE )
79
78
# list of traces for this layer
80
79
trs <- Map(geom2trace , dl , paramz [i ])
81
- # each trace is with respect to which axis?
82
- for (j in seq_along(trs )) {
83
- panel <- unique(dl [[j ]]$ PANEL )
84
- trs [[j ]]$ xaxis <- sub(" axis" , " " , layout [panel , " xaxis" ])
85
- trs [[j ]]$ yaxis <- sub(" axis" , " " , layout [panel , " yaxis" ])
86
- }
87
- # generate name/legendgroup/showlegend, if appropriate
88
- if (length(trs ) > 1 && any(names(key ) %in% names(discreteScales ))) {
80
+ # set name/legendgroup/showlegend, if appropriate
81
+ legendVars <- setdiff(split_by , " PANEL" )
82
+ if (! splitContinuous && length(legendVars ) > 0 && length(trs ) > 1 ) {
89
83
# labels is a list of legend titles, but since we're restricted to
90
84
# one (merged) legend, I think it only makes since to prefix the variable
91
85
# name in the legend entries
92
- lab <- labels [names(discreteScales )]
93
- idx <- names(key ) %in% names(discreteScales )
94
- vals <- key [paste0(names(key [idx ]), " _domain" )]
86
+ lab <- labels [legendVars ]
87
+ vals <- key [paste0(legendVars , " _domain" )]
95
88
valz <- Map(function (x , y ) { paste0(x , " : " , y ) }, lab , vals )
96
- entries <- Reduce(function (x , y ) paste0(x , " <br>" , y ), valz )
97
- # also need to set `layout.legend.traceorder='reversed'` (YUCK!!!)
98
- if (inherits(d , " GeomBar" ) && paramz [[i ]]$ position == " identity" ) {
99
- trs <- rev(trs )
100
- }
89
+ entries <- Reduce(function (x , y ) {
90
+ if (identical(x , y )) x else paste0(x , " <br>" , y )
91
+ }, valz )
101
92
for (k in seq_along(trs )) {
102
93
trs [[k ]]$ name <- entries [[k ]]
103
94
trs [[k ]]$ legendgroup <- entries [[k ]]
104
- trs [[k ]]$ showlegend <- TRUE
95
+ # depending on the geom (e.g. smooth) this may be FALSE already
96
+ if (is.null(trs [[k ]]$ showlegend )) trs [[k ]]$ showlegend <- TRUE
105
97
}
106
98
} else {
107
99
trs <- lapply(trs , function (x ) { x $ showlegend <- FALSE ; x })
108
100
}
101
+
102
+ # each trace is with respect to which axis?
103
+ for (j in seq_along(trs )) {
104
+ panel <- unique(dl [[j ]]$ PANEL )
105
+ trs [[j ]]$ xaxis <- sub(" axis" , " " , layout [panel , " xaxis" ])
106
+ trs [[j ]]$ yaxis <- sub(" axis" , " " , layout [panel , " yaxis" ])
107
+ }
108
+ # also need to set `layout.legend.traceorder='reversed'`
109
+ if (inherits(d , " GeomBar" ) && paramz [[i ]]$ position == " identity" ) {
110
+ trs <- rev(trs )
111
+ }
112
+
109
113
trace.list <- c(trace.list , trs )
110
114
}
115
+
111
116
trace.list
112
117
}
113
118
@@ -156,7 +161,7 @@ to_basic.GeomSmooth <- function(data, prestats_data, layout, params, ...) {
156
161
dat <- prefix_class(data , " GeomPath" )
157
162
dat $ alpha <- NULL
158
163
if (! identical(params $ se , FALSE )) {
159
- dat2 <- prefix_class(ribbon_dat(data ), " GeomPolygon" )
164
+ dat2 <- prefix_class(ribbon_dat(data ), c( " GeomPolygon" , " GeomSmooth " ) )
160
165
dat2 $ colour <- NULL
161
166
dat <- list (dat , dat2 )
162
167
}
@@ -282,10 +287,6 @@ to_basic.GeomVline <- function(data, prestats_data, layout, params, ...) {
282
287
283
288
# ' @export
284
289
to_basic.GeomJitter <- function (data , prestats_data , layout , params , ... ) {
285
- if (" size" %in% names(data )) {
286
- params $ sizemin <- min(prestats_data $ globsizemin )
287
- params $ sizemax <- max(prestats_data $ globsizemax )
288
- }
289
290
prefix_class(data , " GeomPoint" )
290
291
}
291
292
@@ -295,9 +296,20 @@ to_basic.GeomErrorbar <- function(data, prestats_data, layout, params, ...) {
295
296
# (plotly.js wants half, in pixels)
296
297
data <- merge(data , layout , by = " PANEL" , sort = FALSE )
297
298
data $ width <- (data $ xmax - data $ x ) / (data $ x_max - data $ x_min )
299
+ data $ fill <- NULL
298
300
prefix_class(data , " GeomErrorbar" )
299
301
}
300
302
303
+ # ' @export
304
+ to_basic.GeomErrorbarh <- function (data , prestats_data , layout , params , ... ) {
305
+ # height for ggplot2 means size of the entire bar, on the data scale
306
+ # (plotly.js wants half, in pixels)
307
+ data <- merge(data , layout , by = " PANEL" , sort = FALSE )
308
+ data $ width <- (data $ ymax - data $ y ) / (data $ y_max - data $ y_min )
309
+ data $ fill <- NULL
310
+ prefix_class(data , " GeomErrorbarh" )
311
+ }
312
+
301
313
# ' @export
302
314
to_basic.GeomLinerange <- function (data , prestats_data , layout , params , ... ) {
303
315
data $ width <- 0
@@ -313,15 +325,6 @@ to_basic.GeomPointrange <- function(data, prestats_data, layout, params, ...) {
313
325
)
314
326
}
315
327
316
- # ' @export
317
- to_basic.GeomErrorbarh <- function (data , prestats_data , layout , params , ... ) {
318
- # height for ggplot2 means size of the entire bar, on the data scale
319
- # (plotly.js wants half, in pixels)
320
- data <- merge(data , layout , by = " PANEL" , sort = FALSE )
321
- data $ width <- (data $ ymax - data $ y ) / (data $ y_max - data $ y_min )
322
- prefix_class(data , " GeomErrorbarh" )
323
- }
324
-
325
328
# ' @export
326
329
to_basic.default <- function (data , prestats_data , layout , params , ... ) {
327
330
data
@@ -396,7 +399,9 @@ geom2trace.GeomPoint <- function(data, params) {
396
399
)
397
400
# fill is irrelevant for pch %in% c(1, 15:20)
398
401
pch <- uniq(data $ shape ) %|| % params $ shape %|| % GeomPoint $ default_aes $ shape
399
- L $ marker $ color [pch %in% c(1 , 15 : 20 )] <- L $ marker $ line $ color [pch %in% c(1 , 15 : 20 )]
402
+ if (any(pch %in% c(1 , 15 : 20 ))) {
403
+ L $ marker $ color <- L $ marker $ line $ color
404
+ }
400
405
L
401
406
}
402
407
@@ -443,13 +448,12 @@ geom2trace.GeomPolygon <- function(data, params) {
443
448
if (" level" %in% names(data )) {
444
449
data $ level <- paste(" Level:" , data $ level )
445
450
}
446
- list (
451
+ L <- list (
447
452
x = data $ x ,
448
453
y = data $ y ,
449
454
text = data $ text %|| % data $ level ,
450
455
type = " scatter" ,
451
456
mode = " lines" ,
452
- name = if (inherits(data , " GeomSmooth" )) " standard error" ,
453
457
line = list (
454
458
# NOTE: line attributes must be constant on a polygon
455
459
width = aes2plotly(data , params , " size" ),
@@ -462,6 +466,11 @@ geom2trace.GeomPolygon <- function(data, params) {
462
466
aes2plotly(data , params , " alpha" )
463
467
)
464
468
)
469
+ if (inherits(data , " GeomSmooth" )) {
470
+ L $ name <- " standard error"
471
+ L $ showlegend <- FALSE
472
+ }
473
+ L
465
474
466
475
}
467
476
@@ -554,7 +563,7 @@ geom2trace.default <- function(data, params) {
554
563
" Please open an issue with your example code at\n " ,
555
564
" https://github.com/ropensci/plotly/issues"
556
565
)
557
- NULL
566
+ list ()
558
567
}
559
568
560
569
# ---------------------------------------------------------------------------
@@ -605,24 +614,30 @@ group2NA <- function(data) {
605
614
data
606
615
}
607
616
608
-
609
- split_on <- function (geom = " GeomPoint" ) {
610
- # NOTE: Do we also want to split on size?
611
- # Legends based on sizes not implemented yet in Plotly
617
+ # given a geom, should we split on any continuous variables?
618
+ # this is necessary for some geoms, for example, polygons
619
+ # since plotly.js can't draw two polygons with different fill in a single trace
620
+ split_on <- function (dat ) {
621
+ geom <- class(dat )[1 ]
612
622
lookup <- list (
613
- GeomPoint = c(" colour" , " fill" , " shape" ),
614
- GeomPath = c(" linetype" , " size" , " colour" , " shape" ),
615
- GeomPolygon = c(" colour" , " fill" , " linetype" , " size" ),
616
- GeomBar = c(" colour" , " fill" ),
617
- GeomDensity = c(" colour" , " fill" , " linetype" ),
623
+ GeomPath = c(" fill" , " colour" , " size" ),
624
+ GeomPolygon = c(" fill" , " colour" , " size" ),
625
+ GeomBar = " fill" ,
618
626
GeomBoxplot = c(" colour" , " fill" , " size" ),
619
- GeomErrorbar = c(" colour" , " linetype" ),
620
- GeomErrorbarh = c(" colour" , " linetype" ),
621
- GeomArea = c(" colour" , " fill" ),
622
- GeomStep = c(" linetype" , " size" , " colour" ),
623
- GeomText = c(" colour" )
627
+ GeomErrorbar = " colour" ,
628
+ GeomErrorbarh = " colour" ,
629
+ GeomText = " colour"
624
630
)
625
- lookup [[geom ]]
631
+ splits <- lookup [[geom ]]
632
+ # make sure the variable is in the data, and is non-constant
633
+ splits <- splits [splits %in% names(dat )]
634
+ # is there more than one unique value for this aes split in the data?
635
+ for (i in splits ) {
636
+ if (length(unique(dat [, i ])) < 2 ) {
637
+ splits <- setdiff(splits , i )
638
+ }
639
+ }
640
+ splits
626
641
}
627
642
628
643
# make trace with errorbars
0 commit comments