闪亮:ggplot的动态颜色(填充)输入

5

我希望你能帮忙翻译一下这篇文章:Dynamic color input in shiny server,因为它并没有完全回答我的问题。

我想在我的shiny应用中实现动态颜色(填充)选择。我已经准备好了一个示例代码:

library(shiny)
library(shinyjs)
library(reshape2)
library(ggplot2)

dat <- data.frame(matrix(rnorm(60, 2, 3), ncol=3))
dat <- melt(dat)

runApp(shinyApp(
  ui = fluidPage(
    selectizeInput("select","Select:", choices=as.list(levels(dat$variable)), selected="X1",multiple =TRUE),
    uiOutput('myPanel'),
    plotOutput("plot"),
    downloadButton('downloadplot',label='Download Plot')
  ),
  server = function(input, output, session) {
    cols <- reactive({
      lapply(seq_along(unique(input$select)), function(i) {
        colourInput(paste("col", i, sep="_"), "Choose colour:", "black")        
      })
    })

    output$myPanel <- renderUI({cols()})

    cols2 <- reactive({        
      if (is.null(input$col_1)) {
        cols <- rep("#000000", length(input$select))
      } else {
        cols <- unlist(colors())
      }
      cols})

    testplot <- function(){
      dat <- dat[dat$variable %in% input$select, ]
      ggplot(dat, aes(x=variable,y=value, fill=cols2()[1])) + geom_boxplot()}

    output$plot <- renderPlot({testplot()})

    output$downloadplot <- downloadHandler(
      filename ="plot.pdf",
      content = function(file) {
        pdf(file, width=12, height=6.3)
        print(testplot())
        dev.off()
      })
  }
))

