tmap icon indicating copy to clipboard operation
tmap copied to clipboard

removing tm_markers layer with text in shiny not working

Open stragu opened this issue 10 months ago • 7 comments

This is a spinoff from issue #498 . In summary: removing a layer of tm_markers() wouldn't work in tmap 3.3 unless clustering was turned off. Now, in tmap4, the workaround does not work anymore, but even worse: the other point renderings (dots, symbols and bubbles) are affected by the issue.

tmap 3.3-4

In tmap 3.3-4, using the code below results in persistent markers after click, despite the use of tm_remove_layer().

library(shiny)
library(tmap)
library(sf)
tmap_mode("view")

# create initial point data
click_point <- st_point(c(0, 0))
click_sf <- st_sfc(click_point, crs = "WGS84")

# UI
ui <- fluidPage(
  tmapOutput("map")
)

server <- function(input, output, session) {
  # draw tmap
  output$map <- renderTmap({
    tm_shape(click_sf) +
      tm_markers(zindex = 401)
  })
  
  # observe click, remove and redraw only layer
  observeEvent(input$map_click, {
    click_point <- st_point(c(input$map_click$lng, input$map_click$lat))
    click_sf <- st_sfc(click_point, crs = "WGS84")
    tmapProxy("map", session, {
      tm_remove_layer(401) +
        tm_shape(click_sf) +
        tm_markers(zindex = 401)
    })
  })
}	

shinyApp(ui, server)

This is an issue specific to tm_markers(), astm_bubbles(), tm_dots() or tm_bubbles() don't have the same issue.

A workaround is to turn the clustering off with clustering = FALSE, to end up with:

tmapProxy("map", session, {
      tm_remove_layer(401) +
        tm_shape(click_sf) +
        tm_markers(zindex = 401, clustering = FALSE)
    })

This makes sense, as tm_bubbles(), tm_dots() or tm_bubbles() have clustering off by default.

However, now that I am using tmap 4, the workaround does not work.

tmap 4

Using the same code in tmap 4 results in the following message:

── tmap v3 code detected ────────────────────────────────────────────────────────────
[v3->v4] `tm_text()`: migrate the layer options 'clustering' to 'options =
opt_tm_text(<HERE>)'

Following the instructions to end up with:

tmapProxy("map", session, {
      tm_remove_layer(401) +
        tm_shape(click_sf) +
        tm_markers(zindex = 401,
                   options = opt_tm_text(clustering = FALSE))
    })

... does not resolve the issue: the app crashes as soon as the map is clicked:

Warning in renderWidget(instance) :
  Ignoring prepended content; prependContent can't be used in a Shiny render call
