Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to link vueR with leaflet? #4

Closed
TrantorM opened this issue May 1, 2017 · 7 comments
Closed

How to link vueR with leaflet? #4

TrantorM opened this issue May 1, 2017 · 7 comments

Comments

@TrantorM
Copy link

TrantorM commented May 1, 2017

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)
@timelyportfolio
Copy link
Collaborator

timelyportfolio commented May 1, 2017

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
Copy link
Collaborator

timelyportfolio commented May 1, 2017

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
Copy link
Collaborator

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

@TrantorM
Copy link
Author

TrantorM commented May 1, 2017

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
Copy link
Collaborator

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
Copy link
Author

TrantorM commented May 3, 2017

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
Copy link
Author

TrantorM commented May 5, 2017

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants