If you have written any Shiny apps, you are probably familiar with the idea of responsive web design, which focuses on web page layouts adaptive to screen size and popularized by the front-end frameworks such as Bootstrap.
Could the similar idea be applied to statistical graphics? In my opinion, the answer is yes, and the idea can be helpful for arranging multi-panel graphics under the context of creating effective visualizations that are adaptive to any screen sizes.
Starter example
It is straightforward to demonstrate the idea of responsive statistical graphics with ggplot2 and Shiny. The key steps are:
- In Shiny, get the current plot output width and height detected in the
web browser using the session API provided by
session$clientData
. - In ggplot2, use the width data to decide the grid layout (number of columns) and graphical details such as font size of the multi-facet plot.
- Save the output with proper pixel width and height using an appropriate graphical device.
- In Shiny, re-render the plot based on screen size changes using
shiny::renderImage()
.
Following this logic, we can write a minimal prototype with 30 lines of R code:
library("shiny")
library("ggplot2")
ui <- fluidPage(
plotOutput("plot_responsive")
)
server <- function(input, output, session) {
output$plot_responsive <- renderImage(
{
width <- session$clientData$output_plot_responsive_width
height <- session$clientData$output_plot_responsive_height
outfile <- tempfile(fileext = ".png")
png(outfile, width = width * 3, height = height * 3, res = 300)
p <- ggplot(mpg, aes(displ, hwy)) + geom_point()
i <- findInterval(width, c(540, 720, 960, 1140, 1320)) + 1
p <- p + facet_wrap(vars(class), ncol = c(1, 2, 3, 4, 6, 8)[i])
print(p)
dev.off()
list(src = outfile, width = width, height = height)
},
deleteFile = TRUE
)
}
shinyApp(ui, server)
For the purpose of displaying information in a more interpretable way, it might be a good idea to fix the aspect ratio of each panel or for single-facet displays. There is also previous research suggesting the optimal display ratio such as the 45° banking rule by Cleveland et al. (1988).
Responsive graphics within responsive containers
Of course, we can extend the idea of responsive graphics and fit them inside a multi-column responsive page layout. This GitHub Gist has a more generic implementation with modularized plotting and saving functions to show both single-column and two-column layouts with vector-based SVG output.
The multi-column layout case is even more intriguing because unlike the single-column case, this is a “responsive graphics within responsive containers” situation:
- Image output width is not equal to the screen size anymore, and the default output width could be much smaller than the single-column case.
- Image output width is not monotonic to screen size anymore because the responsive layout effect of the Bootstrap fluid containers will also kick in: columns can be rearranged to the next fluid row as screen size decreases.
Therefore, if we want to keep the graphics to have consistent responsive layout behaviors, more deliberate calculations and parameter settings might be required.
A technical note on interval finding
The reactive layout computation requires finding where the current plot width
falls in consecutive intervals of container sizes.
To achieve this, my original prototype implementation included many tedious
if
constructs.
In fact, there are much better ways to handle such interval finding problems
in R: the findInterval()
function can simplify the logic into one function
call. For fewer breaking points, you can also use a fancy approach with
upcasting, for example:
i <- (width >= 960) + (width >= 1140) + 1
p <- p + facet_wrap(vars(class), ncol = c(1, 2, 4)[i])
These tricks were taken from an online discussion (thanks, @yihui).
Conclusion
Of course, you can create a generic responsive plotting framework to automate all the fine details discussed above! Please kindly let me know if you or someone else has already done this as I would love to try it out.