rstudio / leaflet

R Interface to Leaflet Maps
http://rstudio.github.io/leaflet/
Other
798 stars 504 forks source link

Leaflet map fails to recognize flexible UI #248

Open tiernanmartin opened 8 years ago

tiernanmartin commented 8 years ago

I noticed that leaflet maps embedded in shinydashboards fail to render fully after their container expands horizontally.

Here's a reproducible example (hint: click the hamburger icon to minimize the sidebar, then try exploring the leaflet map at different zoom levels and locations).

leaflet_bug_ss (The gray space on the right should be leaflet map)

byzheng commented 8 years ago

The SuperZip example could be one solution for your problem: http://shiny.rstudio.com/gallery/superzip-example.html.

Try this gist: https://gist.github.com/byzheng/7e38d36cc260fff5cba3

tiernanmartin commented 8 years ago

@byzheng Thanks! This is a pretty good work-around. I suppose that keeping the map at full width regardless of what the sidebar is doing is better than what I had before.

That said, this solution is still unideal with the example's UI design because the way the map is centered leaves much of the content obscured by the sidebar. This gets annoying if you have lots of buttons that allow the user to zoom to different locations (which is part of my actual project design).

Perhaps this is better left for SO, but I'm wondering if there's a way to get the leaflet map to adjust its width dynamically (i.e., when the sidebar is collapsed or expanded) without redrawing the map. This was the behavior I was hoping for.

byzheng commented 8 years ago

I have the same problem or feature request, but cannot figure out one way to solve it. It may be require some knowledge of css.

jcheng5 commented 8 years ago

This is happening because htmlwidgets only detect size changes when 1) the browser window size actually changes, or 2) a "shown" or "hidden" jQuery event is triggered somewhere on the document. @wch can we modify shinydashboard to do that when the sidebar is shown/hidden?

In the meantime, here's a workaround you can insert into your UI (anywhere):

tags$script(
  '$(".sidebar-toggle").on("click", function() { $(this).trigger("shown"); });'
)

This is a bit sloppy in that it's not triggering the right event (shown/hidden) on the right element (should be the sidebar container itself, not the sidebar toggle button) but it does work.

byzheng commented 8 years ago

@jcheng5 Thanks. It works very well.

byzheng commented 8 years ago

@jcheng5 Can we make the height of leaflet element into 100% to use the remain space and dynamic resize?

See example here which I can only set the height into 90vh: https://gist.github.com/byzheng/7e38d36cc260fff5cba3

chris-holcomb commented 8 years ago

You can give the map's outer div 0px padding and make the map's div position: fixed. You can also assign multiple boostrap 3 classes to the containers to make it responsive. More information.

ui.R

shinyUI(fluidPage(
  includeCSS("www/styles.css"),
  fluidRow(
    h3("Title", id = "app-title")
  ),
  fluidRow(
    column(4,
      h4("Menu"),     
      class = "col-lg-2 col-md-4", id = "menu-div"
    ),
    column(8,
      leafletOutput("mainmap", width = "100%", height = "100%"),
      class = "col-lg-10 col-md-8", id = "map-div"
    )
  )
))

www/styles.css

#mainmap {
  position: fixed;
}

#map-div {
  padding: 0px;
}

#app-title {
  float: right;
  margin-right: 4px;
  text-align: center;
  margin-top: 2px;
  margin-bottom: 0px;
}

Note: I didn't use shinydashboard, but hopefully this helps. Adding bars and using that to show and hide the menu here would be adding bsButton("menuShown", "", icon = icon("bars"), type = "toggle", value = TRUE), (from shinyBS) and useShinyjs(), to the ui.R. and this in server.R:

  observeEvent(input$menuShown, {
    if (input$menuShown) {
      shinyjs::show("menu-div")
    } else {
      shinyjs::hide("menu-div")
    }
  })
tiernanmartin commented 8 years ago

@jcheng5 Thanks for the workaround – works perfectly!

byzheng commented 8 years ago

@ideamotor Thanks for your suggestion. I found the padding in my previous gist comes from section.content. Now I can make the leaflet map into all available spaces. See here: https://gist.github.com/byzheng/7e38d36cc260fff5cba3

I am just not sure about the height of header (50px comes from the Chrome develop tools).

chris-holcomb commented 8 years ago

So if you use what I shared, your map will have the wrong map_bounds, and fitBounds and setView won't work properly. Here's a partially working fix which non-ideally uses CSS calc.

library(shiny);library(leaflet);library(shinyBS);library(shinyjs)
ui <- shinyUI(fluidPage(
  tags$head(
    tags$title("appTitle"),
    tags$style(HTML("
    #mainmap {
       position: relative;
       height: calc(100vh - 32px) !important;
    }
    #map-div {
      padding: 0px;
    }
    #appTitle {
      float: right;
      margin-right: 4px;
      text-align: center;
      margin-top: 2px;
      margin-bottom: 0px;
    }
    #menuShown {
      border: none;
      float: right;
      border-radius: 0px;
      margin: 0px;
    }
    @media(max-width:767px) {
      #map-div {
        height: 100vh;
        z-index: 1;
      }
      #mainmap {
        position: relative;
      }
      #menu-div {
        z-index: 2;
        position: absolute;
        background-color: white;
      }
    }
    "))
    ),
  useShinyjs(),
  fluidRow(
    bsButton("menuShown", "", icon = icon("bars"), type = "toggle", value = TRUE),
    h3("appTitle", id = "appTitle")
  ),
  fluidRow(
    column(4,
           h4("Menu"),     
           class = "col-lg-2 col-md-4", id = "menu-div"
    ),
    column(8,
           leafletOutput("mainmap", width = "100%", height = "100%"),
           class = "", id = "map-div"
    )
  )
    ))
server <- shinyServer(function(input, output, session) {
  output$mainmap <- renderLeaflet({
    leaflet("mainmap") %>% addTiles() %>% setView(lng = -97.743, lat = 30.267, zoom = 13)
  })
  observeEvent(input$menuShown, {
    if (input$menuShown) {
      shinyjs::show("menu-div")
      addClass("map-div", "col-lg-10 col-md-8 col-sm-8")
      leafletProxy("mainmap") %>% setView(lng = -97.743, lat = 30.267, zoom = 13)
    } else {
      shinyjs::hide("menu-div")
      removeClass("map-div", "col-lg-10 col-md-8 col-sm-8")
      leafletProxy("mainmap") %>% setView(lng = -97.743, lat = 30.267, zoom = 13)
    }
  })
})
shinyApp(ui,server)

The issue is that when you close the menu, the map is still thinking it has it's old size and location. So when we setView again, it's off-center. Additionally, the map has a hard time rendering on the right side. I believe we need a function that just calls Leafet's invalidateSize() on a specified map at any necessary time.

bborgesr commented 7 years ago

I think this may have been solved by rstudio/shinydashboard#185 (not yet on CRAN)

naught101 commented 6 years ago

Not sure if this is related, or a separate bug, but when trying to use CSS calc(), leaflet spits an error.

  leafletOutput("map", height ="calc(100vh - 80px)"),

throws:

ERROR: "calc(100vh - 80px)" is not a valid CSS unit (e.g., "100%", "400px", "auto")

Which seems incorrect. Leaflet should pass that through. Note "100vh" works fine.