vue-r / vueR

vue.js for R
https://vue-r.github.io/vueR
Other
140 stars 14 forks source link

How to link vueR with leaflet? #4

Closed TrantorM closed 1 month ago

TrantorM commented 7 years ago

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/leaflet@v1.0")
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)
timelyportfolio commented 7 years ago

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/leaflet@v1.0")
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 commented 7 years ago

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 commented 7 years ago

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

TrantorM commented 7 years ago

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.

timelyportfolio commented 7 years ago

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.

TrantorM commented 7 years ago

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 commented 7 years ago

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/leaflet@v1.0")
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)