动态创建带有闪亮绘图的选项卡,而无需重新创建现有选项卡

Posted

技术标签:

【中文标题】动态创建带有闪亮绘图的选项卡,而无需重新创建现有选项卡【英文标题】:Dynamically creating tabs with plots in shiny without re-creating existing tabs 【发布时间】:2016-05-03 10:15:39 【问题描述】:

我想创建动态选项卡,每次用户单击按钮时,都会创建一个新选项卡。每个选项卡都有相同的内容,带有各种小部件,用户可以使用这些小部件来选择要绘制的数据集。

目前,我正在使用解决方案 here 来动态创建我的选项卡,但变化在于 lapply 正在调用一个调用 tabPanel 并向选项卡添加内容的函数

`

renderUI(
some_data <- # Dataframe that data is extracted goes here
createTabs <- function(tabNum, some_data)
    
      tabPanel(title = paste("Map", tabNum, sep=" "), 
               fluidRow(
                 column(
                   width = 3,
                   wellPanel(
                     #widgets are added here
    
 mTabs <- lapply(0:input$map, createTabs, some_data)
 do.call(tabsetPanel, mTabs)
)

`

以及here 发布的 for 循环方法以在每个选项卡上创建图。

但是,上面的 2 个解决方案似乎都不是创建新选项卡,而是重新创建所有现有选项卡。因此,如果当前打开了 10 个选项卡,则会重新创建所有 10 个选项卡。不幸的是,这也会重置每个选项卡上的所有用户设置(除了减慢应用程序的速度),并且必须采取额外的规定,如 here 所示,由于必须输入大量输入对象,这进一步减慢了应用程序的速度被创建。

我看到了一个菜单项的解决方案,似乎通过简单地将所有菜单项存储在一个列表中来解决这个问题,并且每次生成一个新的菜单项时,它都会简单地添加到列表中,以便所有其他现有的不需要创建项目。选项卡和渲染图也可以这样吗?

这是代码:

 newTabs <- renderMenu(
    menu_list <- list(
      menu_vals$menu_list)
    sidebarMenu(.list = menu_list)
  )

  menu_vals = reactiveValues(menu_list = NULL)
  observeEvent(eventExpr = input$placeholder,
               handlerExpr = 
                 menu_vals$menu_list[[input$placeholder]] <- menuSubItem(paste("Saved Simulation", length(menu_vals$menu_list) + 1, sep = " "),
                                                                                    tabName = paste("saved_sim", length(menu_vals$menu_list) + 1)) 
               )

如果有人可以向我解释 menu_list

编辑:我认为我能够防止每次创建新选项卡时重新创建绘图,并且还绕过了使用最大绘图数的需要

observeEvent(eventExpr = input$map,
                 handlerExpr = 
                   output[[paste0("outputComparePlot",simNum,"-",input$map)]] <- outputComparePlot(sessionEnv, config, react, input, simNum, input$map) #This function contains the call to renderPlot

                 )

但是,我仍然无法弄清楚如何使用它来创建标签。我尝试了同样的方法,但没有成功。

【问题讨论】:

那么reactiveValues中的menu_list是否与renderMenu中的相关? menu_vals$menu_list 中的 menu_list 呢?他们只是选择了相同的名称,还是在内部声明了一个列表? 【参考方案1】:

我想提出一个解决方案,该解决方案向闪亮添加一个功能,该解决方案早就应该在闪亮的基础中实现。 将 tabPanels 附加到现有 tabsetPanels 的函数。我已经尝试过类似的东西here 和here,但是这一次,我觉得这个解决方案更加稳定和通用。

对于此功能,您需要将 4 部分代码插入到闪亮的应用程序中。然后,您可以通过调用addTabToTabsetanytabPanels 添加到现有tabsetPanel 中,每个tabPanels 都具有任何内容。它的参数是tabPanel(或tabPanels列表)和目标tabsetPanel 的名称(id)。如果您只想添加普通的tabPanels,它甚至适用于navbarPage

应该复制粘贴的代码在“重要!”中厘米。

我的 cmets 可能不足以掌握真正发生的事情(当然还有为什么)。所以如果你想更详细,请留言,我会尽量详细说明。

复制粘贴运行播放!

library(shiny)

