【问题标题】:dynamic ggplot layers in shiny with nearPoints()使用 nearPoints() 闪亮的动态 ggplot 图层
【发布时间】:2017-04-09 21:15:24
【问题描述】:

我熟悉闪亮的基础知识,但在这里遇到了一些困难。我希望能够在单击一个点以突出显示该点时添加一个 ggplot 图层。我知道 ggvis 可以做到这一点,并且图库中有一个很好的示例,但我希望能够使用 nearPoints() 将点击捕获为 ui 输入。

我尝试了一些东西(见下文),除了 ggplot 层出现然后消失。我已经尝试使用reactive()eventReactive() 等对此进行各种编辑。

非常感谢任何帮助...

library(shiny)
library(ggplot2)

shinyApp(
  ui = shinyUI(
        plotOutput("plot", click = "clicked")
    ),

  server = shinyServer(function(input, output) {
    output$plot <- renderPlot({
      ggplot(mtcars, aes(x = mpg, y = wt)) +
        geom_point() +
        geom_point(data = nearPoints(mtcars, input$clicked), colour = "red", size = 5)
    })
  })
)

我想我从概念上理解为什么这不起作用。该图依赖于input$clicked,这意味着当input$clicked 更改时,图会重新渲染,但这反过来又会重置input$clicked。有点捕获 22 的情况。

【问题讨论】:

    标签: r ggplot2 reactive-programming shiny


    【解决方案1】:

    请试试这个:

    方法 1(推荐)

    library(shiny)
    library(ggplot2)
    
    # initialize global variable to record selected (clicked) rows
    selected_points <- mtcars[0, ]
    str(selected_points)
    
    
    shinyApp(
      ui = shinyUI(
        plotOutput("plot", click = "clicked")
      ),
    
      server = shinyServer(function(input, output) {
    
        selected <- reactive({
          # add clicked
          selected_points <<- rbind(selected_points, nearPoints(mtcars, input$clicked))
          # remove _all_ duplicates if any (toggle mode) 
          # http://stackoverflow.com/a/13763299/3817004
          selected_points <<- 
            selected_points[!(duplicated(selected_points) | 
                                duplicated(selected_points, fromLast = TRUE)), ]
          str(selected_points)
          return(selected_points)
        })
    
        output$plot <- renderPlot({
          ggplot(mtcars, aes(x = mpg, y = wt)) +
            geom_point() +
            geom_point(data = selected(), colour = "red", size = 5)
        })
      })
    )
    

    如果您单击一个点,它就会突出显示。如果您再次单击它,突出显示将再次关闭(切换)。

    代码使用全局变量 selected_points 来存储实际突出显示(选定)的点,并使用响应式表达式 selected() 在单击点时更新全局变量。

    str(selected_points) 可能有助于可视化工作,但可以删除。

    方法 2(替代)

    有一种稍微不同的方法,它使用observe() 而不是reactive() 并直接引用全局变量selected_points 而不是从函数中返回对象:

    library(shiny)
    library(ggplot2)
    
    selected_points <- mtcars[0, ]
    str(selected_points)
    
    
    shinyApp(
      ui = shinyUI(
        plotOutput("plot", click = "clicked")
      ),
    
      server = shinyServer(function(input, output) {
    
        observe({
          # add clicked
          selected_points <<- rbind(selected_points, nearPoints(mtcars, input$clicked))
          # remove _all_ duplicates (toggle)
          # http://stackoverflow.com/a/13763299/3817004
          selected_points <<- 
            selected_points[!(duplicated(selected_points) | 
                                duplicated(selected_points, fromLast = TRUE)), ]
          str(selected_points)
        })
    
        output$plot <- renderPlot({
          # next statement is required for reactivity
          input$clicked
          ggplot(mtcars, aes(x = mpg, y = wt)) +
            geom_point() +
            geom_point(data = selected_points, colour = "red", size = 5)
        })
      })
    )
    

    当然,你可以直接在ggplot调用中使用全局变量selected_points,而不是调用反应函数selected()。但是,您必须确保在更改 input$clicked 时执行 renderPlot()。因此,input$clicked 的虚拟引用必须包含在 renderPlot() 内的代码中。

    现在,反应函数 selected() 不再需要,可以替换为 observe() 表达式。与reactive() 不同,observe() 不返回值。只要修改了input$clicked,它就会更新全局变量selected_points

    方法 3(反应值)

    这种方法避免了全局变量。相反,它使用reactiveValues 创建一个类似列表的对象rv,具有响应式编程的特殊功能(请参阅?reactiveValues)。

    library(shiny)
    library(ggplot2)
    
    shinyApp(
      ui = shinyUI(
        plotOutput("plot", click = "clicked")
      ),
    
      server = shinyServer(function(input, output) {
    
        rv <- reactiveValues(selected_points = mtcars[0, ])
    
        observe({
          # add clicked
          rv$selected_points <- rbind(isolate(rv$selected_points), 
                                               nearPoints(mtcars, input$clicked))
          # remove _all_ duplicates (toggle)
          # http://stackoverflow.com/a/13763299/3817004
          rv$selected_points <- isolate(
            rv$selected_points[!(duplicated(rv$selected_points) | 
                                   duplicated(rv$selected_points, fromLast = TRUE)), ])
          str(rv$selected_points)
        })
    
        output$plot <- renderPlot({
          ggplot(mtcars, aes(x = mpg, y = wt)) +
            geom_point() +
            geom_point(data = rv$selected_points, colour = "red", size = 5)
        })
      })
    )
    

    请注意,observer 部分中对rv 的引用需要封装在isolate() 中,以确保只有对input$clicked 的更改才会触发observer 中代码的执行。否则,我们将陷入无限循环。每当反应值rv 发生变化时,都会触发renderPlot 的执行。

    结论

    就个人而言,我更喜欢使用反应函数的方法 1,它使依赖关系(反应性)更加明确。我发现在方法 2 中对 input$clicked 的虚拟调用不太直观。方法 3 需要彻底了解反应性并在正确的位置使用 isolate()

    【讨论】:

    • 谢谢你,这是完美的。起初我不明白为什么第二个geom_point 层有数据源selected(),但现在我可以看到selected() 函数返回selected_points 对象。出于兴趣,我可以将selected_points 作为数据源而不从函数返回对象吗?如果是这样,你为什么要这样做而不是其他?
    • 嗯,这完全与反应性有关,或者当特定数据发生变化时(反应性)将执行哪段代码。是的,您可以使用selected_points,但您必须通过向input$clicked 添加一个虚拟调用来强制响应(请参阅我编辑的答案的方法2)。我更喜欢使用反应式函数,使依赖关系更加明确。
    • 附加问题:如何在此界面中添加“重置”按钮,取消选择所有选定点并允许用户重新开始而不选择任何点?
    • 回答我自己的问题...不确定这是否正确,但我使用方法 3 添加了一个重置​​按钮。将现有的 observe 更改为 observeEvent(input$clicked, {...}) 并添加 observeEvent(input$reset, { rv$selected_points &lt;- mtcars[0,] }) 其中input$resetactionButton("reset", "Reset Selection") 在用户界面部分。不错的答案!
    猜你喜欢
    • 2018-03-07
    • 2017-01-29
    • 1970-01-01
    • 2016-06-04
    • 2022-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多