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