Andrew Plowright
Andrew Plowright

Reputation: 587

Shiny: unwanted space added by plotOutput() and/or renderPlot()

Either plotOutput or renderPlot seems to add a bunch of extra white space around a plot. I've added background colours to the plot and the layout column to illustrate this. Essentially, I would like to be completely rid of this white space, leaving only the area coloured in blue, which should then align itself to the left of the column.

I know that this can be adjusted using the width argument, but having it a either 100% or auto doesn't seem to work properly.

Thanks!

library(shiny)
library(ggplot2)

# Create sample data
animals <- as.data.frame(
  table(Species =
          c(rep("Moose", sample(1:100, 1)),
            rep("Frog", sample(1:100, 1)),
            rep("Dragonfly", sample(1:100, 1))
          )))

server <- function(input, output) {

  output$pieChart <- renderPlot({

    # Create pie chart
    ggplot(animals, aes(x = "", y = Freq, fill = Species)) +
      geom_bar(width = 1, stat = "identity") +
      coord_polar("y", start=0) +
      theme(plot.background = element_rect(fill = "lightblue"))
  })
}

ui <- fluidPage(

  fluidRow(
    column(4, 
           style = "background-color:lightgreen",
           align = "left",
           HTML("filler<br>"),
           plotOutput("pieChart")
    )
  )
)

shinyApp(ui = ui, server = server)

enter image description here

Upvotes: 4

Views: 3711

Answers (2)

Brian
Brian

Reputation: 8295

I had a similar, but not identical, issue with a map. I knew what aspect ratio I wanted in the map (exactly 1:1 for me), much like you have with your pie chart, and I wanted it to occupy as much of the width of the responsive column as it could, changing the height accordingly. However, I didn't want it to be too big, so I added a line of logic to cap it to 400 pixels wide.

My approach was to draw a dummy ggplot object that was invisible, then query the client session to learn about its size. Then I could pass that size as an explicit parameter to the actual desired plot.

# you need to include `session` as a third argument here
server <- function(input, output, session) {

  # blank (plot for width) ----
  output$blank <- renderPlot({
    ggplot(data.frame(x = 1), aes(NA, NA)) + geom_blank() + theme_void()
  })

  blankwidth <- reactive({
          # this is the magic that makes it work
    bw <- session$clientData$output_blank_width
    if (bw > 400) 400 else bw
  })

  blankheight <- reactive({
    blankwidth() / 1.25
    # whatever aspect ratio you need
  })

  output$plotofinterest <- renderPlot({
    ggplot(iris[sample.int(150,50),], aes(1, fill = Species)) + 
      geom_bar() + coord_polar(theta = "y")
  }, height = blankheight, width = blankwidth)
     # this is the necessary bit
}

# ui.R

ui <- fluidPage(
  fluidRow(
    column(4,
           style = "background-color:lightgreen",
           plotOutput('blank', width = '100%', height = 0),
           plotOutput('plotofinterest', inline = T)
    ),
    column(8,
           style = "background-color:lightblue",
           HTML("filler")
    )
  )
)

shinyApp(ui, server)

enter image description here

If you make the window narrower, the plot will be less than the 400px cap, and it will take up the whole column. If you make it wider, the right-side green space gets larger. I haven't noticed a big loss of performance from drawing extra plots to get this info back from the client. Even when you drag around the window size to play with it, it updates quite quickly.

You do need to figure out what aspect ratio you want it drawn at. grid will automatically add whitespace like Claus explained to the unrestricted pair of margins, so it's fair to just kind of eyeball it until it looks good to you.

Upvotes: 4

Claus Wilke
Claus Wilke

Reputation: 17810

The space is added by the grid layout engine, not by anything in shiny. Grid adds the white space because ggplot has asked it to maintain the aspect ratio of the plot, to make sure the circle is round.

Here is how you can see this. First, let's draw the plot the regular way, but let's manually convert to a grob first.

g <- ggplot(animals, aes(x = "", y = Freq, fill = Species)) +
  geom_bar(width = 1, stat = "identity") +
  coord_polar("y", start=0) +
  theme(plot.background = element_rect(fill = "lightblue"))

library(grid)
grob <- ggplotGrob(g)
grid.newpage()
grid.draw(grob)

This will generate plots with whitespace either on the sides or above/below, depending on the shape of the enclosing window:

enter image description here

enter image description here

Now let's turn off the aspect-ratio enforcement and plot again:

grob$respect <- FALSE # this switches of the fixed aspect ratio
grid.newpage()
grid.draw(grob)

Now there is no white space, but also the circle isn't round.

enter image description here

You'll have to put the plot into an enclosing container that can provide necessary white space on the right and bottom. This is possible with grid if you're willing to give the plot a fixed size, by putting the plot into a table with empty space to the side and bottom:

library(grid)
library(gtable)
# need to play around to find numbers that work
plotwidth = unit(6.1, "inch")
plotheight = unit(5, "inch")

grob <- ggplotGrob(g)
mat <- matrix(list(grob, nullGrob(), nullGrob(), nullGrob()), nrow = 2)
widths <- unit(c(1, 1), "null")
widths[1] <- plotwidth
heights <- unit(c(1, 1), "null")
heights[1] <- plotheight
gm <- gtable_matrix(NULL, mat, widths, heights)
grid.newpage()
grid.draw(gm)

enter image description here

enter image description here

The downside of this approach is that now, if the plot window is too small, the plot will not be resized but instead will be cut off:

enter image description here

I'm not sure how to get the plot to scale to the largest possible size that still fits within the window.

Upvotes: 5

Related Questions