Reputation: 13
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)
Upvotes: 1
Views: 5526
Reputation: 7689
There are a few issues with your code.
Firstly, try to test your ggplot
function in R
. Debugging in Shiny
is quite difficult.
ui
there is a comma you need to delete after plotOutput("Plot")
ggplot
and geom_point
call. I just created a subset (gapminder_subset
) and used it in both calls. p
in shiny. Without the assignment it will work+
sign to add ggplot
elements should not be located on a newline.color
and size
in the geom_point call.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)
Upvotes: 1
Reputation: 160387
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.
Upvotes: 1