How to mapshot absolutePanel in shiny leaflet?

shiny
leaflet

#1

Hi there,

I am using leaflet-easyPrint to print my leaflet map since it seems to be the easiest way to give the leaflet map a instant screenshot. (The mapshot() in mapview package seems not support leafletProxy object...)

My goal is to print all objects showing on the map, including the absolutePanel that made by shiny. I found once you put the absolutePanel into the leaflet htmlwiget, the JS library can successfully scrape the map.

Is there a way to hack this?
(Below is my repex for the example.)

Thank you in advance.

library(shiny)
library(leaflet)
library(htmlwidgets) # onRender()

jsfile <- "https://rawgit.com/rowanwins/leaflet-easyPrint/gh-pages/dist/bundle.js" 

ui <- fluidPage(
    tags$head(
        tags$script(src = jsfile)
    ),
    tags$div(
        leafletOutput("mymap"),
        absolutePanel(inputId="mypanel",
                      width = 280, 
                      fixed = F,
                      draggable = T,
                      
                      selectInput(inputId = "zoom",
                                  label = "zoom level",
                                  choices = 7:10,
                                  selected = 7))
    )
)

server <- function(input, output, session) {

    output$mymap <- renderLeaflet({
           leaflet() %>% 
            addTiles() %>% 
            onRender(
                "function(el, x) {
                L.easyPrint({
                sizeModes: ['A4Landscape'],
                filename: 'mymap',
                exportOnly: true, 
                hideControlContainer: false
                }).addTo(this); /* addTo(this)表示附加到這張地圖上*/
                }"
            )
    })
    changeZoom <- reactive({
        as.numeric(input$zoom)
    })
    observe({
       leafletProxy("mymap") %>% 
            setView(-93.65, 42.0285, zoom = changeZoom())
    })
}

shinyApp(ui, server)

#2

If I am understanding you correctly, you want to take a screenshot of the map including any overlaid objects, like if the user drags the absolutePanel on top of the map.

I believe that in general not possible to take a screenshot of a web page from JavaScript inside a browser. You could use a browser extension for that, but that obviously requires users of your application to install the extension, and it won't be available with a nice button inside the web page.


#3

Thanks for your reply, Winston!

It's not necessary to take a screenshot of the whole page. What I'm trying to do here is to add <div> to the map while printing it. And the reason why I want to do this is because

  1. sometimes users might want to show not only the map but also additional information such as title or panel choice to the map, and
  2. the leaflet-easyPrint plug-in only scrapes the leaflet html widget.

For instance, in my repex example the absolutePanel is after the leafletOutput(). Which means when printing the map, the panel won't appear on the map since they are on the same level form the prospect of HTML.

I'm not sure if the leaflet for R and Shiny allows user to use something like:

leaflet() %>% addTiles() %>% addMyDiv()

I guess it could be done by manipulating DOM elements with javascript or jquery.
So in order to test the theory, I run the example, open the inspector, type document.getElementById('mymap').appendChild(document.getElementById('mypanel')) in the console(as you can see now the panel is a childNode of the <div id="mymap">), and press the print button on the left side. Turned out it worked!

The panel showed up on the mapshot. (sorry I am new to the community so I can only post one img at a time.)

So now the question is: How/Where should I add the script in the code so that the panel object can be a child of the map widget?

Thanks again.


#4

Oh, interesting, it looks like it's using dom-to-image under the hood, which doesn't actually take a screenshot of what you see -- instead it copies a DOM node and renders it to an off-screen canvas object.

I don't believe there's a straightforward way to insert arbitrary HTML into an htmlwidget. When the htmlwidget starts out, it is simply an empty div with some classes, including "leaflet" and "html-widget". The initialization of the htmlwidget is what adds all the stuff inside.

This post asks the exact same question: How can we pass (compile-time R-generated) HTML to an htmlwidget?

One kind of hacky thing you could do is to add a bit of JavaScript code that will find the absolute panel (perhaps using the class or ID) and move it into the leaflet div. You will need to make sure this happens after the htmlwidget is initialized, though.