I'm developing a Shiny app where I want a floating container (float-container) to appear when a user selects a country from a dropdown menu. This container should display two Highcharts (g3_output and g4_output) and should close when the user clicks outside of it.
However, I am facing the following issue:
When the observeEvent(input$selected_country, {...})
triggers, the
floating container appears as expected, and it even adjusts its size
as if the charts are being loaded.
But the Highcharts inside the floating container do not render visually—the container remains empty.
The charts (g1_output and g2_output) outside the floating container render without issues. The container successfully closes when clicking outside, so that part is working fine.
What I Want to Achieve
When a user selects a country, the floating container should appear with two Highcharts inside it.
When clicking outside the floating container, it should disappear.
What might be the problem?
I suspect that the issue is related to the rendering behavior of highcharter when inside a dynamically displayed container (display: none;). It seems like the charts are not being properly drawn or initialized when the container is hidden at the start.
How to ensure that Highcharts inside the floating container render correctly when the container appears?
library(shiny)
library(shinyjs)
library(highcharter)
library(dplyr)
data <- data.frame(x = 1:100, y = cumsum(rnorm(100)), y2 = rnorm(100))
g1 <- highchart() %>%
hc_chart(type = "line") %>%
hc_title(text = "Sales Over Time") %>%
hc_xAxis(categories = data$x) %>%
hc_series(list(name = "Series 1", data = data$y))
g2 <- highchart() %>%
hc_chart(type = "column") %>%
hc_title(text = "Monthly Revenue") %>%
hc_xAxis(categories = data$x) %>%
hc_series(list(name = "Series 2", data = data$y2))
ui <- fluidPage(
useShinyjs(),
selectInput("selected_country", "Select a country:",
choices = c("", "USA", "Canada", "Mexico")),
highchartOutput("g1_output"),
highchartOutput("g2_output"),
div(id = "float-container",style = "display: block; position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 60%;
padding: 20px;
background: rgba(0, 0, 0, 0.7); /* Fondo semitransparente */
color: white;
text-align: center;
border-radius: 10px;
z-index: 1000;",
h2("this is a float contatiner!!!"),
highchartOutput("g3_output"),
highchartOutput("g4_output")
),
tags$script(HTML("
document.addEventListener('click', function(event) {
var container = document.getElementById('float-container');
if (container.style.display === 'block' && !container.contains(event.target)) {
Shiny.setInputValue('close_container', true, {priority: 'event'});
}
});
"))
)
server <- function(input, output, session) {
output$g1_output <- renderHighchart({
g1
})
output$g2_output <- renderHighchart({
g2
})
output$g3_output <- renderHighchart({
g1
})
output$g4_output <- renderHighchart({
g2
})
observeEvent(input$selected_country, {
if (input$selected_country != "") {
runjs("document.getElementById('float-container').style.display = 'block';")
runjs("document.getElementById('g3_output').style.display = 'block';")
runjs("document.getElementById('g4_output').style.display = 'block';")
}
})
observeEvent(input$close_container, {
runjs("document.getElementById('float-container').style.display = 'none';")
})
}
shinyApp(ui, server)
The main issue is that Highcharts needs to calculate dimensions when rendering, and it can't do this properly when the container is initially hidden (none). Also highcharts have this flowing animation which might be interupted.
We can force the highchart animation to re-render on float-container show. Use shinyjs show
and hide
functions to show and hide your float Container. Simply changing display to none or block might not be enough. I also changed the css to show the float-container
below relatively to the picklist and colored all texts white so they are more visible.
library(shiny)
library(shinyjs)
library(highcharter)
library(dplyr)
# Sample data
data <- data.frame(x = 1:100, y = cumsum(rnorm(100)), y2 = rnorm(100))
# Create charts
g1 <- highchart() %>%
hc_chart(type = "line") %>%
hc_title(text = "Sales Over Time", style = list(color = "#FFFFFF", useHTML = T)) %>%
hc_xAxis(categories = data$x) %>%
hc_series(list(name = "Series 1", data = data$y))%>%
hc_legend(
itemStyle = list(
color = "#ffffff"
),
itemHoverStyle = list(
color = "#cccccc"
)
)
g2 <- highchart() %>%
hc_chart(type = "column") %>%
hc_title(text = "Monthly Revenue", style = list(color = "#FFFFFF", useHTML = T)) %>%
hc_xAxis(categories = data$x) %>%
hc_series(list(name = "Series 2", data = data$y2))%>%
hc_legend(
itemStyle = list(
color = "#ffffff"
),
itemHoverStyle = list(
color = "#cccccc"
)
)
ui <- fluidPage(
useShinyjs(),
tags$head(
tags$style(HTML("
#float-container {
display: none;
position: relative;
left: 15%;
width: 74%;
padding: 20px;
background: rgba(0, 0, 0, 0.7);
color: white;
text-align: center;
border-radius: 10px;
z-index: 1000;
margin-top: 16px;
}
.chart-container {
margin-bottom: 20px;
}
"))
),
selectInput("selected_country", "Select a country:",
choices = c("", "USA", "Canada", "Mexico")),
# Floating container
div(id = "float-container",
h2("Detailed Country Analysis"),
div(class = "chart-container", highchartOutput("g3_output")),
div(class = "chart-container", highchartOutput("g4_output"))
),
# Click outside handler
tags$script(HTML("
document.addEventListener('click', function(event) {
var container = document.getElementById('float-container');
var selectElement = document.getElementById('selected_country');
if (!container.contains(event.target) && !selectElement.contains(event.target)) {
Shiny.setInputValue('close_container', true, {priority: 'event'});
}
});
"))
)
server <- function(input, output, session) {
# Floating charts with reactive rendering
output$g3_output <- renderHighchart({
req(input$selected_country != "")
g1 %>%
hc_title(text = paste("Sales Over Time -", input$selected_country))
})
output$g4_output <- renderHighchart({
req(input$selected_country != "")
g2 %>%
hc_title(text = paste("Monthly Revenue -", input$selected_country))
})
# Show container and trigger chart rerender
observeEvent(input$selected_country, {
if (input$selected_country != "") {
shinyjs::show("float-container") # Force Highcharts to reflow after showing container
delay(100, {
runjs("
if (window.Highcharts) {
var charts = Highcharts.charts;
charts.forEach(function(chart) {
if (chart) {
chart.reflow();
}
});
}
")
})
}
})
# Hide container
observeEvent(input$close_container, {
shinyjs::hide("float-container")
})
}
shinyApp(ui, server)