(RStudio) Shiny plotOutput with ggplot produces nothing

by Trong Hieu Hoang   Last Updated October 19, 2018 16:26 PM

I was trying to recreate Hans Rosling famous plot using Shiny output. The output turned out to be blank in the main panel where i put my plot without any warning. Can you help me point out my mistake please?

library(gapminder)  
library(ggplot2)  
library(shiny)  
library(dplyr)  

ui <- shinyUI(fluidPage(  
   titlePanel("Life expectancy and GDP per capita from 1952 to 2007"),  
   sidebarLayout(  
  sidebarPanel(  
    p("Select year"),  
     sliderInput("yeartime",  
                 label = "Year",  
                 min = 1952,  
                 max = 2007,  
                 value = 1952,  
                 animate = animationOptions(interval = 500, loop = TRUE)
                 )),  
    mainPanel(
  plotOutput("Plot"),  
  )  
   )  
))

continent_colours <-c(Africa = "#BF590CFF", Americas = "#F80039FF", Asia = "#600071FF", 
                  Europe = "#3B9626FF", Oceania = "#4A51E0FF")  

server <- shinyServer(function(input, output) {  
   output$Plot <- renderPlot({  

p <- ggplot(gapminder, aes(x=lifeExp, y=gdpPercap, size = pop, color = continent))   
  + geom_point(data = filter(gapminder,gapminder$year == input$yeartime), aes(lifeExp, gdpPercap, size = pop, color = continent_colours)) 
  + ylim(30,100) 
   + labs(x="Life expectancy (years)", y = "GDP per capita (USD)", color = 'Continent',size = "Population (millions)")

   })


})


shinyApp(ui = ui, server = server)
Tags : r ggplot2 plot shiny


Answers 2


From the comments, it sounds like you aren't protecting yourself well from problems in the filtering. Here's a stab:

server <- shinyServer(function(input, output) {  
  thisdat <- eventReactive(input$yeartime, {
    req(input$yeartime)
    filter(gapminder, year == input$yeartime)
  })

  output$Plot <- renderPlot({
    req(thisdat())
    p <- ggplot(thisdat()) +
      geom_point(aes(lifeExp, gdpPercap, size = pop, color = continent_colours)) +
      ylim(30,100) +
      labs(x="Life expectancy (years)", y = "GDP per capita (USD)", color = 'Continent',size = "Population (millions)")
    print(p)
  })

})

Walkthrough of some issues:

  • Don't use gapminder$ within the filter, just the column names. It will work when you are doing nothing fancy, but it is defeating some of the efficiencies that dplyr is trying to do for you and will break hard if you do any grouping or summarization.

    filter(gapminder, gapminder$year == input$yeartime)  # wrong
    filter(gapminder, year == input$yeartime)            # right
    
  • Test for (as shiny calls it) "truthy" data by using req. This prevents incomplete inputs from messing up your data and/or plots (seq ?req). For instance, if input$yeartime is unset and therefore NULL, thisdat is not updated. Likewise, if thisdat() returns an empty data.frame, the rest of the plot function is skipped (which should blank the plot vice giving an error on the console).

  • I broke out the data into its own reactive block with the expectation that you might want to do something else with the data. For instance, it's not uncommon to have a small summary table and/or dashboard "card" with some quick summary statistics. The way you had it would require that this additional component re-filter the data for its stuff. Now, anything that wants to see what data is being plotted can just depend on thisdat() (it looks/acts like a function), and a side-effect is that anything that depends on thisdat() will also benefit from the single req(input$yeartime), nothing else should need it (unless it is used elsewhere explicitly).

  • I moved the data object from geom_point to ggplot(...). You can certainly move it back, but unless you are going to use unfiltered data from gapminder, just stick with ggplot() + geom_point(data=thisdat(), aes(...)) + .... You don't need to specify aes(...) twice with this visualization. (Either way, you should only need the data listed once.)

  • A nuance of ggplot2, I added print(p) to the end. You weren't getting there yet, but you would probably need it eventually any.

  • Minor point, but I moved the + all to the end of the lines vice the beginning of the next. It's perhaps a bit stylistic, but I know there are times when it will cause the parser to complain/break. (It also cleans up indenting in most R-friendly editors.)

I didn't make any changes to your ui component.

r2evans
r2evans
October 18, 2018 08:20 AM

There are a few issues with your code.

Firstly, try to test your ggplot function in R. Debugging in Shiny is quite difficult.

  • In your ui there is a comma you need to delete after plotOutput("Plot")
  • Use the same dataset in your ggplot and geom_point call. I just created a subset (gapminder_subset) and used it in both calls.
  • You do not need to assign your plot to a variable p in shiny. Without the assignment it will work
  • The + sign to add ggplot elements should not be located on a newline.
  • You have to specify color and size in the geom_point call.
  • You can use the continent colors instead of a single color in your aes of geom_point, but in that case the specified colors should have the same length as your input data. Or (as Eli Berkow pointed out) you can add the coloring with the ggplot element scale_colour_manual.

Here is the working example:

library(gapminder)  
library(ggplot2)  
library(shiny)  
library(dplyr)  

ui <- shinyUI(fluidPage(  
  titlePanel("Life expectancy and GDP per capita from 1952 to 2007"),  
  sidebarLayout(  
    sidebarPanel(
      p("Select year"),
      sliderInput("yeartime",
                  label="Year",
                  min=1952,
                  max=2007,
                  value=1952,
                  animate=animationOptions(interval=500, loop=TRUE)
      )),
    mainPanel(
      plotOutput("Plot")  
    )  
  )  
))

continent_colours <- c(Africa="#BF590CFF", Americas="#F80039FF", Asia="#600071FF", 
                       Europe="#3B9626FF", Oceania="#4A51E0FF")  

server <- shinyServer(function(input, output) {  
  output$Plot <- renderPlot({  
    gapminder_subset <- gapminder[gapminder$year == input$yeartime, ]
    ggplot(gapminder_subset, aes(x=gdpPercap, y=lifeExp))+ 
        geom_point(aes(size=pop, color=continent))+
        scale_colour_manual(values = continent_colours)+
        ylim(30, 100)+
        labs(x="Life expectancy (years)", y="GDP per capita (USD)", 
             color='Continent', size="Population (millions)")
  })
})

shinyApp(ui=ui, server=server)
Wilmar van Ommeren
Wilmar van Ommeren
October 18, 2018 08:36 AM

Related Questions


shiny update plot based on data.table

Updated June 10, 2017 22:26 PM


ggplot2 density-plot with discrete data

Updated December 17, 2017 18:26 PM