简体   繁体   English

使用刷子和缩放时 Shiny 应用程序中的 DT 表上缺少搜索框

[英]Missing search box on DT table in Shiny app when using brushing and zooming

Following on from this post I am trying to find a way to search multiple items my datatable with spaces rather than pipes and was able to implement this as per the previous post.这篇文章之后,我试图找到一种方法来使用空格而不是管道搜索我的数据表中的多个项目,并且能够按照上一篇文章实现这一点。 Implementing this code into the following example works well:将此代码实现到以下示例中效果很好:

library(shiny)
library(DT)
library(shinythemes)


## ------------------------------------ functions
## JS for searching with spaces between items instead of pipes
callback <- '
$("div.search").append($("#mySearch"));
$("#mySearch").on("keyup redraw", function(){
  var splits = $("#mySearch").val().split(" ").filter(function(x){return x !=="";})
  var searchString = "(" + splits.join("|") + ")";
  table.search(searchString, true).draw(true);
});
'

## css styling 
CSS <- function(values, colors){
  template <- "
.option[data-value=%s], .item[data-value=%s]{
  background: %s !important;
  color: white !important;
}"
  paste0(
    apply(cbind(values, colors), 1, function(vc){
      sprintf(template, vc[1], vc[1], vc[2])
    }),
    collapse = "\n"
  )
}

## points to highlight
highlightOnPlot <- function(coords, fd, myfoi, labels = FALSE) {
  .data <- coords
  points(.data[myfoi, 1], .data[myfoi, 2], col = "white",
         pch = 21, cex = 1, lwd = 1.3)
  if (labels) {
    text(.data[myfoi, 1], .data[myfoi, 2], myfoi, pos = 3, font  = 2, cex = 1.2) 
  }
}


## ------------------------------------ data
## create dataset from iris
data(iris)
object <- iris
rownames(object) <- 1:nrow(object)
m <- object$Species
um <- levels(factor(m))
M <- matrix(0, nrow = nrow(object), ncol = length(um))
rownames(M) <- rownames(object)
colnames(M) <- um
for (j in um) M[which(j == m), j] <- 1
fd <- data.frame(markers = iris$Species, M)
## generate pca
coords <- prcomp(object[,1:4])$x[, 1:2]
rownames(coords) <- rownames(M)


## ------------------------------------ app settings
pmsel <- 1:ncol(M)
profs <- iris[, 1:4]
feats <- toSel <- c(1:ncol(fd))
idxDT <- numeric()
namesIdxDT <- character()
cols <- c("#E41A1C", "#377EB8", "#238B45", "#FF7F00")
fcol <- "markers"
css <- CSS(colnames(M), cols[seq(colnames(M))])






## ------------------------------------ UI
ui <- 
  shinyUI(
    tagList(
      navbarPage(
        theme = shinytheme("flatly"), "flatly theme",

        tabPanel("",
                 sidebarLayout(

                   ## sidebarPanel 
                   sidebarPanel(
                     tags$head(tags$style(HTML(css))),  
                     selectizeInput("markers", "Labels",
                                    choices = colnames(M),
                                    multiple = TRUE,
                                    selected = colnames(M)[pmsel])),

                   ## mainPanel 
                   mainPanel(
                     plotOutput("pca")

                   ) # end of mainPanel

                 ), # end of sidebarLayout

                 ## ------Datatable-----
                 tags$head(tags$style(HTML(".search {float: right;}"))),
                 br(),
                 tags$input(type = "text", id = "mySearch", placeholder = "Search"),
                 DT::dataTableOutput("fDataTable")      
        ) # end of tabPanel 
      )))



