Search code examples
rcolorsshinyfillggvis

How to make fill color consistent when plotting subsets of a dataframe with ggvis


I am trying to make the colors in a ggvis plot remain consistent whenever the data is re-plotted based on the factors (unfortunately I apparently lack enough reputation to include pictures to show you).

I could only find one other post about this controlling-color-of-factor-group-in-ggvis-r but none of his solutions or workarounds work in my situation.

my data looks like this:

         month year     date entity_name prefix module module_entry_key entity_table_name count
    0  January 2011 2011.000   AbLibrary    LIB   Base               BS        AB_LIBRARY     0
    1 February 2011 2011.083   AbLibrary    LIB   Base               BS        AB_LIBRARY     0
    2    March 2011 2011.167   AbLibrary    LIB   Base               BS        AB_LIBRARY     0
    3    April 2011 2011.250   AbLibrary    LIB   Base               BS        AB_LIBRARY     0
    4      May 2011 2011.333   AbLibrary    LIB   Base               BS        AB_LIBRARY     0
    5     June 2011 2011.417   AbLibrary    LIB   Base               BS        AB_LIBRARY     0
 3000  January 2011 2011.000      Vector    VEC   Base               BS            VECTOR     0
 3001 February 2011 2011.083      Vector    VEC   Base               BS            VECTOR     0
 3002    March 2011 2011.167      Vector    VEC   Base               BS            VECTOR     0
 3003    April 2011 2011.250      Vector    VEC   Base               BS            VECTOR   569
 3004      May 2011 2011.333      Vector    VEC   Base               BS            VECTOR   664
 3005     June 2011 2011.417      Vector    VEC   Base               BS            VECTOR   775

I'm using a shiny app to display the page in a browser, and the relevant code is:

 # render the plot, filtering for entities within the module minus any entities selected from the exclude panel
  plot <- reactive({ 
    if (input$filter==1){
      data <- dplyr::filter(.data=melted, module_entry_key %in% input$module)
    }
    else{
      data <- dplyr::filter(.data=melted, entity_name == input$entity) 
    }
    data <- dplyr::filter(.data=data, !entity_name %in% input$excluded)
    data$entity_name <- factor(data$entity_name)
    data %>%
      ggvis(x = ~date, y = ~count, fill = ~entity_name, key := ~id, fillOpacity := 0.5, fillOpacity.hover := 0.9) %>%
      add_legend("fill", title="Entities") %>%
      layer_points() %>% 
      add_tooltip(tooltipText, "hover") %>% 
      add_axis("y", title = "Count", title_offset = 50) %>% 
      add_axis("x", title="Date", title_offset=50, subdivide=6, tick_size_minor=3, format=parseDate(~year, ~month))

  })

the filter is creating the subset of "melted" as "data" based on the filters in the UI (see picture)

since as far as I can tell there is no way to associate a fill color to a factor (the entity name) explicitly and the color is chosen by alphabetical order of the factors, whenever I make a new subset of data the colors are changed.

Is there any way to work around this?


(full shiny code)
server.R

library(ggvis)
library(shiny)
library(dplyr)