我希望用户可以选择箱线图的填充颜色。根据在selectizeInput("select"...中选择的变量数量,将出现相应数量的颜色小部件。到这一步为止,一切都运作得非常完美,但是我无法弄清楚如何将这种颜色应用于ggplot等等...
以下是我的问题:
  1. 我该如何正确地将填充颜色连接到ggplot

  2. 我能否使colourInput()的默认颜色对应于默认调色板(而不是一个颜色-->在我的情况下是黑色)

  3. colourInput(paste("col", i, sep="_"), "Choose colour:",中,我希望有与变量对应的名称(从selectizeInput中选择的变量,此处为X1、X2和X3),而不是“选择颜色”文本。

  4. 我还希望有一个按钮,可以重置所有选择的颜色

感谢您提前的帮助,希望这可以解决问题。
祝好

你想为不同的方框设置不同的颜色,但它们应该有默认值吗? - Michal Majka
是的,我希望在开始时有默认颜色(就像通常在选择填充参数为因子时,在ggplot中),但如果用户想要的话,他/她可以更改它(其中一个或全部)。因此,在此函数中,而不是黑色颜色:cols2 <- reactive({ if (is.null(input$col_1)) { cols <- rep("#000000",,将会是ggplot中默认调色板的第一种颜色,即红色。它可以是任何其他调色板,对我来说并不重要,我只是不想一开始全部都是黑色或单一颜色。 - Mal_a
好的,我会看一下 :) - Michal Majka
太好了!谢谢! :) 我只是想指出,它必须是颜色的自动分配(而不是像X1 =“red”,X2 =“green”这样的变量...),我的原始数据有很多变量,所以不可能像那样列出所有变量。 - Mal_a
aes_string(x="variable",y="value", fill=cols2()[1])替换aes(x=variable,y=value, fill=cols2()[1]),这样就可以了。 - NJBurgo
@NJBurgo,这在我的情况下不可能是解决方案,只是为了试试而已,结果出现了两个错误:Error in [[: subscript out of bounds...Warning: Error in [: object of type 'closure' is not subsettable...,并且没有任何绘图。 - Mal_a
1个回答

9
这些是非常好的具体问题,我很高兴能够回答它们 :)
  1. 如何正确地将填充颜色连接到 ggplot?
在这种情况下,我认为最好的方式是根据变量(响应式变量)填充方块,并添加一个新层的 scale_fill_manual,在其中为不同的方块指定自定义颜色。颜色的数量显然必须等于变量的级别数。这可能是最好的方法,因为您将始终拥有正确的图例。
ggplot(dat, aes(x = variable, y = value, fill = variable)) + 
          geom_boxplot() +
          scale_fill_manual(values = cols)

  1. 我能否使colorInput()的默认颜色与默认颜色调色板相对应(不是一个颜色 -> 在我的情况下是黑色)

当然可以。

首先,您需要了解ggplot使用的离散变量的默认颜色。为了生成这些颜色,我们将使用此处讨论中发现的函数gg_color_hue。我将其更改为gg_fill_hue以遵循ggplot约定。

我们可以在renderUI中编写所有代码,其中我们首先指定所选的级别/变量。为了消除由于动态(可能以不同顺序)生成的小部件而导致的歧义,我们对级别/变量的名称进行排序。

然后,我们使用gg_fil_hue生成适当数量的默认颜色,并将其分配给适当的小部件。

为了简化事情,我们将这些小部件的ID更改为col +“varname”,该名称由input$select给出

output$myPanel <- renderUI({ 
      lev <- sort(unique(input$select)) # sorting so that "things" are unambigious
      cols <- gg_fill_hue(length(lev))

      # New IDs "colX1" so that it partly coincide with input$select...
      lapply(seq_along(lev), function(i) {
        colourInput(inputId = paste0("col", lev[i]),
                    label = paste0("Choose colour for ", lev[i]), 
                    value = cols[i]
        )        
      })
    })

3.将colorInput(paste("col", i, sep="_"), "选择颜色:")中的“选择颜色”文本更改为对应变量(在此示例中为X1,X2和X3)的相应名称(从selectizeInput中选择的变量)。

在上面的代码中也完成了这个操作 - 简单地粘贴。


现在,让我们看一个由于生成的小部件数量动态而引起的非常重要的问题。我们必须根据唯一的colorInput设置盒子的颜色,可能有1、2或甚至10个这样的输入。

我认为,一种非常好的方法是创建一个字符向量,其中包含指定如何正常访问这些小部件的元素。在下面的示例中,该向量如下所示:c("input$X1", "input$X2", ...)

然后,使用非标准评估(evalparse),我们可以评估这些输入以获取选定颜色的向量,然后将其传递给scale_fill_manual层。

为了防止选择之间可能出现的错误,我们将使用函数'req'来确保颜色向量的长度与所选级别/变量的长度相同。

output$plot <- renderPlot({
        cols <- paste0("c(", paste0("input$col", sort(input$select), collapse = ", "), ")")
        # print(cols)
        cols <- eval(parse(text = cols))
        # print(cols)

        # To prevent errors
        req(length(cols) == length(input$select))

        dat <- dat[dat$variable %in% input$select, ]
        ggplot(dat, aes(x = variable, y = value, fill = variable)) + 
          geom_boxplot() +
          scale_fill_manual(values = cols)

    })

  1. 我希望有一个按钮,能够重置所有选择的颜色

在客户端定义actionButton并设置ID="reset"后,我们创建了一个观察器来更新colorInput

我们的目标是返回一个列表,其中包含每个可用的colourInput小部件的合适参数化的updateColourInput

我们定义一个包含所有已选择的级别/变量的变量,并生成适当数量的默认颜色。然后我们再次对向量进行排序,以避免歧义。

然后我们使用lapplydo.call调用一个updateColourInput函数,该函数使用指定的参数作为列表给出。

observeEvent(input$reset, {
      # Problem: dynamic number of widgets
      # - lapply, do.call

      lev <- sort(unique(input$select))
      cols <- gg_fill_hue(length(lev))

      lapply(seq_along(lev), function(i) {
              do.call(what = "updateColourInput",
                      args = list(
                        session = session,
                        inputId = paste0("col", lev[i]),
                        value = cols[i]
                      )
              )
      })
    })

完整示例:

library(shiny)
library(shinyjs)
library(reshape2)
library(ggplot2)

dat <- data.frame(matrix(rnorm(60, 2, 3), ncol=3))
dat <- melt(dat)

# Function that produces default gg-colours is taken from this discussion:
# https://dev59.com/Bmsy5IYBdhLWcg3w9i1u
gg_fill_hue <- function(n) {
  hues = seq(15, 375, length = n + 1)
  hcl(h = hues, l = 65, c = 100)[1:n]
}

runApp(shinyApp(
  ui = fluidPage(
    selectizeInput("select", "Select:", 
                   choices = as.list(levels(dat$variable)), 
                   selected = "X1", 
                   multiple = TRUE), 
    uiOutput('myPanel'),
    plotOutput("plot"),
    downloadButton('downloadplot', label = 'Download Plot'),
    actionButton("reset", "Default colours", icon = icon("undo"))
  ),
  server = function(input, output, session) {

    output$myPanel <- renderUI({ 
      lev <- sort(unique(input$select)) # sorting so that "things" are unambigious
      cols <- gg_fill_hue(length(lev))

      # New IDs "colX1" so that it partly coincide with input$select...
      lapply(seq_along(lev), function(i) {
        colourInput(inputId = paste0("col", lev[i]),
                    label = paste0("Choose colour for ", lev[i]), 
                    value = cols[i]
        )        
      })
    })


    output$plot <- renderPlot({
      cols <- paste0("c(", paste0("input$col", sort(input$select), collapse = ", "), ")")
      # print(cols)
      cols <- eval(parse(text = cols))
      # print(cols)

      # To prevent errors
      req(length(cols) == length(input$select))

      dat <- dat[dat$variable %in% input$select, ]
      ggplot(dat, aes(x = variable, y = value, fill = variable)) + 
        geom_boxplot() +
        scale_fill_manual(values = cols)

    })


    observeEvent(input$reset, {
      # Problem: dynamic number of widgets
      # - lapply, do.call

      lev <- sort(unique(input$select))
      cols <- gg_fill_hue(length(lev))

      lapply(seq_along(lev), function(i) {
        do.call(what = "updateColourInput",
                args = list(
                  session = session,
                  inputId = paste0("col", lev[i]),
                  value = cols[i]
                )
        )
      })
    })




    output$downloadplot <- downloadHandler(
      filename = "plot.pdf",
      content = function(file) {
        pdf(file, width = 12, height = 6.3)
        print(testplot())
        dev.off()
      })
  }
))

2
这是一个惊人的答案!我非常感激,它像魔法一样有效! :) - Mal_a

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接