reactable icon indicating copy to clipboard operation
reactable copied to clipboard

Crosstalk interface - how to keep selected elements visible/selected?

Open tbrittoborges opened this issue 3 years ago • 2 comments

Using reactable with plotly::highlight and crosstalk::SharedData as input works like a charm:

library(reactable)
library(plotly)
library(crosstalk)

shared <- SharedData$new(iris, function(data) rownames(data))

bscols(
  shared %>% 
    plot_ly(x = ~Sepal.Length, y = ~Sepal.Width, color = ~Species) %>% 
    add_markers(alpha = 0.5) %>% 
    highlight(color = 'red'),
  reactable(shared, selection = 'multiple')
)

Selection from reactable -> highlights plotly:

image

Highlight from plotly -> filter reactable:

Table is filter but row is not selected: image

Highlight from plotly and then select from filtered table:

image

Once a row is selected, the table is unfiltered and the selected element is lost. The element is still selected. Is there a way to improve this integration? Ideally, keep the selected entries at the top of the table.

Session info

R version 4.0.5 (2021-03-31) Platform: x86_64-pc-linux-gnu (64-bit) Running under: Debian GNU/Linux 10 (buster)

Matrix products: default BLAS: /beegfs/biosw/R/4.0.5_deb10/lib/R/lib/libRblas.so LAPACK: /beegfs/biosw/R/4.0.5_deb10/lib/R/lib/libRlapack.so

locale: [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8 LC_MONETARY=en_US.UTF-8
[6] LC_MESSAGES=en_US.UTF-8 LC_PAPER=en_US.UTF-8 LC_NAME=C LC_ADDRESS=C LC_TELEPHONE=C
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C

attached base packages: [1] stats graphics grDevices utils datasets methods base

other attached packages: [1] crosstalk_1.2.0 plotly_4.10.0 ggplot2_3.3.6 reactable_0.2.3.9000

loaded via a namespace (and not attached): [1] Rcpp_1.0.9 RColorBrewer_1.1-3 later_1.3.0 pillar_1.8.1 compiler_4.0.5 tools_4.0.5 digest_0.6.29
[8] jsonlite_1.8.0 lifecycle_1.0.1 tibble_3.1.8 gtable_0.3.0 viridisLite_0.4.1 pkgconfig_2.0.3 rlang_1.0.4
[15] shiny_1.7.1 cli_3.3.0 DBI_1.1.2 rstudioapi_0.13 yaml_2.3.5 fastmap_1.1.0 reactR_0.4.4
[22] withr_2.5.0 dplyr_1.0.9 httr_1.4.3 generics_0.1.3 vctrs_0.4.1 htmlwidgets_1.5.4 grid_4.0.5
[29] tidyselect_1.1.2 glue_1.6.2 data.table_1.14.2 R6_2.5.1 fansi_1.0.3 farver_2.1.1 purrr_0.3.4
[36] tidyr_1.2.0 magrittr_2.0.3 ellipsis_0.3.2 promises_1.2.0.1 scales_1.2.1 htmltools_0.5.2 assertthat_0.2.1
[43] xtable_1.8-4 mime_0.12 colorspace_2.0-3 httpuv_1.6.5 utf8_1.2.2 lazyeval_0.2.2 munsell_0.5.0

tbrittoborges avatar Aug 30 '22 14:08 tbrittoborges

I remember finding this weird behavior as well while adding the Crosstalk integration way back. There's a complicated reason why it works like this, and I unfortunately have no ideas for a good solution.

This is also kind of an issue with Crosstalk widgets in general, and happens with DT and Plotly, or DT and Leaflet on the Crosstalk home page: https://rstudio.github.io/crosstalk/index.html (filter the Leaflet map and then select a row in the DT table)

Since the issue is complicated, here's what happening behind the scenes in Crosstalk:

  1. Select a row in reactable: reactable performs a Crosstalk selection (linked brushing) on Plotly that highlights a point.
  2. Select a point in Plotly: Plotly clears the previous selection and performs a Crosstalk selection on reactable that filters rows.
  3. Select a row in reactable: reactable clears the previous Crosstalk selection and performs a Crosstalk selection on Plotly. Since the previous selection that filtered the table was cleared, the filtering is reset and the selected row potentially gets lost on a different page.

The main problem is that the Crosstalk selection behaviors aren't really congruent when coming in and out of a table. Crosstalk selections from the table are done by selecting table rows, but Crosstalk selections onto the table will actually filter rows rather than select rows.

Package authors can interpret Crosstalk selections however they want, but this was how DT worked, and I thought it made a lot of sense. Most users would probably expect selecting Plotly/Leaflet points to filter the table as well. But unfortunately, it can also cause weird behaviors like this where resetting an existing Crosstalk selection will appear to reset a completely different state. I.e., selecting a row won't reset a previous row selection, but will reset a previous row filter.

Thinking through a couple solutions:

  • We could make Crosstalk selection work consistently in reactable by having it select rows in the table rather than filtering rows. Then the rows wouldn't jump around as you apply a new Crosstalk selection. But this loses the nice behavior of having Plotly/Leaflet selections filter the table down to the selected rows.
  • Sorting selected entries at the top of the table is an interesting idea. I guess this problem most especially affects widgets that can't show all data points at once like paginated tables. A similar idea would be to automatically jump to the page with the selected row rather than start back at page 1. However, these seem tricky to implement, and I could imagine cases where a user wouldn't want this to happen:
    • If all rows already fit on the first page of the table, should the row still be sorted to the top? Or only for paginated tables?
    • Would users expect selected rows to be auto-sorted to the top when there's no existing Crosstalk selection? I'd imagine no, but the difference in row selection behaviors depending on whether there's an existing Crosstalk selection could be confusing as well
    • After a row gets sorted to the top, would users still want table sorting to include that row? Is that row only sorted to the top temporarily?
    • After a row gets sorted to the top, what happens if you select another row? Does that second row get sorted to the top as well, or left in place?

glin avatar Sep 24 '22 21:09 glin

Hi, @glin. Thank you for the detailed response. I understand this is a complicated issue, given multiple users expect different behaviors. What do you think about allowing users to configure the selection columns? We could then define the sortable and default sort value. This would require breaking the current behavior, by default.

I could help with the implementation, if necessary.

tbrittoborges avatar Sep 28 '22 13:09 tbrittoborges