@@ -254,48 +254,54 @@ 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
259
dat <- do_eval(d )
260
+ # put everything into a single trace
261
+ trace <- dat
262
+ trace [[" indices" ]] <- seq_along(trace [[" 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 <- list (trace )
284
288
if (has_color ) {
285
289
title <- as.list(d $ args )[[" color" ]] %|| % as.list(d $ args )[[" z" ]] %|| % " "
286
- x $ data <- c( x $ data , colorize( dat , title ) )
290
+ traces <- colorize( traces , dat , title )
287
291
}
288
292
# 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 ))
293
+ if (has_symbol ) traces <- symbolize(traces , dat )
294
+ if (has_group ) traces <- subdivide_traces(traces , dat , " group" )
295
+ traces <- lapply(traces , function (trace ) {# print(attributes(trace));
296
+ trace $ indices <- NULL ;
297
+ trace })
298
+ traces
292
299
} else {
293
- x $ data <- c( x $ data , list (d ) )
300
+ list (d )
294
301
}
295
- }
302
+ }), recursive = FALSE )
296
303
# it's possible have nested layouts (e.g., plot_ly() %>% layout() %>% layout())
297
- nms <- names(l $ layout )
298
- idx <- nms %in% " layout"
304
+ idx <- names(l $ layout ) %in% " layout"
299
305
l $ layout <- c(list (l $ layout [! idx ]), setNames(l $ layout [idx ], NULL ))
300
306
for (i in seq_along(l $ layout )) {
301
307
x $ layout [[i ]] <- perform_eval(l $ layout [[i ]])
@@ -333,7 +339,7 @@ plotly_build <- function(l = last_plot()) {
333
339
}
334
340
335
341
# returns a _list of traces_.
336
- colorize <- function (dat , title = " " ) {
342
+ colorize <- function (traces , dat , title = " " ) {
337
343
cols <- dat [[" color" ]] %|| % dat [[" z" ]]
338
344
if (is.numeric(cols )) {
339
345
# by default, use viridis::viridis(10) -> http://rud.is/b/2015/07/20/using-the-new-viridis-colormap-in-r-thanks-to-simon-garnier/
@@ -348,68 +354,96 @@ colorize <- function(dat, title = "") {
348
354
colorbar = list (title = as.character(title )),
349
355
colorscale = setNames(df , NULL )
350
356
)
351
- # scatter-like traces can have both line and marker objects
352
- if (grepl(" scatter" , dat [[" type" ]] %|| % " scatter" )) {
353
- col_list $ color <- cols
354
- dat [[" marker" ]] <- modifyList(col_list , dat [[" marker" ]] %|| % list ())
355
- # mode <- dat[["mode"]] %||% "markers+lines"
356
- # can't have a colorscale for both markers and lines???
357
- # dat[["line"]] <- modifyList(col_list, dat[["line"]] %||% list())
358
- } else {
359
- dat <- c(dat , col_list )
360
- }
361
- dat <- list (dat )
357
+ traces <- lapply(traces , function (trace ) {
358
+ # scatter-like traces can have both line and marker objects
359
+ if (grepl(" scatter" , trace [[" type" ]] %|| % " scatter" )) {
360
+ trace [[" marker" ]] <- modifyList(col_list , trace [[" marker" ]] %|| % list ())
361
+ trace [[" marker" ]]$ color <- cols [trace $ indices ]
362
+ # mode <- dat[["mode"]] %||% "markers+lines"
363
+ # can't have a colorscale for both markers and lines???
364
+ # dat[["line"]] <- modifyList(col_list, dat[["line"]] %||% list())
365
+ } else {
366
+ trace <- c(trace , col_list )
367
+ }
368
+ trace $ color <- NULL ; trace $ colors <- NULL
369
+ trace
370
+ })
362
371
} else { # discrete color scale
363
372
lvls <- unique(cols )
364
373
N <- length(lvls )
365
374
default <- if (is.ordered(cols )) viridis :: viridis(N )
366
375
else RColorBrewer :: brewer.pal(N , " Set2" )
367
376
colors <- dat [[" colors" ]] %|| % default
368
377
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 )
378
+ traces <- subdivide_traces(traces , dat , " color" , function (sub_trace , lvl ) {
379
+ sub_trace $ marker <- c(sub_trace $ marker , list (color = colz [lvl ]))
380
+ sub_trace $ color <- NULL
381
+ sub_trace $ colors <- NULL
382
+ sub_trace
383
+ })
372
384
}
373
- dat <- lapply(dat , function (x ) { x $ color <- NULL ; x $ colors <- NULL ; x })
374
- dat
385
+ traces
375
386
}
376
387
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 )
388
+ symbolize <- function (traces , dat ) {
389
+ N <- length(unique(dat [[" symbol" ]]))
383
390
if (N > 8 ) warning(" Plotly supports 8 different symbols, but you have " , N , " levels!" )
384
391
symbols <- c(' dot' , ' cross' , ' diamond' , ' square' , ' triangle-down' , ' triangle-left' , ' triangle-right' , ' triangle-up' )
385
- 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
392
+ sym <- dat [[" symbols" ]][seq_len(N )] %|| % symbols [seq_len(N )]
393
+ subdivide_traces(traces , dat , " symbol" , function (trace , lvl ) {
394
+ trace $ symbol <- NULL
395
+ trace $ marker <- c(trace $ marker , list (symbol = sym [[lvl ]]))
396
+ # symbols really only make sense when markers are in the mode, right?
397
+ trace $ mode <- dat $ mode %|| % " markers"
398
+ trace
399
+ })
388
400
}
389
401
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 ]]
402
+ # break up each trace in a list into a smaller traces according to the values of
403
+ # a given property and apply FUN to each resulting subtrace
404
+ # FUN <- function(sub_trace, level_index) is a function taking the new sub-trace and
405
+ # the corresponding index of the property value as its input and returning
406
+ # the modified sub-trace
407
+ subdivide_traces <- function (traces , dat , prop = " group" , FUN = function (trace , lvl ) trace , ... ){
408
+ x <- dat [[prop ]]
394
409
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 )
410
+ return (traces ) # property not found, no traces change
412
411
}
412
+
413
+ # the order of lvls determines the order in which traces are drawn
414
+ # for ordered factors at least, it makes sense to draw the highest level first
415
+ # since that _should_ be the darkest color in a sequential pallette
416
+ lvls <- if (is.factor(x )) rev(levels(x )) else unique(x )
417
+ n <- length(x )
418
+
419
+ new_traces <- unlist(lapply(seq_along(lvls ), function (lvl_ix ) {
420
+ lvl <- lvls [[lvl_ix ]]
421
+ indices <- which(x %in% lvl )
422
+
423
+ lvl_subtraces <- lapply(traces , function (trace ){
424
+ mask <- trace $ indices %in% indices
425
+ if (! any(mask )) return (NULL ) # empty trace
426
+
427
+ trace_size <- length(mask )
428
+ # subset the properties that are subsettable
429
+ for (i in 1 : length(trace )) {
430
+ # FIXME better check for subsettable property
431
+ tr_prop <- trace [[i ]]
432
+ if ((is.vector(tr_prop ) || is.factor(tr_prop )) && (length(tr_prop ) == trace_size )) {
433
+ trace [[i ]] <- tr_prop [mask ]
434
+ }
435
+ }
436
+ if (" name" %in% names(trace )) {
437
+ # append lvl to an existing name
438
+ trace $ name <- paste0(trace $ name , ' /' , lvl )
439
+ } else {
440
+ trace $ name <- lvl
441
+ }
442
+ FUN(trace , lvl_ix , ... ) # customize subtrace
443
+ })
444
+ }), recursive = FALSE )
445
+ new_traces <- new_traces [! sapply(new_traces , is.null )] # remove NULL subtraces
446
+ new_traces
413
447
}
414
448
415
449
axis_titles <- function (x , l ) {
0 commit comments