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

Layers fail with no data #252

Closed
16 tasks done
mitchelloharawild opened this issue Dec 9, 2019 · 14 comments
Closed
16 tasks done

Layers fail with no data #252

mitchelloharawild opened this issue Dec 9, 2019 · 14 comments

Comments

@mitchelloharawild
Copy link
Contributor

mitchelloharawild commented Dec 9, 2019

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

  • arc
  • column
  • geojson
  • greatcircle
  • grid
  • heatmap
  • hexagon
  • line
  • mesh
  • path
  • pointcloud
  • polygon
  • scatterplot
  • screengrid
  • text
  • trips
@SymbolixAU
Copy link
Collaborator

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.

@mitchelloharawild
Copy link
Contributor Author

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).

@SymbolixAU
Copy link
Collaborator

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).

@dcooley
Copy link
Collaborator

dcooley commented Jan 27, 2020

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)

dcooley added a commit that referenced this issue Jan 27, 2020
@mitchelloharawild
Copy link
Contributor Author

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)?

@dcooley
Copy link
Collaborator

dcooley commented Jan 27, 2020

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

@mitchelloharawild
Copy link
Contributor Author

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.

@dcooley
Copy link
Collaborator

dcooley commented Jan 27, 2020

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.

@mitchelloharawild
Copy link
Contributor Author

That would work. 👍

@dcooley
Copy link
Collaborator

dcooley commented Jan 27, 2020

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, ... ) )
    }
}

@dcooley
Copy link
Collaborator

dcooley commented Jan 27, 2020

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 )

@dcooley
Copy link
Collaborator

dcooley commented Jan 27, 2020

@mitchelloharawild could you try devtools::install_github("SymbolixAU/mapdeck", ref = "issue252") for me and let me know if it works for you?

dcooley added a commit that referenced this issue Jan 27, 2020
@dcooley
Copy link
Collaborator

dcooley commented Jan 3, 2024

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)

@dcooley
Copy link
Collaborator

dcooley commented Jan 3, 2024

now in master branch

@dcooley dcooley closed this as completed Jan 3, 2024
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