简体   繁体   English

R-Shiny应用程序的动态选项卡使用相同的输出功能

[英]Dynamic Tabs with R-Shiny app using the same output function

Goal: I'm working on a bioinformatics project. 目标:我正在从事生物信息学项目。 I'm currently trying to implement R code that dynamically creates tabPanels (they are essentially carbon copies except for the data output). 我目前正在尝试实现可动态创建tabPanels的R代码(除数据输出外,它们实质上是复本)。

Implementation: After doing some research I implemented this solution. 实施:经过研究后,我实施了解决方案。 It works in a way (the panels that I'm "carbon copying" are created), but the data that I need cannot be displayed. 它以某种方式工作(创建了我正在“抄写”的面板),但是无法显示我需要的数据。

Problem: I'm sure that the way I'm displaying my data is fine. 问题:我确定显示数据的方式很好。 The problem is that I can't use the same output function to display the data as seen here . 问题是,我不能使用相同的输出功能看到显示的数据在这里 So let me get to the code... 所以让我来看看代码...

ui.R ui.R

library(shiny)
library(shinythemes)
library(dict)
library(DT)
...# Irrelevant functions removed #...

geneinfo <- read.table(file = "~/App/final_gene_info.csv",
                   header = TRUE,
                   sep = ",",
                   na.strings = "N/A",
                   as.is = c(1,2,3,4,5,6,7))


ui <- navbarPage(inverse = TRUE, "GENE PROJECT",
                theme = shinytheme("cerulean"),
                 tabPanel("Home",
                          #shinythemes::themeSelector(),
                          fluidPage(
                            includeHTML("home.html")
                            )),
                  tabPanel("Gene Info",
                          h2('Detailed Gene Information'),
                          DT::dataTableOutput('table')),
                 tabPanel("File Viewer",
                          sidebarLayout(
                            sidebarPanel(
                              selectizeInput(inputId = "gene", label = "Choose a Gene", choice = genes, multiple = TRUE),
                              selectInput(inputId = "organism", label = "Choose an Organism", choice = orgs),
                              selectInput(inputId = "attribute", label = "Choose an Other", choice = attributes),
                              width = 2),
                            mainPanel(
                              uiOutput('change_tabs'),
                            width = 10))),
                 tabPanel("Alignment")

)

I'm using uiOutput to generate tabs dynamically on the server side.... 我正在使用uiOutput在服务器端动态生成选项卡。

server.R server.R

server <- function (input, output, session) {

  # Generate proper files from user input
  fetch_files <- function(){
    python <- p('LIB', 'shinylookup.py', python=TRUE)
    system(sprintf('%s %s %s', python, toString(genie), input$organism), wait = TRUE)
    print('Done with Python file generation.')

  # Fetch a temporary file for data output

  fetch_temp <- function(){

    if(input$attribute != 'Features'){
      if(input$attribute != 'Annotations'){
        chosen <- toString(attribute_dict[[input$attribute]])

      }
      else{
        chosen <- toString(input$sel)
        extension <<- '.anno'
      }
    }
    else{
      chosen <- toString(input$sel)
      extension <<- '.feat'
    }
    count = 0
    oneline = ''
    f <- paste(toString(genie), toString(input$organism), sep = '_')
    f <- paste(f, extension, sep = '')

    # Writes a temporary file to display output to the UI

    target <- p('_DATA', f)
    d <- dict_fetch(target)
    temp_file <- tempfile("temp_file", p('_DATA', ''), fileext = '.txt')
    write('', file=temp_file)
    vectorofchar <- strsplit(toString(d[[chosen]]), '')[[1]]
    for (item in vectorofchar){
      count = count + 1
      oneline = paste(oneline, item, sep = '')

      # Only 60 characters per line (Find a better solution)
      if (count == 60){ 
        write(toString(oneline), file=temp_file, append=TRUE)
        oneline = ''
        count = 0
      }
    }
    write(toString(oneline), file=temp_file, append=TRUE)
    return(temp_file)
  }

  # Get the tabs based on the number of genes selected in the UI
  fetch_tabs <- function(Tabs, OId, s = NULL){
    count = 0

    # Add a select input or nothing at all based on user input
    if(is.null(s)==FALSE){
      selection <- select(s)
      x <- selectInput(inputId = 'sel', label = "Choose an Annotation:", choices = selection$keys())
    }
    else
      x <- ''

    for(gene in input$gene){
      if(count==0){myTabs = character()}
      count = count + 1
      genie <<- gene
      fetch_files()
      file_tab <- lapply(sprintf('File for %s', gene), tabPanel
                                          fluidRow(
                                            titlePanel(sprintf("File for %s:", gene)),
                                            column(5,
                                                   pre(textOutput(outputId = "file")),offset = 0))
                             )

      addTabs <- c(file_tab, lapply(sprintf('%s for %s',paste('Specific', Tabs), gene), tabPanel,
                                       fluidRow(
                                           x,
                                         titlePanel(sprintf("Attribute for %s:", gene)),
                                         column(5,
                                                pre(textOutput(outputId = OId), offset = 0)))
                                       ))
      # Append additional tabs every iteration
      myTabs <- c(myTabs, addTabs)
    }
    return(myTabs)
  }
  # Select the proper file and return a dictionary for selectInput
  select <- function(ext, fil=FALSE){
    f <- paste(toString(genie), toString(input$organism), sep = '_')
    f <- paste(f, ext, sep = '')
    f <- p('_DATA', f)
    if(fil==FALSE){
      return(dict_fetch(f))
    }
    else if(fil==TRUE){
      return(toString(f))
    }
  }

  # Output gene info table
  output$table <- DT::renderDataTable(
    geneinfo,
    filter = 'top',
    escape = FALSE,
    options = list(autoWidth = TRUE,
                   options = list(pageLength = 10),
                   columnDefs = list(list(width = '600px', targets = c(6))))
  )
    observe({

      x <- geneinfo[input$table_rows_all, 2]
      if (is.null(x))
        x <- genes
      updateSelectizeInput(session, 'gene', choices = x)
    })


  # Output for the File tab
  output$file <- renderText({
    extension <<- '.gbk'
    f <- select(extension, f=TRUE)
    includeText(f)
  })
  # Output for attributes with ony one property
  output$attributes <- renderText({
    extension <<- '.kv'
    f <- fetch_temp()
    includeText(f)
  })
  # Output for attributes with multiple properties (features, annotations)
  output$sub <- renderText({
    f <- fetch_temp()
    includeText(f)
  })

  # Input that creates tabs and selectors for more input
  output$change_tabs <- renderUI({

    # Fetch all the appropriate files for output
    Tabs = input$attribute

    if(input$attribute == 'Annotations'){
      extension <<- '.anno'
      OId = 'sub'
      s <- extension
    }
    else if(input$attribute == 'Features'){
      extension <<- '.feat'
      OId = 'sub'
      s <- extension
    }
    else{
      OId = 'attributes'
      s <- NULL
    }
    myTabs <- fetch_tabs(Tabs, OId, s = s)
    do.call(tabsetPanel, myTabs)
  })
}
)

Explanation: Now I'm aware that there's a lot to look at here.. But my problem exists within output$change_tabs (it's the last function), which calls fetch_tabs() . 说明:现在我知道这里有很多需要注意的地方。但是我的问题存在于output $ change_tabs (这是最后一个函数)中,该函数调用fetch_tabs() Fetch tabs uses the input$gene (a list of genes via selectizeInput(multiple=TRUE) ) to dynamically create a set of 2 tabs per gene selected by the user. 提取选项卡使用input $ gene (通过selectizeInput(multiple = TRUE)列出的基因)来动态创建用户选择的每个基因2个选项卡的集合。

What's Happening: So if the user selects 2 genes then 4 tabs are created. 发生了什么:因此,如果用户选择2个基因,则会创建4个标签。 With 5 genes 10 tabs are created... And so on and so forth... Each tab is EXACTLY THE SAME, except for the data. 利用5个基因,创建了10个标签...依此类推,等等...除了数据之外,每个标签都是一样的。

Roadblocks: BUT... for each tab I'm trying to use the same output Id (since they are EXACTLY THE SAME) for the data that I want to display ( textOutput(outputId = "file") ). 障碍:但是...对于每个选项卡,我都试图为我要显示的数据使用相同的输出ID(因为它们完全相同)( textOutput(outputId =“ file”) )。 As explained above in the second link, this simply does not work because HTML. 如上面在第二个链接中所述,这根本就行不通,因为HTML。

Questions: I've tried researching several solutions, but I would rather not have to implement this solution. 问题:我曾尝试研究几种解决方案,但我宁愿不必实施解决方案。 I don't want to have to rewrite so much code. 我不想重写太多的代码。 Is there any way I can add a reactive or observer function that can wrap or fix my output$file function? 有什么办法可以添加可包装或修复我的output $ file函数的反应或观察器函数? Or is there a way for me to add information to my tabs after the do.call(tabsetPanel, myTabs) ? 还是有一种方法可以在do.call(tabsetPanel,myTabs)之后向我的标签添加信息? Am I thinking about this the right way? 我在考虑正确的方法吗?

I'm aware that my code isn't commented very well so I apologize in advance. 我知道我的代码注释不佳,所以我事先表示歉意。 Please feel free to critique my coding style in the comments, even if you don't have a solution. 即使您没有解决方案,也请随时在评论中批评我的编码风格。 Please and thank you! 谢谢,麻烦您了!

I've come up with a very VERY crude answer that will work for now... 我已经提出了一个非常非常粗略的答案,该答案现在可以使用...

Here is the answer from @BigDataScientist 这是@BigDataScientist答案

My Issue with BigDataScientist's Answer: 我的BigDataScientist问题解答:

I can't dynamically pass data to the outputs. 我无法将数据动态传递到输出。 The output functions are not interpreted until they are needed... So if I wanted to pass the for loop iterator that you created (iter) into the dynamically created outputs, then I wouldn't be able to do that. 在需要输出函数之前,它们不会被解释...因此,如果我想将您创建的for循环迭代器(iter)传递到动态创建的输出中,那么我将无法做到这一点。 It can only take static data 它只能获取静态数据

My Solution: 我的解决方案:
I end up taking advantage of sys.calls() solution I found here in order to get the name of the function as a string. 最后,我利用了在这里找到的sys.calls()解决方案,以便以字符串形式获取函数的名称。 The name of the function has the info I need (in this case a number). 函数的名称包含我需要的信息(在这种情况下为数字)。

library(shiny)
library(shinythemes)
myTabs <<- list()
conv <- function(v1) {
  deparse(substitute(v1))
}
ui <- navbarPage(inverse = TRUE, "GENE PROJECT",
                 theme = shinytheme("cerulean"),
                 tabPanel("Gene Info",

                          sidebarLayout(
                            sidebarPanel(
                              sliderInput("bins",
                                          "Number of bins:",
                                          min = 1,
                                          max = 5,
                                          value = 3)
                            ),

                            # Show a plot of the generated distribution
                            mainPanel(
                              uiOutput('changeTab')
                            )
                          )
                 )
)


server <- function(input, output) {
  observe({
    b <<- input$bins
    myTabs <<- list()
    # Dynamically Create output functions
    # Dynamically Create formatted tabs
    # Dynamically Render the tabs with renderUI
    for(iter in 1:b){
      x <<- iter
      output[[sprintf("tab%s", iter)]] <- renderText({
        temp <- deparse(sys.calls()[[sys.nframe()-3]])
        x <- gsub('\\D','',temp)
        x <- as.numeric(x)
        f <- sprintf('file%s.txt', x)
        includeText(f)
      })
      addTabs <<- lapply(sprintf('Tab %s', iter), tabPanel,
                        fluidRow(
                          titlePanel(sprintf("Tabble %s:", iter)),
                          column(5,
                                 pre(textOutput(outputId = sprintf('%s%s','tab', iter))))))
      myTabs <<- c(myTabs, addTabs)
    }
    myTabs <<- c(myTabs, selected = sprintf('Tab %s', x))

    output$changeTab <- renderUI({
      do.call(tabsetPanel, myTabs)

    })
  })

}


# Run the application 
shinyApp(ui = ui, server = server)

I think your being a victim of this behavior . 我认为您是这种行为的受害者。 Try: 尝试:

for (el in whatever) {
  local({
    thisEl <- el
    ...
 })
} 

like Joe suggests in the first reply to the Github issue I linked to. 就像乔在我链接到的Github问题的第一个回复中所建议的那样。 This is only necessary if you're using a for loop. 仅在使用for循环时才需要。 lapply already takes el as an argument, so you get this "dynamic evaluation" benefit (for lack of a better name) for free. lapply已经将el作为参数,因此您可以免费获得此“动态评估”好处(由于缺少更好的名称)。

For readability, I'm going to quote most of Joe's answer here: 为了提高可读性,我将在此处引用Joe的大部分答案:

You're the second person at useR that I talked to that was bitten by this behavior in R. It's because all the iterations of the for loop share the same reference to el. 您是在useR上与我交谈过的第二个人,这是R中的这种行为所困扰。这是因为for循环的所有迭代都共享对el的相同引用。 So when any of the created reactive expressions execute, they're using whatever the final value of el was. 因此,当执行任何创建的反应式表达式时,它们都会使用el的最终值。

You can fix this either by 1) using lapply instead of a for loop; 您可以通过以下方法解决此问题:1)使用lapply而不是for循环; since each iteration executes as its own function call, it gets its own reference to el; 由于每个迭代都作为其自己的函数调用执行,因此它获得对el的引用。 or 2) using a for loop but introducing a local({...}) inside of there, and creating a local variable in there whose value is assigned to el outside of the reactive. 或2)使用for循环,但在其中引入local({...}),并在其中创建一个局部变量,其值分配给React之外的el。

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

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