DT icon indicating copy to clipboard operation
DT copied to clipboard

Can't make first column selectable when rownames is FALSE

Open bhogan-mitre opened this issue 3 years ago • 5 comments

It seems there is some inconsistent behavior of the indexing in selectable when rownames is FALSE.

Suppose I want to make a few cells in the first column selectable by referencing column 0. The following throws an error that "selectable must be either all positive or all non-positive values"

library(shiny)
library(DT)

shinyApp(
  ui = fluidPage(fluidRow(column(12, DTOutput('tbl')))),
  server = function(input, output) {
    output$tbl = renderDT(
      iris, 
      rownames = FALSE,
      selection = list(mode = 'single', target = 'cell', 
                       selectable = cbind(1:5,0))
    )
  }
)
Screen Shot 2021-11-24 at 12 32 04 AM

Yet if I reference column 1, the second data column is selectable implying zero-based indexing is respected.

library(shiny)
library(DT)

shinyApp(
  ui = fluidPage(fluidRow(column(12, DTOutput('tbl')))),
  server = function(input, output) {
    output$tbl = renderDT(
      iris, 
      rownames = FALSE,
      selection = list(mode = 'single', target = 'cell', 
                       selectable = cbind(1:5,1))
    )
  }
)
Screen Shot 2021-11-24 at 12 30 45 AM

The behavior is slightly different but still inconsistent when selecting by column.

When rownames are included, making column 0 selectable is interpreted as the first data column (not counting the rowname column).

library(shiny)
library(DT)

shinyApp(
  ui = fluidPage(fluidRow(column(12, DTOutput('tbl')))),
  server = function(input, output) {
    output$tbl = renderDT(
      iris, 
      rownames = TRUE,
      selection = list(mode = 'single', target = 'column', selectable = 0)
    )
  }
)
Screen Shot 2021-11-24 at 12 39 51 AM

But if rownames are off, requesting column 0 to be selectable results in all other columns except the first data column being selectable.

library(shiny)
library(DT)

shinyApp(
  ui = fluidPage(fluidRow(column(12, DTOutput('tbl')))),
  server = function(input, output) {
    output$tbl = renderDT(
      iris, 
      rownames = FALSE,
      selection = list(mode = 'single', target = 'column', selectable = 0)
    )
  }
)
Screen Shot 2021-11-24 at 12 41 22 AM
> xfun::session_info('DT')
R version 4.0.4 (2021-02-15)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS Big Sur 11.6.1, RStudio 2021.9.0.351

Locale: en_US.UTF-8 / en_US.UTF-8 / en_US.UTF-8 / C / en_US.UTF-8 / en_US.UTF-8

Package version:
  base64enc_0.1.3   crosstalk_1.2.0   digest_0.6.28     DT_0.20.1        
  fastmap_1.1.0     graphics_4.0.4    grDevices_4.0.4   htmltools_0.5.2  
  htmlwidgets_1.5.4 jquerylib_0.1.4   jsonlite_1.7.2    later_1.3.0      
  lazyeval_0.2.2    magrittr_2.0.1    methods_4.0.4     promises_1.2.0.1 
  R6_2.5.1          Rcpp_1.0.7        rlang_0.4.12      stats_4.0.4      
  utils_4.0.4       yaml_2.2.1       

By filing an issue to this repo, I promise that

  • [x] I have fully read the issue guide at https://yihui.name/issue/.
  • [x] I have provided the necessary information about my issue.
    • If I'm asking a question, I have already asked it on Stack Overflow or RStudio Community, waited for at least 24 hours, and included a link to my question there.
    • If I'm filing a bug report, I have included a minimal, self-contained, and reproducible example, and have also included xfun::session_info('DT'). I have upgraded all my packages to their latest versions (e.g., R, RStudio, and R packages), and also tried the development version: remotes::install_github('rstudio/DT').
    • If I have posted the same issue elsewhere, I have also mentioned it in this issue.
  • [x] I have learned the Github Markdown syntax, and formatted my issue correctly.