ui <- shinyUI(fluidPage(

  # Important! : javascript functionality to add the Tabs
  tags$head(tags$script(html("
    /* In coherence with the original Shiny way, tab names are created with random numbers. 
       To avoid duplicate IDs, we collect all generated IDs.  */
    var hrefCollection = [];

    Shiny.addCustomMessageHandler('addTabToTabset', function(message)
      var hrefCodes = [];
      /* Getting the right tabsetPanel */
      var tabsetTarget = document.getElementById(message.tabsetName);

      /* Iterating through all Panel elements */
      for(var i = 0; i < message.titles.length; i++)
        /* Creating 6-digit tab ID and check, whether it was already assigned. */
        do 
          hrefCodes[i] = Math.floor(Math.random()*100000);
         
        while(hrefCollection.indexOf(hrefCodes[i]) != -1);
        hrefCollection = hrefCollection.concat(hrefCodes[i]);

        /* Creating node in the navigation bar */
        var navNode = document.createElement('li');
        var linkNode = document.createElement('a');

        linkNode.appendChild(document.createTextNode(message.titles[i]));
        linkNode.setAttribute('data-toggle', 'tab');
        linkNode.setAttribute('data-value', message.titles[i]);
        linkNode.setAttribute('href', '#tab-' + hrefCodes[i]);

        navNode.appendChild(linkNode);
        tabsetTarget.appendChild(navNode);
      ;

      /* Move the tabs content to where they are normally stored. Using timeout, because
         it can take some 20-50 millis until the elements are created. */ 
      setTimeout(function()
        var creationPool = document.getElementById('creationPool').childNodes;
        var tabContainerTarget = document.getElementsByClassName('tab-content')[0];

        /* Again iterate through all Panels. */
        for(var i = 0; i < creationPool.length; i++)
          var tabContent = creationPool[i];
          tabContent.setAttribute('id', 'tab-' + hrefCodes[i]);

          tabContainerTarget.appendChild(tabContent);
        ;
      , 100);
    );
    "))),
  # End Important

  tabsetPanel(id = "mainTabset", 
    tabPanel("InitialPanel1", "Some Text here to show this is InitialPanel1", 
      actionButton("goCreate", "Go create a new Tab!"),
      textOutput("creationInfo")
    ),
    tabPanel("InitialPanel2", "Some Text here to show this is InitialPanel2 and not some other Panel")
  ),

  # Important! : 'Freshly baked' tabs first enter here.
  uiOutput("creationPool", style = "display: none;")
  # End Important

  ))

server <- function(input, output, session)

  # Important! : creationPool should be hidden to avoid elements flashing before they are moved.
  #              But hidden elements are ignored by shiny, unless this option below is set.
  output$creationPool <- renderUI()
  outputOptions(output, "creationPool", suspendWhenHidden = FALSE)
  # End Important

  # Important! : This is the make-easy wrapper for adding new tabPanels.
  addTabToTabset <- function(Panels, tabsetName)
    titles <- lapply(Panels, function(Panel)return(Panel$attribs$title))
    Panels <- lapply(Panels, function(Panel)Panel$attribs$title <- NULL; return(Panel))

    output$creationPool <- renderUI(Panels)
    session$sendCustomMessage(type = "addTabToTabset", message = list(titles = titles, tabsetName = tabsetName))
  
  # End Important 

  # From here: Just for demonstration
  output$creationInfo <- renderText(
    paste0("The next tab will be named NewTab", input$goCreate + 1)
  )

  observeEvent(input$goCreate, 
    nr <- input$goCreate

    newTabPanels <- list(
      tabPanel(paste0("NewTab", nr), 
        actionButton(paste0("Button", nr), "Some new button!"), 
        textOutput(paste0("Text", nr))
      ), 
      tabPanel(paste0("AlsoNewTab", nr), sliderInput(paste0("Slider", nr), label = NULL, min = 0, max = 1, value = 1))
    )

    output[[paste0("Text", nr)]] <- renderText(
      if(input[[paste0("Button", nr)]] == 0)
        "Try pushing this button!"
       else 
        paste("Button number", nr , "works!")
      
    )

    addTabToTabset(newTabPanels, "mainTabset")
  )


shinyApp(ui, server)

【讨论】:

非常好,感谢分享代码!有没有办法清除所有的 newTabPanels? @JorgeSepulveda 新创建的 tabPanel 与您在 shinyUI 中的初始程序启动时绘制的 tabPanel 没有什么不同。所以你问,tabPanels 一般是否可以动态删除? @k-rohde 我在问是否有办法让“creationPool”面板消失(或根据输入列表替换为新面板)。我试过removeUI('creationPool'),但它不起作用。制作newTabPanels &lt;- NULL 并运行addTabToTabset(newTabPanels, "mainTabset") 也不会这样做。感谢您的帮助! @JorgeSepulveda creationPool 不是元素所在的位置,也不是元素之一。过程是:1)在creationPool中创建元素,2)从创建池中移除元素并将其移动到tabPanels。此过程完成后,creationPool 中就没有任何剩余了。你想要做的是,你想删除 tabPanel 本身。这个问题更笼统,因为它需要在所有可能的 tabPanel 上工作。它涉及删除 tabPanel、导航链接并可能销毁链接到选项卡的所有观察者。 非常感谢您的代码。您可以将其作为拉取请求提交给 Shiny 存储库吗? github.com/rstudio/shiny/pulls【参考方案2】:

可能要感谢@k-rohde,Shiny 现在原生提供了一组方法来在选项卡集中添加/删除/附加选项卡:

library(shiny)
runApp(list(
  ui=fluidPage(
    fluidRow(
      actionLink("newTab", "Append tab"),
      actionLink("removeTab", "Remove current tab")
    ),
    tabsetPanel(id="myTabs", type="pills")
  ),
  server=function(input, output, session)
    tabIndex <- reactiveVal(0)
    observeEvent(input$newTab, 
      tabIndex(tabIndex() + 1)
      appendTab("myTabs", tabPanel(tabIndex(), tags$p(paste("I'm tab", tabIndex()))), select=TRUE)
    )
    observeEvent(input$removeTab, 
      removeTab("myTabs", target=input$myTabs)
    )
  
))

【讨论】:

以上是关于动态创建带有闪亮绘图的选项卡,而无需重新创建现有选项卡的主要内容,如果未能解决你的问题,请参考以下文章

带有用户单击所选组件的动态选项卡

将单选按钮选项卡动态链接到内容 div

使用嵌套在闪亮应用程序的可反应详细信息函数中的 tabsetPanel 的意外选项卡行为

使用闪亮动态地将绘图添加到网页

每 x 秒执行一次 Python 而无需重新打开选项卡

Ajax 在 div 中加载页面内容,但在右键单击时保持“在新选项卡中打开”