Warning: Error in if: argument is of length zero
  86: tm_markers
  81: observe [/home/stragu/OneDrive/r-projects/2024-12-06_possible-tmap-bug-remove-layers/test_app/app2.R#28]
  80: <observer:observeEvent(input$map_click)> [/tmp/Rtmpa151Ad/renv-package-new-364e9275e8b02/shiny/R/utils.R#1459]
   1: runApp [/tmp/Rtmpa151Ad/renv-package-new-364e9275e8b02/shiny/R/runapp.R#388]

The first issue is that the message is misleading: tm_markers() does not use options = opt_tm_text(), but it uses options = opt_tm_markers(). However, even if I use the correct opt_* function:

tmapProxy("map", session, {
      tm_remove_layer(401) +
        tm_shape(click_sf) +
        tm_markers(zindex = 401,
                   options = opt_tm_markers(clustering = FALSE))
    })

... the app still doesn't remove the pre-existing layer.

Finally, if I try using a different point rendering function as a workaround:

    tmapProxy("map", session, {
      tm_remove_layer(401) +
        tm_shape(click_sf) +
        tm_dots(zindex = 401)
    })

... the layer is persistent.

So the issue has gotten worse with tmap 4.

Any idea how to workaround this problem? And what might be a fix for it?

Note also that I tried setting the clustering to FALSE with e.g. dots, and it crashes the app when clicking:

tmapProxy("map", session, {
      tm_remove_layer(401) +
        tm_shape(click_sf) +
        tm_dots(zindex = 401,
                options = opt_tm_dots(clustering = FALSE))
    })

stragu avatar Feb 19 '25 03:02 stragu

Thx for letting us know @stragu Annoying bug, but glad to find out.

The following app doesn't crash and doesn't show the 'prependContent` warning:

ui <- fluidPage(
	tmapOutput("map")
)

server <- function(input, output, session) {
	# draw tmap
	output$map <- renderTmap({
		tm_shape(click_sf) +
			tm_dots(zindex = 401)
			#tm_markers(zindex = 401)
	})

	# observe click, remove and redraw only layer
	observeEvent(input$map_click, {
		click_point <- st_point(c(input$map_click$lng, input$map_click$lat))
		click_sf <- st_sfc(click_point, crs = "WGS84")
		tmapProxy("map", session, {
			tm_remove_layer(401) +
				tm_shape(click_sf) +
				tm_dots(zindex = 401)
				#tm_markers(zindex = 401, options = opt_tm_markers(clustering = FALSE))
		})
	})
}

shinyApp(ui, server)

So probably this warning is cause by some css styling regarding the markers. Will look into it.

The main issue remains: the point is not removed. Will also look into it asap.

mtennekes avatar Feb 21 '25 07:02 mtennekes

Issue should be solved more or less. At least, both tm_dots and tm_markers should work. Please test @stragu

Still some minor issues to be solved:

  • It's not possible to use CSS with shiny (https://www.rdocumentation.org/packages/htmlwidgets/versions/1.6.4/topics/prependContent) tmap uses additional CSS for a few things: background color and marker widths/heights (https://github.com/r-tmap/tmap/issues/973#issuecomment-2565996901)
  • tm_markers with text does not work well, because currently tm_markers will run both tm_symbols and tm_text. For leaflet this is inefficient because probably both can be achieved with addMarkers

mtennekes avatar Feb 22 '25 10:02 mtennekes

Thank you for working on this @mtennekes ! I tested all my different cases in my original description:

  • 👍 Original 3.3 code works as expected
  • 👍 as does the (now unnecessary) workaround with clustering = FALSE (which still gives the v3->v4 warning, as expected)
  • 🛑 following the v3->v4 instructions to move to using options = opt_tm_text() still crashes (which I expected, as you said "tm_markers with text does not work well"):
Warning: Error in if: argument is of length zero
  86: tm_markers [/tmp/RtmpY3M48c/renv-package-new-69b73609ab1f/tmap/R/tm_layers_symbols.R#663]
  81: observe [/home/stragu/OneDrive/r-projects/2024-12-06_possible-tmap-bug-remove-layers/test_app/app2.R#26]
  80: <observer:observeEvent(input$map_click)> [/tmp/Rtmpa151Ad/renv-package-new-364e9275e8b02/shiny/R/utils.R#1459]
   1: runApp [/tmp/Rtmpa151Ad/renv-package-new-364e9275e8b02/shiny/R/runapp.R#388]
  • 👍 using opt_tm_markers() instead does solve the issue: no crash, no warning, layer removed as expected!
  • 👍 using a different layer like tm_dots() does work too
  • 🛑 using clustering = FALSE with tm_dots() still crashes:
Warning: Error in opt_tm_dots: unused argument (clustering = FALSE)
  88: as.list.environment
  86: tm_dots [/tmp/RtmpY3M48c/renv-package-new-69b73609ab1f/tmap/R/tm_layers_symbols.R#485]
  81: observe [/home/stragu/OneDrive/r-projects/2024-12-06_possible-tmap-bug-remove-layers/test_app/app2.R#26]
  80: <observer:observeEvent(input$map_click)> [/tmp/Rtmpa151Ad/renv-package-new-364e9275e8b02/shiny/R/utils.R#1459]
   1: runApp [/tmp/Rtmpa151Ad/renv-package-new-364e9275e8b02/shiny/R/runapp.R#388]

So thanks for fixing it for my usecase! However, the two remaining issues are the 2 crashes:

  • 🛑 when the v3->v4 warning leads to the users erroneously using opt_tm_text() in tm_markers()
  • 🛑 when tm_dots() has clustering = FALSE

stragu avatar Mar 21 '25 03:03 stragu

Happy to close this as fixed, @mtennekes ?

I can open new tickets for the two remaining crashes if you prefer.

stragu avatar Apr 16 '25 04:04 stragu

@stragu We can leave this open: your post from last month is very useful, thx for that

mtennekes avatar Apr 16 '25 09:04 mtennekes

I guess most subissues should be solved now by the following:

I've improved the v3-v4 warning: 'clustering' should be moved to opt_tm_markers and not opt_tm_text.

Some context: so far, clustering is only available for text layers, not for symbols (and therefore also not for dots). tm_markers is basically a duo layer, so tm_text + tm_dots. opt_tm_markers contains both text and symbol options. Therefore, it fails when you enter only the text options.

The main issue remains, so a marker with text (so a duo layer) is not properly removed.

If there are any remaining (sub)issues let me know @stragu

mtennekes avatar Apr 23 '25 20:04 mtennekes

Thanks for fixing the message, I can confirm it works with the current source! The two crashes in https://github.com/r-tmap/tmap/issues/1044#issuecomment-2742153353 remain but users are less likely to hit the first one with the fixed message.

stragu avatar Apr 25 '25 14:04 stragu