I understand that my issue may be closed if I don't fulfill my promises.

bhogan-mitre avatar Nov 24 '21 05:11 bhogan-mitre

This is actually expected... However, I do admit that it's very confusing...

The thing is that the zero index is treated as a "negative number" which means "non-selectable".

So,

  • 0 can't be used with other positive numbers, like cbind(1, 0)
  • "rowname = true & column with selectable = 0" means the rowname column is non-selectable, so you can select column 1 and plus
  • "rowname = false & column with selectable = 0" means the first column is non-selectable, so you can only select column 2 and plus

I do agree that with rownames=FALSE and selection.mode='cell', making the cells in the first column being selectable is difficult, as you have to construct a big non-selectable matrix.

As of your example, the solution would be below:

library(shiny)
library(DT)

mat = expand.grid(seq_len(nrow(iris)), seq_len(ncol(iris)) - 1) 
mat = as.matrix(mat)
mat = mat[-(1:5),]

shinyApp(
  ui = fluidPage(fluidRow(column(12, DTOutput('tbl')))),
  server = function(input, output) {
    output$tbl = renderDT(
      iris, 
      rownames = FALSE,
      selection = list(mode = 'single', target = 'cell', 
                       selectable = -mat)
    )
  }
)

Finally, if you have any ideas on how to improve this behavior, we're happy to hear.

shrektan avatar Nov 26 '21 16:11 shrektan

Thank you for the detailed response! I agree that it's clunky to have to input a matrix of all other indices in order to apply this option on the first column. Although I don't have any obvious suggestions to get around that.

It'd be nice to have a consistent API e.g. with one-based column indexing in R and then do the accounting to provide zero-based indices to JS behind the scenes. I know that would mean selection runs counter to the zero-based JS column indices needed in options, which doesn't seem great. But a bonus would mean rows and columns both use consistent one-based indices in selection on the R side, which could make the usage less confusing (especially for matrix examples).

I would just push back a bit on the assumption in the original PR that row names are usually shown and thus desired to be not selectable.

Thanks again and I appreciate all of your work on this package!

bhogan-mitre avatar Dec 03 '21 03:12 bhogan-mitre

What currently worked for me is keeping rownames = TRUE, but setting all names to an empty string and making the column as small as possible.

Note that rownames(data) <- rep("", nrow(data)) does not work since rownames are expected to be unique.

MWE:

library(shiny)
library(DT)

shinyApp(
    ui = fluidPage(fluidRow(column(12, DTOutput('tbl')))),
    server = function(input, output) {
      output$tbl = renderDT({
            data <- iris
            attr(data, "row.names") <- rep("", nrow(data))
            
            datatable(
                data, 
                rownames = TRUE,
                options = list(
                    columnDefs = list(
                        list(
                            width = '0px',
                            targets = c(0)) # make row names as small as possible
                    )
                ),
                selection = list(mode = 'single', target = 'cell', 
                    selectable = cbind(1:5,1)
                )
            )
          }
      )
    }
)

Screenshot from 2023-08-25 10-24-14

Note that there still is a visible, yet barely noticeable, column on the left. It's not really resolving the core issue, but at least easy to implement/understand as a workaround.

JasperSch avatar Aug 25 '23 08:08 JasperSch

Can't we simply hide the row names column (visible=FALSE)?

stla avatar Aug 25 '23 12:08 stla

@stla

That's indeed the better approach, thank you!

FWIW, I now realized I was working on a package where this trick was used:

https://stackoverflow.com/questions/60406027/how-to-disable-double-click-reactivity-for-specific-columns-in-r-datatable

CSS <- "
table tbody td:nth-child(1) {pointer-events: none;}
table tbody td:nth-child(1)>div {pointer-events: auto;}
"

So please ignore my comment, I needlessly over-complicated things.

JasperSch avatar Aug 25 '23 15:08 JasperSch