shinyServer(function(input, output, session){

  modules_list <- as.character(c("Base" = "BS",
                                 "Screening" = "SC",
                                 "Protein Engineering" = "EN",
                                 "Protein Production" = "PP",
                                 "CD",
                                 "PT",
                                 "PD"))


  #melted <- read.table(file="~/dataOut.txt", sep="\t", strip.white=TRUE, row.names=1, header=TRUE);


  modules <- as.character(as.vector(unique(melted$module_entry_key)))
  modules <- modules[modules != "null"]
  entities <- as.character(as.vector(unique(melted$entity_name)))
  entities <- entities[entities != "null"]

  for (i in entities){
    melted <- rbind(melted, data.frame(month=NA, year=NA, date=NA, entity_name=i, prefix=NA, module=NA, module_entry_key=NA, entity_table_name=NA, count=NA))
  }
  melted$id <- 1:nrow(melted)

  #create ui checkbox for modules in the data
  output$module_list <- renderUI({
    checkboxGroupInput(inputId = "module",
                       label = "Module",
                       choices = modules,
                       selected = "BS")
  })

  #create the ui list for entities 
  output$entity_list <- renderUI({
    checkboxGroupInput(
      inputId = "entity",
      label = "Entity",
      choices = entities,
      selected = "Vector"
    )
  })

  #ex <- entities

  #create the checkboxGroupInput with entities to 'exclude'
  output$exclusion_entities <- renderUI({
    checkboxGroupInput(inputId = "excluded", label = "Exclude", 
                       choices = entities)
  })

  #update the excluded entities list with entities within a particular module
  observe({
    if (input$filter==1)
      ex1 <- as.character(as.vector(unique(dplyr::filter(.data=melted, module_entry_key %in% input$module)$entity_name)))
      updateCheckboxGroupInput(session, inputId = "excluded", "Exclude", choices=ex1, selected = input$excluded )       
  })


  # render the plot, filtering for entities within the module minus any entities selected from the exclude panel
  plot <- reactive({ 
    if (input$filter==1){
      data <- dplyr::filter(.data=melted, module_entry_key %in% input$module)
    }
    else{
      data <- dplyr::filter(.data=melted, entity_name == input$entity) 
    }
    data <- dplyr::filter(.data=data, !entity_name %in% input$excluded)
    data$entity_name <- factor(data$entity_name)
    data %>%
      ggvis(x = ~date, y = ~count, fill = ~entity_name, key := ~id, fillOpacity := 0.5, fillOpacity.hover := 0.9) %>%
      add_legend("fill", title="Entities") %>%
      layer_points() %>% 
      add_tooltip(tooltipText, "hover") %>% 
      add_axis("y", title = "Count", title_offset = 50) %>% 
      add_axis("x", title="Date", title_offset=50, subdivide=6, tick_size_minor=3, format=parseDate(~year, ~month))

  })

  #function to add color and mouse-over effect to layer_points() (unused in this code)
  points <- reactive({
    layer_points(fillOpacity := 0.5, fillOpacity.hover := 1, fill.hover := "red")
  })

  #d3 date format for formatting x-axis text 
  parseDate <- function(year, month){
    paste("d3.time.format(\"%Y\").parse(", year, ")", sep="")
  }

  #function for what to display in mouse-hover tooltip
  tooltipText <- function(x) {
    if(is.null(x)) return(NULL)
    row <- melted[melted$id == x$id, ]
    paste(row$entity_name, ": ", row$count, sep="")
  }

  #bind the plot to the UI
  plot %>% #layer_points(fill = ~factor(entity_name)) %>% 
    bind_shiny("ggvis")

  #select all button for modules
  observe({
    if (input$selectall ==0){
      return(NULL)
    }  
    else if ((input$selectall%%2)==0){
      updateCheckboxGroupInput(session, inputId = "module", "Module", choices = modules)
    }
    else{
      updateCheckboxGroupInput(session, inputId = "module", "Module", choices=modules, selected=modules)
    }
  })
  #select all button for excluded entities
  observe({
    list <- as.character(as.vector(unique(dplyr::filter(.data=melted, module_entry_key %in% input$module)$entity_name)))
    if (input$exclude_all ==0){
      return(NULL)
    }  
    else if ((input$exclude_all%%2)==0){
      updateCheckboxGroupInput(session, inputId = "excluded", "Exclude", choices=list )     
    }
    else{
      updateCheckboxGroupInput(session, inputId = "excluded", "Exclude", choices=list, selected=list ) 
    }
  })

  #---general output / debugging stuff ----#
  output$table <- renderTable({dataInput()})

  output$entity_selected = renderPrint({
    list <- as.character(as.vector(unique(dplyr::filter(.data=melted, module_entry_key %in% input$module)$entity_name)))
    entities[!entities %in% input$excluded & entities %in% list]
  })

  output$filter_value = renderPrint({input$filter})
  output$modules = renderPrint({input$module})
  output$link = renderPrint(input$selectall%%2)
  #----------------------------------------#

})

ui.R

library(shiny)

shinyUI(fluidPage(
  titlePanel("DB Analysis"),
  sidebarLayout(
    sidebarPanel(
      width=3,
      radioButtons(inputId="filter",
                   label="Filter",
                   choices = list("By Module" = 1, "By Entity" = 2), 
                   selected = 1),
      conditionalPanel(condition = "input.filter == 1",
                       uiOutput("module_list"),
                       actionButton("selectall", "Select All"),
                       uiOutput("exclusion_entities"),
                       actionButton("exclude_all", "Select All")
      ),
      conditionalPanel(condition = "input.filter == 2",              
                       uiOutput("entity_list")
      )      
    ),
    mainPanel(
      h2("Cumulative Entity Counts over Time (years)", align="center"),
      #verbatimTextOutput("value"),
      #verbatimTextOutput("filter_value"),
      #verbatimTextOutput("modules"),
      #tableOutput("table"),
      ggvisOutput("ggvis"),
      verbatimTextOutput("link"),
      verbatimTextOutput("entity_selected")
     #textOutput("entities_plot")
    )
  )
)
)

Solution

  • This is probably the best way to do it. Try something like this:

    df[which(df$entity_name == "AbLibrary"),]$color <- "FF0000"
    df[which(df$entity_name == "Vector"),]$color <- "#FFB90F"
    

    For each one in your data frame. Set your fill then to color each time. The only problem is trying to make a legend. (I have been trying to figure that out, so if I find it I will edit this post.