24

I am visualizing some vector data using leaflet in r which has multiple non-spatial variables that a user might be interested in. I want to allow the user to select one variable that will determine the color of the features. I accomplish this with the baseGroups argument to the addLayersControl function, where each group is the same data with a different color palette. This works fine for switching the colors of the features themselves, but does not allow me to switch between legends- every legend I add is always shown, even if I use the appropriate group within addLegend. See the example code and screenshot below:

data <- data.frame(long = c(-93.2, -93, -93.5), lat = c(44.9, 45, 44.9), 
               var1 = c(1,2,3), var2 = c(10, 9, 1))

pal1 <- colorNumeric(palette = "Blues", domain = data$var1)
pal2 <- colorNumeric(palette = "Reds", domain = data$var2)

leaflet(data) %>%
  addCircleMarkers(color = ~pal1(var1), group = "var1") %>%
  addCircleMarkers(color = ~pal2(var2), group = "var2") %>%
  addLegend(pal = pal1, values = ~var1, group = "var1") %>%
  addLegend(pal = pal2, values = ~var2, group = "var2") %>%
  addLayersControl(baseGroups = c("var1", "var2"), position = "topleft")

If I replace baseGroups with overlayGroups in my layers control, this works as expected, and only the legends for the selected groups are shown. However, this option isn't ideal because I don't want the user to be able to select multiple groups or deselect all groups.

enter image description here

These questions are quite similar to mine, but the accepted solutions both use overlayGroups, while I want to stick with baseGroups. I'm also hoping to avoid using shiny, if possible.

Joe
  • 3,831
  • 4
  • 28
  • 44

1 Answers1

14

It seems legends in baseGroups won't remove/re-added as if in overlayGroups, which can be further proved by the persistence of legends even after calling hideGroup("var1").

A crude workaround can be adding an event handler to hide/unhide legends according to the current selected group of baseGroups using group = "<groupName>" as a key, and nothing else should need to be changed. For example:

htmlwidgets::onRender("
    function(el, x) {
      var updateLegend = function () {
          var selectedGroup = document.querySelectorAll('input:checked')[0].nextSibling.innerText.substr(1);

          document.querySelectorAll('.legend').forEach(a => a.hidden=true);
          document.querySelectorAll('.legend').forEach(l => {
            if (l.children[0].children[0].innerText == selectedGroup) l.hidden=false;
          });
      };
      updateLegend();
      this.on('baselayerchange', e => updateLegend());
    }")

Demo

demo of workaround

Source of Demo

require(leaflet)


data <- data.frame(long = c(-93.2, -93, -93.5), lat = c(44.9, 45, 44.9), 
                   var1 = c(1,2,3), var2 = c(10, 9, 1))

pal1 <- colorNumeric(palette = "Blues", domain = data$var1)
pal2 <- colorNumeric(palette = "Reds", domain = data$var2)

leaflet(data) %>%
  addCircleMarkers(color = ~pal1(var1), group = "var1") %>%
  addCircleMarkers(color = ~pal2(var2), group = "var2") %>%
  addLegend(pal = pal1, values = ~var1, group = "var1") %>%
  addLegend(pal = pal2, values = ~var2, group = "var2") %>%
  addLayersControl(baseGroups = c("var1", "var2"), 
                   position = "topleft",
                   options = layersControlOptions(collapsed=F)) %>%
  htmlwidgets::onRender("
    function(el, x) {
      var updateLegend = function () {
          var selectedGroup = document.querySelectorAll('input:checked')[0].nextSibling.innerText.substr(1);

          document.querySelectorAll('.legend').forEach(a => a.hidden=true);
          document.querySelectorAll('.legend').forEach(l => {
            if (l.children[0].children[0].innerText == selectedGroup) l.hidden=false;
          });
      };
      updateLegend();
      this.on('baselayerchange', e => updateLegend());
    }")
Quar
  • 1,032
  • 11
  • 12
  • 2
    This answer is great but it does not appear to work if the leaflet object is output to an r markdown document that uses tabs. – al-obrien Apr 09 '20 at 02:18
  • Wouldn't it be easier to use 'overlayGroups' instead of 'baseGroups'? Because then you wouldn't need necessarily the 'htmlwidgets::onRender' – g07kore May 22 '20 at 08:32
  • 3
    @g07kore Yes, it is easier and PO is aware of this approach, but as PO stated "this option isn't ideal because I don't want the user to be able to select multiple groups or deselect all groups". – Quar May 22 '20 at 18:32
  • 3
    This is very helpful but fragile. The JS code requires the legend title to be exactly the same as the name in the layers control. This can easily break, for example if you give a title to the legend. – Kent Johnson Feb 06 '21 at 22:10
  • @Quar thank you! I had exactly this problem and this was just the solution I was looking for. – leviemb Jan 20 '23 at 20:17