tl;dr version: For a faceted plotly, I need a way to
I am currently building a Shiny app for performing basic exploratory data analysis on various datasets. I'm using ggplot2 along with plotly to create histograms for different factors. However, I'm facing an issue when it comes to displaying long factor names on the x-axis. I tried rotating the labels to make them fit better, but plotly doesn't seem to allocate enough vertical space for them, causing the labels to intersect with the plot area. Here's a screenshot to better illustrate my problem:
So my questions are:
Or maybe I am asking the wrong question. Is it a bad idea to mess with plot dimensions manually altogether and should I just try something else?
Additionally, I noticed that the plot area becomes quite small in certain cases. So here is a bonus question: How can I estimate the size the entire plot would need, so I can scale the total height dynamically, so that nothing gets cut off.
Below is a minimal working example with dummy data from the synthpop
package:
library(shiny)
library(tidyverse)
library(plotly)
library(synthpop) # for example data
# generate example data frame
data <- synthpop::SD2011 |>
select(where(is.factor)) |>
slice(1:6)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(),
mainPanel(
plotlyOutput("hist_plot")
)
)
)
server <- function(input, output) {
hist_plot <- reactive({
data |>
pivot_longer(cols=everything(), names_to = "key", values_to = "value") |>
ggplot(aes(x=value)) +
geom_bar() +
facet_wrap(~key, nrow = 2, scales = "free_x") +
theme(
axis.text.x = element_text(angle = 90))
})
output$hist_plot <- renderPlotly({
req(hist_plot)
ggplotly(hist_plot())
})
}
shinyApp(ui = ui, server = server)
Here are some things I tried/considered:
renderPlot()
. This, however, makes the plots appear blurry in the app and it has it own scaling and overlapping problems.plotlyOutput("hist_plot", height = "1000px")
. This somewhat solves the problem when I know what total size I need, but it also increases both the size of the plot area and for the labels. Ideally, we should be able to control the size of both the overall chart size and the plot independently of each other.Edit: added tl:dr section
Since I don't know what else is in your app, I doubt this is a 'one shot' answer. After you've gone through it, let me know what you're thinking, if it's not working out for you. I am certain this is not perfect. There is too much static sizing for that to happen.
A few things to note, relevant to this answer
ggplot
to plotly
it did some hokey, yet expected, thingsplotly.annotations
plotly.shapes
fixer()
functionyanchor
y
positionMost of the code is not changed from the code in your question. Look for my comments in the code to note the changes. If you have questions, I'm but a comment away.
library(shiny)
library(tidyverse)
library(plotly)
library(synthpop) # for example data
# generate example data frame
data <- synthpop::SD2011 |>
select(where(is.factor)) |>
slice(1:6)
fixer <- function(pt) { # <--- I added
rg <- pt$x$layout$yaxis2$domain[2] # graph size based on domain
lapply(1:length(pt$x$layout$shapes), function(i) { # modify shapes
if(isTRUE(pt$x$layout$shapes[[i]]$yanchor == rg)) { # isTRUE for errors
pt$x$layout$shapes[[i]]$yanchor <<- 2.5 * rg # move grey squares up
}
})
lapply(1:length(pt$x$layout$annotations), function(j) { # modify annotations
if(isTRUE(round(pt$x$layout$annotations[[j]]$y, 6) == round(rg, 6))) { ## isTRUE for errors
pt$x$layout$annotations[[j]]$y <<- 2.5 * rg # move facet labels up
}
})
pt$x$layout$yaxis2$domain <- c(1.5 * rg, 2.5 * rg) # modify plot positions
pt
}
ui <- fluidPage(
tags$head( # <--- I added
tags$style(HTML( # dynamic x-axis labels' size; dynamic plot height
".xaxislayer-above text{
font-size: calc(6px + 6 * ((100vw - 300px) / (1600 - 300))) !important;
}
#hist_plot{
height: 75vh !important;
}"))
),
sidebarLayout(
sidebarPanel(),
mainPanel(
plotlyOutput("hist_plot")
)
)
)
server <- function(input, output) {
hist_plot <- reactive({
data |>
pivot_longer(cols=everything(), names_to = "key", values_to = "value") |>
ggplot(aes(x=value)) +
geom_bar() +
facet_wrap(~key, nrow = 2, scales = "free_x") +
theme(
axis.text.x = element_text(angle = 90))
})
output$hist_plot <- renderPlotly({
req(hist_plot)
ggplotly(hist_plot()) %>%
layout(margin = list(b = 100)) %>% fixer() # <--- I added
})
}
shinyApp(ui = ui, server = server)