@@ -287,8 +287,7 @@ plotly_build.plotly_hash <- function(l = last_plot()) {
287
287
# this is ugly, but I think it is necessary, since we don't know how many
288
288
# traces we have until we evaluate args and call traceify() (or similar)
289
289
x <- list ()
290
- for (i in seq_along(l $ data )) {
291
- d <- l $ data [[i ]]
290
+ x $ data <- unlist(lapply(l $ data , function (d ) {
292
291
if (should_eval(d )) {
293
292
dat <- do_eval(d )
294
293
# start processing specially named arguments
@@ -310,25 +309,82 @@ plotly_build.plotly_hash <- function(l = last_plot()) {
310
309
txt <- paste0(as.list(d $ args )[[" size" ]], " (size): " , s )
311
310
dat [[" text" ]] <- if (is.null(dat [[" text" ]])) txt else paste0(dat [[" text" ]], " <br>" , txt )
312
311
}
313
- has_color <- ! is.null(dat [[" color" ]]) ||
314
- isTRUE(! is.null(dat [[" z" ]]) && ! dat [[" type" ]] %in% " scatter3d" )
315
- has_symbol <- ! is.null(dat [[" symbol" ]])
316
- has_group <- ! is.null(dat [[" group" ]])
317
- if (has_color ) {
312
+
313
+ # helper functions to process dat
314
+ tracify_by_data <- function (points_df , data , data_name , force_numeric = FALSE ) {
315
+ if (! is.null(data ) && (! is.numeric(data ) || force_numeric )) {
316
+ points_df [[paste0(data_name ," _index" )]] <- as.factor(data )
317
+ }
318
+ return (points_df )
319
+ }
320
+ tracify_by_column <- function (points_df , dat , col_name , force_numeric = FALSE ) {
321
+ tracify_by_data(points_df , dat [[col_name ]], col_name , force_numeric = force_numeric )
322
+ }
323
+ tracify_by_color <- function (points_df , dat ) {
324
+ if (! is.null(dat [[" color" ]])) {
325
+ cols <- dat [[" color" ]]
326
+ } else if (isTRUE(! is.null(dat [[" z" ]]) && ! dat [[" type" ]] %in% " scatter3d" )) {
327
+ cols <- dat [[" z" ]]
328
+ } else {
329
+ cols <- NULL
330
+ }
331
+ tracify_by_data(points_df , cols , " color" )
332
+ }
333
+
334
+ # define the dat traces
335
+ points_df <- data.frame (dat_index = seq_along(dat [[" x" ]] %|| % dat [[" y" ]] %|| % dat [[" z" ]])) %> % # indices of the original data elements used in the trace FIXME properly define data length
336
+ tracify_by_color(dat ) %> %
337
+ tracify_by_column(dat , " symbol" , force_numeric = TRUE )
338
+ trace_key_cols <- setdiff(colnames(points_df ), " dat_index" )
339
+ points_df <- dplyr :: arrange_(points_df , .dots = c(trace_key_cols , " dat_index" )) %> %
340
+ dplyr :: group_by_(.dots = trace_key_cols )
341
+ points_df $ trace_index <- dplyr :: group_indices(points_df )
342
+ points_df <- dplyr :: ungroup(points_df )
343
+ points_df $ point_order <- seq_len(nrow(points_df ))
344
+
345
+ trace_point_indices <- attr(dplyr :: group_by(points_df , trace_index ), " indices" )
346
+ if (length(trace_point_indices ) > 0 ) {
347
+ trace_point_indices <- lapply(trace_point_indices , function (ixs ) ixs + 1L )
348
+ trace_dat_indices <- lapply(trace_point_indices , function (ixs ) points_df $ dat_index [ixs ])
349
+ } else { # single/empty trace
350
+ trace_point_indices <- list (as.integer())
351
+ trace_dat_indices <- list (points_df $ dat_index )
352
+ }
353
+
354
+ # assign trace names
355
+ names(trace_point_indices ) <- sapply(trace_point_indices , function (point_indices ){
356
+ if (length(point_indices )> 0 ) {
357
+ paste0(lapply(points_df [point_indices [[1 ]], trace_key_cols ], as.character ), collapse = " /" )
358
+ } else {
359
+ NA
360
+ }
361
+ })
362
+
363
+ # list of the functions to apply for each created trace
364
+ trace_brushes <- list ()
365
+
366
+ # assigns name to the trace
367
+ trace_brushes <- append(trace_brushes , function (trace , trace_ix , dat , dat_point_indices ) {
368
+ trace $ name <- names(trace_point_indices )[[trace_ix ]]
369
+ trace
370
+ })
371
+
372
+ if (! is.null(dat [[" color" ]]) ||
373
+ isTRUE(! is.null(dat [[" z" ]]) && ! dat [[" type" ]] %in% " scatter3d" )) {
318
374
title <- as.list(d $ args )[[" color" ]] %|| % as.list(d $ args )[[" z" ]] %|| % " "
319
- x $ data <- c( x $ data , colorize (dat , title ))
375
+ trace_brushes <- append( trace_brushes , color_brush (dat , title ))
320
376
}
321
377
# TODO: add a legend title (is this only possible via annotations?!?)
322
- if (has_symbol ) x $ data <- c(x $ data , symbolize(dat ))
323
- if (has_group ) x $ data <- c(x $ data , traceify(dat , " group" ))
324
- if (! has_color && ! has_symbol && ! has_group ) x $ data <- c(x $ data , list (dat ))
378
+ if (! is.null(dat [[" symbol" ]])) {
379
+ trace_brushes <- append(trace_brushes , symbol_brush(dat ))
380
+ }
381
+ generate_traces(dat , max(points_df $ dat_index , na.rm = TRUE ), trace_dat_indices , trace_brushes )
325
382
} else {
326
- x $ data <- c( x $ data , list (d ) )
383
+ list (d )
327
384
}
328
- }
385
+ }), recursive = FALSE )
329
386
# it's possible have nested layouts (e.g., plot_ly() %>% layout() %>% layout())
330
- nms <- names(l $ layout )
331
- idx <- nms %in% " layout"
387
+ idx <- names(l $ layout ) %in% " layout"
332
388
l $ layout <- c(list (l $ layout [! idx ]), setNames(l $ layout [idx ], NULL ))
333
389
for (i in seq_along(l $ layout )) {
334
390
x $ layout [[i ]] <- perform_eval(l $ layout [[i ]])
@@ -366,7 +422,7 @@ plotly_build.plotly_hash <- function(l = last_plot()) {
366
422
}
367
423
368
424
# returns a _list of traces_.
369
- colorize <- function (dat , title = " " ) {
425
+ color_brush <- function (dat , title = " " ) {
370
426
cols <- dat [[" color" ]] %|| % dat [[" z" ]]
371
427
if (is.numeric(cols )) {
372
428
# by default, use viridis::viridis(10) -> http://rud.is/b/2015/07/20/using-the-new-viridis-colormap-in-r-thanks-to-simon-garnier/
@@ -375,74 +431,92 @@ colorize <- function(dat, title = "") {
375
431
rng <- range(cols , na.rm = TRUE )
376
432
x <- seq(min(rng ), max(rng ), length.out = 10 )
377
433
colz <- scales :: col_numeric(colors , rng , na.color = " transparent" )(x )
378
- df <- if (length(cols ) > 1 ) data.frame (scales :: rescale(x ), colz )
379
- else data.frame (c(0 , 1 ), rep(colz , 2 ))
434
+ df <- if (length(cols ) > 1 ) data.frame (scales :: rescale(x ), colz ) else data.frame (c(0 , 1 ), rep(colz , 2 ))
380
435
col_list <- list (
381
436
colorbar = list (title = as.character(title )),
382
437
colorscale = setNames(df , NULL )
383
438
)
384
439
# scatter-like traces can have both line and marker objects
385
440
if (grepl(" scatter" , dat [[" type" ]] %|| % " scatter" )) {
386
- col_list $ color <- cols
387
- dat [[" marker" ]] <- modifyList(col_list , dat [[" marker" ]] %|| % list ())
388
- # mode <- dat[["mode"]] %||% "markers+lines"
389
- # can't have a colorscale for both markers and lines???
390
- # dat[["line"]] <- modifyList(col_list, dat[["line"]] %||% list())
441
+ return (function (trace , trace_ix , dat , dat_indices ) {
442
+ trace [[" marker" ]] <- modifyList(col_list , trace [[" marker" ]] %|| % list ())
443
+ trace [[" marker" ]]$ color <- cols [dat_indices ]
444
+ # mode <- dat[["mode"]] %||% "markers+lines"
445
+ # can't have a colorscale for both markers and lines???
446
+ # dat[["line"]] <- modifyList(col_list, dat[["line"]] %||% list())
447
+ trace $ color <- NULL ; trace $ colors <- NULL
448
+ trace
449
+ })
391
450
} else {
392
- dat <- c(dat , col_list )
451
+ return (function (trace , trace_ix , dat , dat_indices ) {
452
+ trace $ color <- NULL ; trace $ colors <- NULL
453
+ c(trace , col_list )
454
+ })
393
455
}
394
- dat <- list (dat )
395
456
} else { # discrete color scale
396
457
lvls <- unique(cols )
397
458
N <- length(lvls )
398
459
default <- if (is.ordered(cols )) viridis :: viridis(N )
399
460
else RColorBrewer :: brewer.pal(N , " Set2" )
400
461
colors <- dat [[" colors" ]] %|| % default
401
462
colz <- scales :: col_factor(colors , levels = lvls , na.color = " transparent" )(lvls )
402
- dat <- traceify(dat , " color" )
403
- dat <- Map(function (x , y ) { x [[" marker" ]] <- c(x [[" marker" ]], list (color = y )); x },
404
- dat , colz )
463
+ names(colz ) <- lvls
464
+ return (function (trace , trace_ix , dat , dat_indices ) {
465
+ trace $ marker <- c(trace $ marker , list (color = colz [[trace [[" color" ]][[1 ]]]]))
466
+ trace $ color <- NULL
467
+ trace $ colors <- NULL
468
+ trace
469
+ })
405
470
}
406
- dat <- lapply(dat , function (x ) { x $ color <- NULL ; x $ colors <- NULL ; x })
407
- dat
408
471
}
409
472
410
- symbolize <- function (dat ) {
411
- # symbols really only make sense when markers are in the mode, right?
412
- dat $ mode <- dat $ mode %|| % " markers"
413
- dat <- traceify(dat , " symbol" )
414
- dat <- lapply(dat , function (x ) { x $ symbol <- NULL ; x })
415
- N <- length(dat )
416
- if (N > 8 ) warning(" Plotly supports 8 different symbols, but you have " , N , " levels!" )
473
+ symbol_brush <- function (dat ) {
474
+ lvls <- unique(dat [[" symbol" ]])
475
+ N <- length(lvls )
476
+ if (N > 8 ) warning(" Plotly supports 8 different symbols, but you have " , N , " levels!" ) # FIXME: actually, plotly supports more
417
477
symbols <- c(' dot' , ' cross' , ' diamond' , ' square' , ' triangle-down' , ' triangle-left' , ' triangle-right' , ' triangle-up' )
418
- sym <- dat [[1 ]][[" symbols" ]][seq_len(N )] %|| % symbols [seq_len(N )]
419
- dat <- Map(function (x , y ) { x $ marker $ symbol <- y ; x }, dat , sym )
420
- dat
478
+ sym <- (dat [[" symbols" ]] %|| % symbols )[seq_len(N )]
479
+ names(sym ) <- lvls
480
+ # return brush function
481
+ function (trace , trace_ix , dat , dat_indices ) {
482
+ trace $ marker <- c(trace $ marker , list (symbol = sym [[trace [[" symbol" ]][[1 ]]]]))
483
+ trace $ symbol <- NULL
484
+ # symbols really only make sense when markers are in the mode, right?
485
+ trace $ mode <- dat $ mode %|| % " markers"
486
+ trace
487
+ }
421
488
}
422
489
423
- # break up a single trace into multiple traces according to values stored
424
- # a particular key name
425
- traceify <- function (dat , nm = " group" ) {
426
- x <- dat [[nm ]]
427
- if (is.null(x )) {
428
- return (list (dat ))
429
- } else {
430
- # the order of lvls determines the order in which traces are drawn
431
- # for ordered factors at least, it makes sense to draw the highest level first
432
- # since that _should_ be the darkest color in a sequential pallette
433
- lvls <- if (is.factor(x )) rev(levels(x )) else unique(x )
434
- n <- length(x )
435
- # recursively search for a non-list of appropriate length (if it is, subset it)
436
- recurse <- function (z , n , idx ) {
437
- if (is.list(z )) lapply(z , recurse , n , idx ) else if (length(z ) == n ) z [idx ] else z
438
- }
439
- new_dat <- list ()
440
- for (j in seq_along(lvls )) {
441
- new_dat [[j ]] <- lapply(dat , function (y ) recurse(y , n , x %in% lvls [j ]))
442
- new_dat [[j ]]$ name <- lvls [j ]
443
- }
444
- return (new_dat )
490
+ # Split dat into traces given indices of dat elements for each trace,
491
+ # then apply brushes to each resulting trace.
492
+ # A brush is a function function(trace, trace_ix, dat, dat_indices)
493
+ # that is supposed to return the modified version of the trace
494
+ generate_traces <- function (dat , dat_len , traces_dat_indices , trace_brushes ){
495
+ # create trace by subseting dat columns and copying non-vector elements as-is
496
+ subset_dat <- function (dat , dat_indices ) {
497
+ lapply(dat , function (dat_el ) {
498
+ # FIXME better check for subsettable property
499
+ if ((is.vector(dat_el ) || is.factor(dat_el )) && (length(dat_el ) == dat_len )) {
500
+ dat_el [dat_indices ]
501
+ } else if (is.list(dat_el )) {
502
+ # recursion
503
+ lapply(dat_el , subset_dat , dat_indices )
504
+ } else {
505
+ dat_el # as-is
506
+ }
507
+ })
445
508
}
509
+ # create traces by subsetting dat and applying brushes
510
+ # start with the traces of highest index so that they are drawn first
511
+ # since that _should_ be the darkest color in a sequential pallette
512
+ lapply(rev(seq_along(traces_dat_indices )), function (trace_ix ) {
513
+ dat_indices <- traces_dat_indices [[trace_ix ]]
514
+ trace <- subset_dat(dat , dat_indices )
515
+ for (brush in trace_brushes ) {
516
+ trace <- brush(trace , trace_ix , dat , dat_indices )
517
+ }
518
+ trace
519
+ })
446
520
}
447
521
448
522
axis_titles <- function (x , l ) {
0 commit comments