vueR icon indicating copy to clipboard operation
vueR copied to clipboard

How to link vueR with leaflet?

Open TrantorM opened this issue 8 years ago • 7 comments

Thanks for this great package. How can one link a reactive vueR variable to select topojson properties in leaflet? I tried the following example. The preselection incidents is working, but leaflet doesn't rerendering when I select an other property. Any Idea, how to fix that?

library(htmltools)
library(leaflet) # I installed leaflet 1.0 with devtools::install_github("timelyportfolio/[email protected]")
library(leaflet.extras)
library(vueR)

topojson <- readr::read_file('https://rawgit.com/TrantorM/leaflet-choropleth/gh-pages/examples/basic_topo/crimes_by_district.topojson')

map <- leaflet() %>% 
  setView(-75.14, 40, zoom = 11) %>% 
  addProviderTiles("CartoDB.Positron") %>% 
  addGeoJSONChoropleth(topojson,valueProperty = "{{selected}}")

ui <- tagList(tags$div(id="app",
                 tags$select("v-model" = "selected",
                             tags$option("disabled value"="","Select one"),
                             tags$option("incidents"),
                             tags$option("dist_num")),
                 tags$span("Selected: {{selected}}"),
                 tags$div(map)
        ),
        tags$script(
          "
          var app = new Vue({
          el: '#app',
          data: {
          selected: 'incidents'
          }
          });
          "
        ),
        html_dependency_vue())

browsable(ui)

TrantorM avatar May 01 '17 17:05 TrantorM

Very good question, and I would classify this as advanced usage. vuejs does not know how to automatically update htmlwidgets, since they are not components. We will need to watch selected and explicitly provide instructions to update the map. I think this gets close, but let me know if not.

library(htmltools)
library(leaflet) # I installed leaflet 1.0 with devtools::install_github("timelyportfolio/[email protected]")
library(leaflet.extras)
library(vueR)

topojson <- readr::read_file('https://rawgit.com/TrantorM/leaflet-choropleth/gh-pages/examples/basic_topo/crimes_by_district.topojson')

map <- leaflet() %>% 
  setView(-75.14, 40, zoom = 11) %>% 
  addProviderTiles("CartoDB.Positron") %>% 
  addGeoJSONChoropleth(
    topojson,
    valueProperty = "{{selected}}",
    group = "choro"
  )