## ------------------------------------ SERVER
server <- 
  shinyServer(
    function(input, output, session) {

      ## Get coords for data according to selectized class(es)
      mrkSel <- reactive({lapply(input$markers, function(z) which(M[, z] == 1))})

      ## Update colours according to selected classes
      myCols <- reactive({cols[sapply(input$markers, function(z) 
        which(colnames(M) == z))]})

      ## PCA plot
      output$pca <- renderPlot({
        plot(x = coords[,1], y = coords[,2])
        if (!is.null(input$markers)) {
          for (i in 1:length(input$markers))
            points(coords[mrkSel()[[i]], ], col = myCols()[i], pch = 19)
        }
      })



      ## Feature data table
      output$fDataTable <- DT::renderDataTable({

        dtdata <- fd
        ## display datatable
        DT::datatable(data = dtdata,
                      rownames = TRUE,
                      options = list(
                        search = list(regex = TRUE, 
                                      caseInsensitive = TRUE),
                        dom = "l<'search'>rtip"
                      ),
                      selection = list(mode = 'multiple', selected = toSel),
                      callback = JS(callback))
      })

    })

shinyApp(ui, server)

在此处输入图像描述

I have quite a complicated app that uses brushing and zooming on multiple plots and have tried to simplify it here into a reproducible example.我有一个相当复杂的应用程序,它在多个绘图上使用刷子和缩放,并试图在此处将其简化为可重现的示例。 If I add in the brushing and zooming features, as per the below code, I lose the search box of my DT table.如果我按照下面的代码添加刷子和缩放功能,我会丢失 DT 表的搜索框。

