@@ -254,45 +254,56 @@ plotly_build <- function(l = last_plot()) {
254
254
# this is ugly, but I think it is necessary, since we don't know how many
255
255
# traces we have until we evaluate args and call traceify() (or similar)
256
256
x <- list ()
257
- for (i in seq_along(l $ data )) {
258
- d <- l $ data [[i ]]
257
+ x $ data <- unlist(lapply(l $ data , function (d ) {
259
258
if (should_eval(d )) {
260
- dat <- do_eval(d )
259
+ data <- do_eval(d )
260
+ # put everything into a single trace
261
+ trace <- structure(data , class = " plotly_trace" )
262
+ trace $ indices <- seq_along(data [[" x" ]]) # indices of the original data elements used in the trace FIXME properly define data length
261
263
# start processing specially named arguments
262
- s <- dat [[" size" ]]
264
+ s <- trace [[" size" ]]
263
265
if (! is.null(s )) {
264
266
if (! is.numeric(s )) warning(" size should be numeric" , call. = FALSE )
265
267
# if autosizing is used, guess that the plot is 300 by 600
266
- auto <- dat [[" layout" ]][[" autosize" ]] %|| % TRUE
268
+ auto <- trace [[" layout" ]][[" autosize" ]] %|| % TRUE
267
269
hw <- if (auto ) c(300 , 600 )
268
- else c(dat [[" layout" ]][[" height" ]], dat [[" layout" ]][[" width" ]])
270
+ else c(trace [[" layout" ]][[" height" ]], trace [[" layout" ]][[" width" ]])
269
271
# ensure that markers cover 30% of the plot area
270
272
m <- list (
271
273
size = 0.3 * prod(hw ) * (s / sum(s )),
272
274
sizemode = " area"
273
275
)
274
276
# the marker object is the only type of object which respects size
275
- dat [[" marker" ]] <- modifyList(dat [[" marker" ]] %|| % list (), m )
277
+ trace [[" marker" ]] <- modifyList(trace [[" marker" ]] %|| % list (), m )
276
278
# either add some appropriate hover text
277
279
txt <- paste0(as.list(d $ args )[[" size" ]], " (size): " , s )
278
- dat [[" text" ]] <- if (is.null(dat [[" text" ]])) txt else paste0(dat [[" text" ]], " <br>" , txt )
280
+ trace [[" text" ]] <- if (is.null(trace [[" text" ]])) txt else paste0(trace [[" text" ]], " <br>" , txt )
279
281
}
280
- has_color <- ! is.null(dat [[" color" ]]) ||
281
- isTRUE(! is.null(dat [[" z" ]]) && ! dat [[" type" ]] %in% " scatter3d" )
282
- has_symbol <- ! is.null(dat [[" symbol" ]])
283
- has_group <- ! is.null(dat [[" group" ]])
282
+ has_color <- ! is.null(trace [[" color" ]]) ||
283
+ isTRUE(! is.null(trace [[" z" ]]) && ! trace [[" type" ]] %in% " scatter3d" )
284
+ has_symbol <- ! is.null(trace [[" symbol" ]])
285
+ has_group <- ! is.null(trace [[" group" ]])
286
+ # put the whole dat into a single trace first
287
+ traces <- structure(list (trace ),
288
+ aes = as.character(), # aesthetic parameters that are used so far to define trace
289
+ data = data ,
290
+ class = " plotly_traces" )
284
291
if (has_color ) {
285
292
title <- as.list(d $ args )[[" color" ]] %|| % as.list(d $ args )[[" z" ]] %|| % " "
286
- x $ data <- c( x $ data , colorize(dat , title ) )
293
+ traces <- colorize(traces , title )
287
294
}
288
295
# TODO: add a legend title (is this only possible via annotations?!?)
289
- if (has_symbol ) x $ data <- c(x $ data , symbolize(dat ))
290
- if (has_group ) x $ data <- c(x $ data , traceify(dat , " group" ))
291
- if (! has_color && ! has_symbol && ! has_group ) x $ data <- c(x $ data , list (dat ))
296
+ if (has_symbol ) traces <- symbolize(traces )
297
+ if (has_group ) traces <- subdivide_traces(traces , " group" )
292
298
} else {
293
- x $ data <- c(x $ data , list (d ))
299
+ # d should be plotly_trace already
300
+ traces <- structure(list (d ),
301
+ aes = as.character(), # aesthetic parameters that are used so far to define trace
302
+ data = d ,
303
+ class = " plotly_traces" )
294
304
}
295
- }
305
+ traces
306
+ }), recursive = FALSE )
296
307
# it's possible have nested layouts (e.g., plot_ly() %>% layout() %>% layout())
297
308
nms <- names(l $ layout )
298
309
idx <- nms %in% " layout"
@@ -333,9 +344,11 @@ plotly_build <- function(l = last_plot()) {
333
344
}
334
345
335
346
# returns a _list of traces_.
336
- colorize <- function (dat , title = " " ) {
347
+ colorize <- function (traces , title = " " ) {
348
+ dat <- attr(traces , " data" )
337
349
cols <- dat [[" color" ]] %|| % dat [[" z" ]]
338
350
if (is.numeric(cols )) {
351
+ # FIXME needs to be updated for plotly_traces
339
352
# by default, use viridis::viridis(10) -> http://rud.is/b/2015/07/20/using-the-new-viridis-colormap-in-r-thanks-to-simon-garnier/
340
353
colors <- dat [[" colors" ]] %|| % viridis :: viridis(10 )
341
354
cols <- as.vector(cols )
@@ -359,57 +372,88 @@ colorize <- function(dat, title = "") {
359
372
dat <- c(dat , col_list )
360
373
}
361
374
dat <- list (dat )
375
+ lapply(traces , function (x ) { x $ color <- NULL ; x $ colors <- NULL ; x })
376
+ traces
362
377
} else { # discrete color scale
363
378
lvls <- unique(cols )
364
379
N <- length(lvls )
365
380
default <- if (is.ordered(cols )) viridis :: viridis(N )
366
381
else RColorBrewer :: brewer.pal(N , " Set2" )
367
382
colors <- dat [[" colors" ]] %|| % default
368
383
colz <- scales :: col_factor(colors , levels = lvls , na.color = " transparent" )(lvls )
369
- dat <- traceify(dat , " color" )
370
- dat <- Map(function (x , y ) { x [[" marker" ]] <- c(x [[" marker" ]], list (color = y )); x },
371
- dat , colz )
384
+ traces <- subdivide_traces(traces , " color" , function (sub_trace , lvl ) {
385
+ sub_trace $ marker <- c(sub_trace $ marker , list (color = colz [lvl ]))
386
+ sub_trace $ color <- NULL
387
+ sub_trace $ colors <- NULL
388
+ sub_trace
389
+ })
372
390
}
373
- dat <- lapply(dat , function (x ) { x $ color <- NULL ; x $ colors <- NULL ; x })
374
- dat
391
+ traces
375
392
}
376
393
377
- symbolize <- function (dat ) {
378
- # symbols really only make sense when markers are in the mode, right?
379
- dat $ mode <- dat $ mode %|| % " markers"
380
- dat <- traceify(dat , " symbol" )
381
- dat <- lapply(dat , function (x ) { x $ symbol <- NULL ; x })
382
- N <- length(dat )
394
+ symbolize <- function (traces ) {
395
+ dat <- attr(traces , " data" )
396
+ symb_data <- dat [[" symbol" ]]
397
+ N <- length(unique(symb_data ))
383
398
if (N > 8 ) warning(" Plotly supports 8 different symbols, but you have " , N , " levels!" )
384
399
symbols <- c(' dot' , ' cross' , ' diamond' , ' square' , ' triangle-down' , ' triangle-left' , ' triangle-right' , ' triangle-up' )
385
400
sym <- dat [[1 ]][[" symbols" ]][seq_len(N )] %|| % symbols [seq_len(N )]
386
- dat <- Map(function (x , y ) { x $ marker $ symbol <- y ; x }, dat , sym )
387
- dat
401
+ subdivide_traces(traces , " symbol" , function (sub_trace , lvl ) {
402
+ trace $ symbol <- NULL
403
+ trace $ marker $ symbol <- sym [[lvl ]]
404
+ # symbols really only make sense when markers are in the mode, right?
405
+ trace $ mode <- dat $ mode %|| % " markers"
406
+ trace
407
+ })
388
408
}
389
409
390
- # break up a single trace into multiple traces according to values stored
391
- # a particular key name
392
- traceify <- function (dat , nm = " group" ) {
393
- x <- dat [[nm ]]
410
+ # break up each trace in a list into a smaller traces according to the values of
411
+ # a given property and apply FUN to each resulting subtrace
412
+ # FUN <- function(sub_trace, level_index) is a function taking the new sub-trace and
413
+ # the corresponding index of the property value as its input and returning
414
+ # the modified sub-trace
415
+ subdivide_traces <- function (traces , prop = " group" , FUN = function (trace , lvl ) trace , ... ){
416
+ x <- attr(traces , " data" )[[prop ]]
394
417
if (is.null(x )) {
395
- return (list (dat ))
396
- } else {
397
- # the order of lvls determines the order in which traces are drawn
398
- # for ordered factors at least, it makes sense to draw the highest level first
399
- # since that _should_ be the darkest color in a sequential pallette
400
- lvls <- if (is.factor(x )) rev(levels(x )) else unique(x )
401
- n <- length(x )
402
- # recursively search for a non-list of appropriate length (if it is, subset it)
403
- recurse <- function (z , n , idx ) {
404
- if (is.list(z )) lapply(z , recurse , n , idx ) else if (length(z ) == n ) z [idx ] else z
405
- }
406
- new_dat <- list ()
407
- for (j in seq_along(lvls )) {
408
- new_dat [[j ]] <- lapply(dat , function (y ) recurse(y , n , x %in% lvls [j ]))
409
- new_dat [[j ]]$ name <- lvls [j ]
410
- }
411
- return (new_dat )
418
+ return (traces ) # property not found, no traces change
412
419
}
420
+
421
+ # the order of lvls determines the order in which traces are drawn
422
+ # for ordered factors at least, it makes sense to draw the highest level first
423
+ # since that _should_ be the darkest color in a sequential pallette
424
+ lvls <- if (is.factor(x )) rev(levels(x )) else unique(x )
425
+ n <- length(x )
426
+
427
+ new_traces <- unlist(lapply(seq_along(lvls ), function (lvl_ix ) {
428
+ lvl <- lvls [[lvl_ix ]]
429
+ indices <- which(x %in% lvl )
430
+
431
+ lvl_subtraces <- lapply(traces , function (trace ){
432
+ mask <- trace $ indices %in% indices
433
+ if (sum(mask ) == 0 ) return (NULL ) # empty trace
434
+
435
+ trace_size <- length(mask )
436
+ # subset the properties that are subsettable
437
+ for (i in 1 : length(trace )) {
438
+ # FIXME better check for subsettable property
439
+ prop <- trace [[i ]]
440
+ if ((is.vector(prop ) || is.factor(prop )) && (length(prop ) == trace_size )) {
441
+ trace [[i ]] <- prop [mask ]
442
+ }
443
+ }
444
+ if (" name" %in% names(trace )) {
445
+ # append lvl to an existing name
446
+ trace $ name <- paste0(trace $ name , ' /' , lvl )
447
+ } else {
448
+ trace $ name <- lvl
449
+ }
450
+ FUN(trace , lvl_ix , ... ) # customize subtrace
451
+ })
452
+ }), recursive = FALSE )
453
+ new_traces <- new_traces [! sapply(new_traces , is.null )] # remove NULL subtraces
454
+ attributes(new_traces ) <- attributes(traces )
455
+ attr(new_traces , " aes" ) <- c(attr(traces , " aes" ), prop ) # log that given aesthetic was processed
456
+ new_traces
413
457
}
414
458
415
459
axis_titles <- function (x , l ) {
0 commit comments