ui <- tagList(
  tags$div(
    id="app",
    tags$select("v-model" = "selected",
      tags$option("disabled value"="","Select one"),
      tags$option("incidents"),
      tags$option("dist_num")
    ),
    tags$span(
      "Selected: {{selected}}"
    ),
    tags$div(map)
),
  tags$script(
"
var app = new Vue({
  el: '#app',
  data: {
    selected: 'incidents'
  },
  watch: {
    selected: function() {
      // uncomment debugger below if you want to step through
      //debugger;

      // only expect one
      //  if we expect multiple leaflet then we will need
      //  to be more specific
      var instance = HTMLWidgets.find('.leaflet');
      // get the map
      //  could easily combine with above
      var map = instance.getMap();
      // we set group name to choro above
      //  so that we can easily clear
      map.layerManager.clearGroup('choro');

      // now we will use the prior method to redraw
      var el = document.querySelector('.leaflet');
      // get the original method
      var addgeo = JSON.parse(document.querySelector(\"script[data-for='\" + el.id + \"']\").innerText).x.calls[1];
      addgeo.args[7].valueProperty = this.selected;
      LeafletWidget.methods.addGeoJSONChoropleth.apply(map,addgeo.args);
    }
  }
});
"
  ),
  html_dependency_vue(offline=FALSE)
)

browsable(ui)

timelyportfolio avatar May 01 '17 19:05 timelyportfolio

live example; I did have to change to update the valueProperty. could have possibly used Vue.nextTick also

https://bl.ocks.org/timelyportfolio/1a7d43b14a23665857ad5ef421d9ac41

timelyportfolio avatar May 01 '17 19:05 timelyportfolio

After all said and done, not sure if it is worth or not unless you plan to use other vuejs components.

timelyportfolio avatar May 01 '17 19:05 timelyportfolio

Thank you so much for this code, it does exactly what I was looking for. I would never have figured it out by myself.

It's faster than a solution with shiny (See here and here).

I will check now the performence with a bigger topojson file with much more geometries and properties.

TrantorM avatar May 01 '17 21:05 TrantorM

Glad it worked. I will try to post some alternative solutions over the next couple of days. Do you plan to integrate in a bigger application or in a bigger Shiny context? Most of the performance gain is not coming from vuejs here, and if a minimal application, we could just stick with vanilla JS.

timelyportfolio avatar May 02 '17 02:05 timelyportfolio

The idea is to build an atlas with a collection of distribution maps in topic of prehistory (mainly point geometries), that one can compare with each other (like in a simple GIS viewer but in the browser). The project is in the very beginning, I just want to check, what's possible in R.

In the atlas I would like to include also the results from the genetics regarding the prehistoric migration and the distribution of modern YDNA-haplogroups (quite a lot of polygon geometries with several properties).

In this example I try to visualise the Ratio of two different haplogroups. The initial representation is correct, but when I select an other property and go back to the initial haplogroup R1b, the calculated results are wrong. Any Idea, how to fix that?

I try to avoid a solution with Shiny.

TrantorM avatar May 03 '17 00:05 TrantorM

I would be interested in alternative solutions.

Is it possible to assign a function to the addgeo.args[7].valueProperty expression in order to compute the valueProperty based on the selected property? Something like addgeo.args[7].valueProperty = function(feature) {return feature.properties.{{selected}} / feature.properties.area_sqmi}. I can't get it running.

library(htmltools)
library(leaflet) # I installed leaflet 1.0 with devtools::install_github("timelyportfolio/[email protected]")
library(leaflet.extras)
library(vueR)

topojson <- readr::read_file('https://rawgit.com/TrantorM/leaflet-choropleth/gh-pages/examples/basic_topo/crimes_by_district.topojson')

map <- leaflet() %>% 
  setView(-75.14, 40, zoom = 11) %>% 
  addProviderTiles("CartoDB.Positron") %>% 
  addGeoJSONChoropleth(
    topojson,
    valueProperty = JS(paste0("function(feature) {return feature.properties.{{selected}} / feature.properties.area_sqmi}")),
    group = "choro"
  )

ui <- tagList(tags$div(id="app",
                 tags$select("v-model" = "selected",
                             tags$option("disabled value"="","Select one"),
                             tags$option("incidents"),
                             tags$option("area_sqmi"),
                             tags$option("dist_num")),
                 tags$span("Selected: {{selected}}"),
                 tags$div(map)
        ),
        tags$script(
          "
          var app = new Vue({
            el: '#app',
            data: {
              selected: 'incidents'
            },
            watch: {
              selected: function() {
                // uncomment debugger below if you want to step through debugger;
          
                // only expect one; if we expect multiple leaflet then we will need to be more specific
                var instance = HTMLWidgets.find('.leaflet');
                // get the map; could easily combine with above
                var map = instance.getMap();
                // we set group name to choro above, so that we can easily clear
                map.layerManager.clearGroup('choro');
                
                // now we will use the prior method to redraw
                var el = document.querySelector('.leaflet');
                // get the original method
                var addgeo = JSON.parse(document.querySelector(\"script[data-for='\" + el.id + \"']\").innerText).x.calls[1];
                addgeo.args[7].valueProperty = 'function(feature) {return feature.properties.{{selected}} / feature.properties.area_sqmi}';
                LeafletWidget.methods.addGeoJSONChoropleth.apply(map,addgeo.args);
              }
            }
          });
          "
        ),
        html_dependency_vue(offline=FALSE,minified=FALSE))

browsable(ui)

TrantorM avatar May 05 '17 17:05 TrantorM