How to link vueR with leaflet?
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)
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)
live example; I did have to change to update the valueProperty. could have possibly used Vue.nextTick also
https://bl.ocks.org/timelyportfolio/1a7d43b14a23665857ad5ef421d9ac41
After all said and done, not sure if it is worth or not unless you plan to use other vuejs components.
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.
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.
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.
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)