11
votes

I've added the leaflet TextPath plugin to a shiny app in analogy to this example. This works pretty well:

output$fullscreen_map <- renderLeaflet({
  points <- points_reactive()

  points %>%
    leaflet() %>% 
    addTiles() %>%
    fitBounds(~min(lon), ~min(lat), ~max(lon), ~max(lat)) %>%
    registerPlugin(textPathPlugin) %>%
    onRender("function(el, x, data) {
                data = HTMLWidgets.dataframeToD3(data);
                data = data.map(function(val) { return [val.lat, val.lon]; });
                var layer = L.polyline(data);
                layer.on('mouseover', function () {
                this.setText('  ►  ', {repeat: true, attributes: {fill: 'blue'}});
                });
                layer.on('mouseout', function () {
                this.setText(null);
                });
                layer.addTo(this);
    }", data = points)

})

In accordance with the docs I would now like to use leafletProxy() so that the entire map doesn't get redrawn whenever the reactive data source changes. Alas, using the following snippet of code

leafletProxy("fullscreen_map", data = points) %>%
  onRender("function(el, x, data) {
              data = HTMLWidgets.dataframeToD3(data);
              data = data.map(function(val) { return [val.lat, val.lon]; });
              var layer = L.polyline(data);
              layer.on('mouseover', function () {
              this.setText('  ►  ', {repeat: true, attributes: {fill: 'blue'}});
              });
              layer.on('mouseout', function () {
              this.setText(null);
              });
              layer.addTo(this);
  }", data = points)

does not work as intended. I assume this is because onRender is only called when a new render occurs and the whole point of leafletProxy is to not re-render? If this is correct, is there a way to do this using other methods?

2
This is an interesting problem... Shiny has pretty robust event handling, so I think the trick is to figure out the right place to tie in your script. It is a bit tricky to figure this out with out a WME. Would you mind posting one? - Ian Wesley
Seems to me like it should work. I wouldn't be surprised if a minor adjustment to the javascript fixed it, but would need to make a MWE and don't have the time right now. - Mike Wise
Have you tried restarting your shiny server and running it again? I've had some terrible shiny problems over the past couple days that are inexplicable, but were solved by a restart. It's worth a shot. - mysteRious
@yosukesabai Would you please post a minimal reproducible example? - Josh O'Brien
yes yes, this evening i will do - yosukesabai

2 Answers

1
votes

It could be something like below, although there would be more cleaner methods.

What I did was to use leafletProxy to add polylines layer from points_reactive() function, while setting group to be reactive. I used listened to layeradd event of map, and if a layer with reactive group got added, i added the textPath.

output$fullscreen_map <- renderLeaflet({
  points <- points_reactive()

  points %>%
    leaflet() %>% 
    addTiles() %>%
    fitBounds(~min(lon), ~min(lat), ~max(lon), ~max(lat)) %>%
    registerPlugin(textPathPlugin) %>%
    onRender("function(el, x, data) {
              var mymap = this;

              mymap.on('layeradd',
                function(e) {
                  console.log(e);
                  var layer = e.layer;

                  if (layer.groupname == 'reactive') {
                    layer.on('mouseover', function () {
                      this.setText('  ►  ', {repeat: true, attributes: {fill: 'blue'}});
                    });
                    layer.on('mouseout', function () {
                      this.setText(null);
                    });
                  }
                }
              );

            }", data = points)

  })

  observeEvent(input$clickme,{
               points <- points_reactive()

               leafletProxy('fullscreen_map') %>%
                 addPolylines(lng=points$lon, lat=points$lat, group='reactive')

  }
)
}
0
votes

The docs say: active inputs and expressions that affect the renderLeaflet expression will cause the entire map to be redrawn from scratch and reset the map position and zoom level.

It sounds like you will have to dig deeper into Leaflet or trigger leafletProxy yourself, and make sure that the data source does not change, by using two data sources... (Perhaps "Proxy" is a misnomer here.)

Have a look and see if one of these plugins can help: https://leafletjs.com/plugins#dynamiccustom-data-loading