Can anyone please advise how to rectify this?谁能告诉我如何纠正这个问题? (Apologies this is still code heavy but leaving out the brushing and zooming I can't reproduce the error.) (抱歉,这仍然是代码繁重,但忽略了刷牙和缩放我无法重现该错误。)

Many thanks in advance.提前谢谢了。

library(shiny)
library(DT)
library(shinythemes)


## ------------------------------------ functions
## JS for searching with spaces between items instead of pipes
callback <- '
$("div.search").append($("#mySearch"));
$("#mySearch").on("keyup redraw", function(){
  var splits = $("#mySearch").val().split(" ").filter(function(x){return x !=="";})
  var searchString = "(" + splits.join("|") + ")";
  table.search(searchString, true).draw(true);
});
'

## css styling 
CSS <- function(values, colors){
  template <- "
.option[data-value=%s], .item[data-value=%s]{
  background: %s !important;
  color: white !important;
}"
  paste0(
    apply(cbind(values, colors), 1, function(vc){
      sprintf(template, vc[1], vc[1], vc[2])
    }),
    collapse = "\n"
  )
}

## points to highlight
highlightOnPlot <- function(coords, fd, myfoi, labels = FALSE) {
  .data <- coords
  points(.data[myfoi, 1], .data[myfoi, 2], col = "white",
         pch = 21, cex = 1, lwd = 1.3)
  if (labels) {
    text(.data[myfoi, 1], .data[myfoi, 2], myfoi, pos = 3, font  = 2, cex = 1.2) 
  }
}


## ------------------------------------ data
## create dataset from iris
data(iris)
object <- iris
rownames(object) <- 1:nrow(object)
m <- object$Species
um <- levels(factor(m))
M <- matrix(0, nrow = nrow(object), ncol = length(um))
rownames(M) <- rownames(object)
colnames(M) <- um
for (j in um) M[which(j == m), j] <- 1
fd <- data.frame(markers = iris$Species, M)
## generate pca
coords <- prcomp(object[,1:4])$x[, 1:2]
rownames(coords) <- rownames(M)


## ------------------------------------ app settings
pmsel <- 1:ncol(M)
profs <- iris[, 1:4]
feats <- toSel <- c(1:ncol(fd))
idxDT <- numeric()
namesIdxDT <- character()
cols <- c("#E41A1C", "#377EB8", "#238B45", "#FF7F00")
fcol <- "markers"
css <- CSS(colnames(M), cols[seq(colnames(M))])






## ------------------------------------ UI
ui <- 
shinyUI(
  tagList(
    navbarPage(
      theme = shinytheme("flatly"), "flatly theme",

      tabPanel("",
               sidebarLayout(

                 ## sidebarPanel 
                 sidebarPanel(
                   tags$head(tags$style(HTML(css))),  
                   selectizeInput("markers", "Labels",
                                  choices = colnames(M),
                                  multiple = TRUE,
                                  selected = colnames(M)[pmsel]),
                   br(),
                   actionButton("resetButton", "Zoom/reset plot"),
                   br(),
                   actionButton("clear", "Clear selection"),
                   width = 3),

                 ## mainPanel 
                 mainPanel(
                   plotOutput("pca",
                              dblclick = "dblClick",
                              brush = brushOpts(id = "pcaBrush", resetOnNew = TRUE))
                 ) # end of mainPanel

               ), # end of sidebarLayout

               ## ------Datatable-----
               tags$head(tags$style(HTML(".search {float: right;}"))),
               br(),
               tags$input(type = "text", id = "mySearch", placeholder = "Search"),
               DT::dataTableOutput("fDataTable")      
      ) # end of tabPanel 
)))



## ------------------------------------ SERVER
server <- 
  shinyServer(
    function(input, output, session) {

      ## settings for brushing on the plot
      ranges <- reactiveValues(x = NULL, y = NULL)
      brushBounds <- reactiveValues(i =  try(coords[, 1] >= min(coords[, 1]) &
                                               coords[, 1] <= max(coords[, 1])),
                                    j = try(coords[, 2] >= min(coords[, 2]) &
                                              coords[, 2] <= max(coords[, 2])))
      resetLabels <- reactiveValues(logical = FALSE)

      ## Get coords for data according to selectized class(es)
      mrkSel <- reactive({lapply(input$markers, function(z) which(M[, z] == 1))})

      ## Update colours according to selected classes
      myCols <- reactive({cols[sapply(input$markers, function(z) 
        which(colnames(M) == z))]})


      ## PCA plot
      output$pca <- renderPlot({

        plot(x = coords[,1], y = coords[,2], 
             xlim = ranges$x, ylim = ranges$y)
        if (!is.null(input$markers)) {
          for (i in 1:length(input$markers))
            points(coords[mrkSel()[[i]], ], col = myCols()[i], pch = 19)
        }

        ## highlight point on plot by selecting item in table
        idxDT <<- feats[input$fDataTable_rows_selected]
        if (resetLabels$logical) idxDT <<- numeric()  ## If TRUE labels are cleared
        namesIdxDT <<- names(idxDT)
        if (length(idxDT)) {
          highlightOnPlot(coords, fd, namesIdxDT)
          highlightOnPlot(coords, fd, namesIdxDT, labels = TRUE)
        }
        resetLabels$logical <- FALSE
      })



      ## Feature data table
      output$fDataTable <- DT::renderDataTable({

        ## Double clicking to identify point
        feats <<- which(brushBounds$i & brushBounds$j)
        if (!is.null(input$dblClick)) {
          dist <- apply(coords, 1, function(z) sqrt((input$dblClick$x - z[1])^2
                                                           + (input$dblClick$y - z[2])^2))
          idxPlot <- which(dist == min(dist))
          if (idxPlot %in% idxDT) {                          ## 1--is it already clicked?
            setsel <- setdiff(names(idxDT), names(idxPlot))  ## Yes, remove it from table
            idxDT <<- idxDT[setsel]
          } else {                                           ## 2--new click?
            idxDT <<- c(idxDT, idxPlot)                      ## Yes, highlight it to table
          }
        }
        namesIdxDT <<- names(idxDT)
        toSel <- match(namesIdxDT, rownames(fd)[brushBounds$i & brushBounds$j])
        if (resetLabels$logical) toSel <- numeric()
        dtdata <- fd
        dtdata <- dtdata[brushBounds$i & brushBounds$j, ]

        ## display datatable
        DT::datatable(data = dtdata,
                      rownames = TRUE,
                      options = list(
                        search = list(regex = TRUE, 
                                      caseInsensitive = TRUE),
                        dom = "l<'search'>rtip"
                      ),
                      selection = list(mode = 'multiple', selected = toSel),
                      callback = JS(callback))
      })


      ## When a the reset button is clicked check to see is there is a brush on
      ## the plot, if yes zoom, if not reset the plot.
      observeEvent(input$resetButton, {
        brush <- input$pcaBrush
        if (!is.null(brush)) {
          ranges$x <- c(brush$xmin, brush$xmax)
          ranges$y <- c(brush$ymin, brush$ymax)
          brushBounds$i <- coords[, 1] >= brush$xmin & coords[, 1] <= brush$xmax
          brushBounds$j <- coords[, 2] >= brush$ymin & coords[, 2] <= brush$ymax
        } else {
          ranges$x <- NULL
          ranges$y <- NULL
          brushBounds$i <- try(coords[, 1] >= min(coords[, 1])
                               & coords[, 1] <= max(coords[, 1]))
          brushBounds$j <- try(coords[, 2] >= min(coords[, 2])
                               & coords[, 2] <= max(coords[, 2]))
        }
      })

      ## Clear indices and reset clicked selection
      observeEvent(input$clear, {resetLabels$logical <- TRUE})

    })

shinyApp(ui, server)

在此处输入图像描述 SessionInfo会话信息

> sessionInfo()
R version 3.6.3 (2020-02-29)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS High Sierra 10.13.6

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.6/Resources/lib/libRlapack.dylib

locale:
[1] en_GB.UTF-8/en_GB.UTF-8/en_GB.UTF-8/C/en_GB.UTF-8/en_GB.UTF-8

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

other attached packages:
[1] shinythemes_1.1.2 DT_0.13           shiny_1.4.0.2    

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.4.6      crayon_1.3.4      digest_0.6.25     later_1.0.0       mime_0.9          R6_2.4.1         
 [7] jsonlite_1.6.1    xtable_1.8-4      magrittr_1.5      rlang_0.4.5       rstudioapi_0.11   promises_1.1.0   
[13] tools_3.6.3       htmlwidgets_1.5.1 crosstalk_1.1.0.1 rsconnect_0.8.16  yaml_2.2.1        httpuv_1.5.2     
[19] fastmap_1.0.1     compiler_3.6.3    htmltools_0.4.0  

Thanks again.再次感谢。

When you play with the brushing/zooming, the renderDT reacts.当您使用刷子/缩放时, renderDT会做出反应。 I believe this destroys the previous table and also the text input mySearch because it is included in the datatable.我相信这会破坏前一个表以及文本输入mySearch ,因为它包含在数据表中。

I have not tried with a reactive datatable, but I think the following code should work.我没有尝试过使用反应式数据表,但我认为下面的代码应该可以工作。 The text input mySearch is created in the callback, so it should be recreated when a new table is created.文本输入mySearch是在回调中创建的,因此在创建新表时应该重新创建它。 So remove the tags$input as well as the CSS, because I set the CSS property float in the callback.所以删除tags$input以及 CSS,因为我在回调中设置了 CSS 属性float

library(shiny)
library(DT)

callback <- '
var x = document.createElement("INPUT");
x.setAttribute("type", "text");
x.setAttribute("id", "mySearch");
x.setAttribute("placeholder", "Search");
x.style.float = "right";
$("div.search").append($(x));
$("#mySearch").on("keyup redraw", function(){
  var splits = $("#mySearch").val().split(" ").filter(function(x){return x !=="";})
  var searchString = "(" + splits.join("|") + ")";
  table.search(searchString, true).draw(true);
});
'

ui <- fluidPage(
  #tags$head(tags$style(HTML(".search {float: right;}"))), --- REMOVE THAT
  br(),
  DTOutput("dtable")
)

server <- function(input, output){

  output[["dtable"]] <- renderDT({
    datatable(
      iris[c(1,2,51,52,101,102),],
      options = list(
        dom = "l<'search'>rtip"
      ),
      callback = JS(callback)
    )
  }, server = FALSE)

}

shinyApp(ui, server)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM