mapdeck
mapdeck copied to clipboard
Layers fail with no data
Describe the bug An error occurs when adding a layer without data. Having no data to display can be common in interactive environments.
To Reproduce
library(sf)
#> Linking to GEOS 3.6.2, GDAL 2.2.3, PROJ 5.1.0
library(mapdeck)
sf <- sf::st_as_sf( capitals, coords = c("lon", "lat") )
mapdeck() %>%
add_scatterplot(
data = head(sf,0)
, radius = 1000000
, fill_colour = "country"
, layer_id = "scatter_layer"
, tooltip = "capital"
)
#> Error in rcpp_point_geojson(data, l, geometry_column, digits, "scatterplot"): 'getCharCE' must be called on a CHARSXP
Created on 2019-12-09 by the reprex package (v0.2.1)
Expected behaviour The layer to be added to the plot, without any data to display.
Versions Current dev version
TODO
- [x] arc
- [x] column
- [x] geojson
- [x] greatcircle
- [x] grid
- [x] heatmap
- [x] hexagon
- [x] line
- [x] mesh
- [x] path
- [x] pointcloud
- [x] polygon
- [x] scatterplot
- [x] screengrid
- [x] text
- [x] trips
yeah at the moment I always check for nrow() > 0
, but it's not an ideal solution.
I've made this issue in spatialwidget as that's where the fix will need to be.
I'll leave this one open though until it's solved.
Great. That's what I'm using at the moment too, along with clearing the layer when nrow() == 0
.
The minor drawback to clearing the layer to remove points is that it also removes the legend (as it should for clearing a layer, not so much for no data).
I've been exploring solutions to this in both the C++ (spatialwidget) side and the javascript (deck.gl) side.
Deck.gl will error if it has invalid coordinates (i.e., empty data), so it may not be possible to simply pass it a {}
object. The solution may actually be to check the data in R and clear the layer if the data.frame / sf
object is empty (as we're currently doing).
I think the soultion is to have a check at the top of each function
if( nrow( data ) == 0 ) {
return( clear_path( map, layer_id ) )
}
(apart from geojson, where it has to be placed after any conversions to a data.frame)
I don't think this will preserve legends. It's been a little while since I've looked at the deck.gl side, but if the legend is coupled with the layers, then it may be worth raising an issue with the,.
As for passing {}
, does it work if you pass a dataset with just the structure (but no values)?
What should the legend show, if there isn't any data on the map?
If you really do want a legend without any data, you'll have to create the legend manually. I don't see another way around this as the legend is intimately tied to the data on the map. And to me it doesn't make sense to have a legend, but no data being shown.
As for passing {}, does it work if you pass a dataset with just the structure (but no values)?
No, I don't think so.
mapdeck() %>%
add_path(
data = roads[0,]
)
# Error in rcpp_path_geojson(data, l, geometry_column, digits, "path") :
# Error creating data layer
mapdeck() %>%
add_scatterplot(
data = data.frame(lon = numeric(), lat = numeric())
, lon = "lon", lat = "lat"
)
# Error in rcpp_point_geojson_df(data, l, geometry_column, digits, "scatterplot") :
# Error creating data layer
# In addition: Warning messages:
# 1: In min(lon) : no non-missing arguments to min; returning Inf
# 2: In max(lon) : no non-missing arguments to max; returning -Inf
# 3: In min(lat) : no non-missing arguments to min; returning Inf
# 4: In max(lat) : no non-missing arguments to max; returning -Inf
If you're animating data on the plot, and use a manually created legend to keep the values constant, I would expect the legend to remain static regardless of plotted values. AFAIK, clearing the layer will remove the legend associated with it.
how about if the various clear_()
functions gain the arguments
-
update_view = TRUE
-
clear_legend = TRUE
so then if they're false the associated calls in the layer_clear
function get by-passed.
That would work. :+1:
yeah might be the best solution.
The remaining questions is how should those arguments propogate into the clear_()
functions if nrow( data ) == 0
, because you don't want to use the update_view
argument from the add_()
function...
add_arc <- function(
map,
data = get_map_data(map),
...
update_view = TRUE,
focus_layer = FALSE
) {
if( nrow( data ) == 0 ) {
return( clear_arc( map, layer_id, ... ) )
}
}
ok, the clear_()
functions get the signature
clear_path <- function( map, layer_id = NULL, clear_legend = TRUE, clear_view = TRUE) {}
and the add_()
functions get ...
so you can pass these arguments in
add_path <- function(map, data, etc, ... ) {
if( nrow( data ) == 0 ) {
return( clear_path( map, layer_id, ... ) )
}
}
Which for the most part works
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader()
, dashboardSidebar(
actionButton(
inputId = "clear"
, label = "clear"
)
)
, dashboardBody(
mapdeck::mapdeckOutput(
outputId = "map"
)
)
)
server <- function(input, output) {
set_token( read.dcf("~/Documents/.googleAPI", fields = "MAPBOX") )
l1 <- legend_element(
variables = c("Begins with A", "Doesn't begin with A")
, colours = c("#00FF00FF", "#FF0000FF")
, colour_type = "fill"
, variable_type = "category"
)
js <- mapdeck_legend(l1)
output$map <- mapdeck::renderMapdeck({
mapdeck() %>%
add_path(
data = roads
, stroke_colour = "ROAD_NAME"
, legend = T
)
})
observeEvent(input$clear, {
mapdeck::mapdeck_update(map_id = "map") %>%
add_path(
data = roads[0, ]
, clear_legend = FALSE
, clear_view = FALSE
)
})
}
shinyApp(ui, server)
(although there is a minor bug in deck.gl where you have to have interacted with the map for it to know the current 'view state', to then know not to change the view. I'm watching this as part of #239 )
@mitchelloharawild could you try devtools::install_github("SymbolixAU/mapdeck", ref = "issue252")
for me and let me know if it works for you?
updated example:
library(shiny)
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader()
, dashboardSidebar(
actionButton(
inputId = "clear"
, label = "clear"
)
)
, dashboardBody(
mapdeck::mapdeckOutput(
outputId = "map"
)
)
)
server <- function(input, output) {
set_token(secret::get_secret("MAPBOX"))
output$map <- mapdeck::renderMapdeck({
mapdeck() %>%
add_path(
data = roads
, stroke_colour = "ROAD_NAME"
, legend = T
)
})
observeEvent(input$clear, {
mapdeck::mapdeck_update(map_id = "map") %>%
add_path(
data = roads[0, ]
, clear_legend = FALSE
, update_view = FALSE
)
})
}
shinyApp(ui, server)
now in master branch