使用 Leaflet/Shiny 选择和取消选择多个多边形时更改样式 [英] Changing styles when selecting and deselecting multiple polygons with Leaflet/Shiny
问题描述
在我正在开发的 Leaflet Shiny 应用程序中选择和取消选择多边形时,我在更改多边形样式时遇到了一些问题.在我当前的应用程序中,当您单击一个多边形时,该多边形会以不同的颜色突出显示.理想情况下,我希望用户能够选择并突出显示多个多边形.我还希望用户能够重新单击单个突出显示的多边形以取消选择它.
我能够管理的最好方法是选择多个多边形,为它们提供已选择"的相同组 ID,然后在重新单击多边形时取消选择整个组.这是一些示例/可重现的代码:
库(光栅)图书馆(闪亮)图书馆(传单)#加载形状文件rwa <- getData("GADM", country = "RWA", level = 1)闪亮的应用程序(ui =流体页面(传单输出(地图")),服务器 <- 功能(输入,输出,会话){#初始地图输出输出$map <- renderLeaflet({传单() %>%addTiles() %>%addPolygons(数据 = rwa,fillColor = "白色",填充不透明度 = 1,颜色=黑色",行程 = T,重量 = 1,layerId = rwa@data$OBJECTID,组=地区")}) #END 渲染传单观察事件(输入$map_shape_click,{#为单击的多边形创建对象点击 <- 输入$map_shape_click#define 二级区域地图的传单代理代理 <- 传单代理(地图")#subset 区域 shapefile 通过单击的多边形selectedReg <-rwa[rwa@data$OBJECTID == click$id,]#map 点击多边形代理 %>% addPolygons(data = selectedReg,fillColor = "红色",填充不透明度 = 1,重量 = 1,颜色=黑色",行程 = T,组=选择",# layerId = "选中")layerId = selectedReg@data$OBJECTID)#remove 被点击两次的多边形组如果(点击$组==选择"){代理 %>%清除组(组=选择")} #END 条件}) #END OBSERVE 事件}) #END SHINYAPP
在上面的例子中,每个被点击的多边形都会变成红色.如果再次单击先前选择的红色多边形,则将从地图中清除每个红色多边形,留下初始的白色多边形渲染.
当我一次只使用一个多边形时,我可以通过使用字符串 layerId selected"(在上面的代码中注释掉)来实现所需的选择/取消选择效果,但是这样做会消除我选择和取消选择的能力同时突出显示多个多边形.
我愿意接受任何和所有建议!
答案就在layerIds中.我不明白这些是如何应用于我的多边形和删除形状的——理解这是关键.这可能不是最优雅的解决方案,但它可以完成工作!
在下面的代码中,卢旺达的初始地图渲染有一个layerId
为rwa@data$NAME_1
,即区域名称.您可以在 label
也设置为 rwa@data$NAME_1
的情况下看到这一点.所以在下图中,最左边的多边形被标记为 Iburengerazuba,它的属性在 NAME_1
列中.此 layerId 为您在此初始地图渲染中的任何点击事件设置 click$id
. 因此,正如这个多边形被标记为 Iburengerazuba,它的 click$id
也将设置为 Iburengerazuba.
接下来是形状点击的 observeEvent
.
移除红色点击多边形的关键是为这些多边形赋予一个不同于初始地图渲染的 layerId
. 请注意,在上图中,白色多边形是标记为 Iburengerazuba 现在标记为 3.这是因为第二个 addPolygons
调用中的 layerId
设置为 CC_1
INSTEAD OF NAME_1代码>.因此,底层白色地图有一个 NAME_1 图层 ID,因此有 NAME_1 点击 ID,而绘制在其顶部的任何红色点击多边形都有一个 CC_1 图层 ID,因此有 CC_1 点击 ID.
if
语句声明如果您的 click$id
已存在于 clickedPolys
多边形中,则该形状将被移除.这有点令人困惑,所以再一次,它可能有助于遍历每一行代码并玩弄它以真正理解.
再次使用上面的示例,单击最左侧的多边形会将 layerId
Iburengerazuba 添加到 clickedIds$ids
向量中.此单击事件触发第二次地图绘制,以不同的样式在其自身顶部绘制单击的多边形,并且 layerId
为 3(来自 CC_1
列).我们想说,如果任何红色多边形被点击两次(if(click$id %in% clickedPolys@data$CC_1)
),它就被视为取消选择,并且应该从地图.因此,如果您单击 layerId
为 3 的最左侧红色多边形,则 clickedIds$ids
向量将由 Iburengerazuba
和 组成3代码>.
clickedPolys
多边形的 NAME_1
列中的 Iburengerazuba 对应 CC_1
列中的 3,触发 if
语句.调用 removeShape(layerId = click$id)
意味着删除对应于该 click$id 的形状.所以在这种情况下,clickedPolys
多边形的 CC_1
layerId
为 3.
请记住,每次点击 id,NAME_1
和 CC_1
都记录在您的 clickedIds$ids
矢量.该向量将您的卢旺达 shapefile 设置为子集以映射所有单击的多边形,因此当您单击多边形时,clickedPolys
多边形正在动态更新(使用 print
调用来检查如果这对您没有意义,请编写代码!).删除任何双击的形状并不足以正确绘制所有内容 --您需要从 clickedIds$ids
向量 中删除取消选择的 layerId,包括 NAME_1 和 CC_1.我将每个取消选择的 CC_1 layerId
与其对应的 NAME_1
值匹配,并从 clickedIds$ids
向量中删除了这两个属性,以便将它们从clickedPolys
多边形.
瞧!现在您可以选择和取消选择您想要的任何多边形!
库(光栅)图书馆(闪亮)图书馆(传单)#加载形状文件rwa <- getData(GADM",国家 = RWA",级别 = 1)闪亮的应用程序(ui =流体页面(传单输出(地图")),服务器 <- 功能(输入,输出,会话){#创建空向量来保存所有点击IDclickedIds <-reactiveValues(ids = vector())#初始地图输出输出$map <- renderLeaflet({传单() %>%addTiles() %>%addPolygons(数据 = rwa,fillColor =白色",填充不透明度 = 1,颜色=黑色",行程 = T,重量 = 1,layerId = rwa@data$NAME_1,组=区域",标签 = rwa@data$NAME_1)}) #END 渲染传单观察事件(输入$map_shape_click,{#为单击的多边形创建对象点击 <- 输入$map_shape_click#define 二级区域地图的传单代理proxy <-leafletProxy(map")#将所有点击ID添加到空向量中clickedIds$ids <- c(clickedIds$ids, click$id)#shapefile 与所有单击的多边形 - 原始 shapefile 由单击列表中的所有管理员名称子集clickedPolys <- rwa[rwa@data$NAME_1 %in% clickedIds$ids, ]#如果当前点击ID [来自CC_1] 存在于点击的多边形中(如果它被点击了两次)if(click$id %in% clickedPolys@data$CC_1){#define 将与 CC_1 点击 ID 匹配的 NAME 子集的向量nameMatch <- clickedPolys@data$NAME_1[clickedPolys@data$CC_1 == click$id]#从 clickedPolys shapefile 中删除当前 click$id 及其名称匹配clickedIds$ids <- clickedIds$ids[!clickedIds$ids %in% click$id]clickedIds$ids <- clickedIds$ids[!clickedIds$ids %in% nameMatch]#从地图中删除突出显示的多边形代理 %>% removeShape(layerId = click$id)} 别的 {#map 突出显示的多边形代理 %>% addPolygons(data = clickedPolys,填充颜色=红色",填充不透明度 = 1,重量 = 1,颜色=黑色",行程 = T,标签 = clickedPolys@data$CC_1,layerId = clickedPolys@data$CC_1)} #END 条件}) #END OBSERVE 事件}) #END SHINYAPP
I'm having some problems changing polygon styles when selecting and deselecting polygons in a Leaflet Shiny app I'm working on. In my current app, when you click on a polygon, that polygon is highlighted with a different color. Ideally, I want the user to be able to select and highlight multiple polygons. I also want the user to be able to re-click a single highlighted polygon to deselect it.
The best that I've been able to manage is to select multiple polygons, give them the same group ID "selected", then deselect that entire group when a polygon is re-clicked. Here's some example/reproducible code:
library(raster)
library(shiny)
library(leaflet)
#load shapefile
rwa <- getData("GADM", country = "RWA", level = 1)
shinyApp(
ui = fluidPage(
leafletOutput("map")
),
server <- function(input, output, session){
#initial map output
output$map <- renderLeaflet({
leaflet() %>%
addTiles() %>%
addPolygons(data = rwa,
fillColor = "white",
fillOpacity = 1,
color = "black",
stroke = T,
weight = 1,
layerId = rwa@data$OBJECTID,
group = "regions")
}) #END RENDER LEAFLET
observeEvent(input$map_shape_click, {
#create object for clicked polygon
click <- input$map_shape_click
#define leaflet proxy for second regional level map
proxy <- leafletProxy("map")
#subset regions shapefile by the clicked on polygons
selectedReg <-rwa[rwa@data$OBJECTID == click$id,]
#map clicked on polygons
proxy %>% addPolygons(data = selectedReg,
fillColor = "red",
fillOpacity = 1,
weight = 1,
color = "black",
stroke = T,
group = "selected",
# layerId = "selected")
layerId = selectedReg@data$OBJECTID)
#remove polygon group that are clicked twice
if(click$group == "selected"){
proxy %>%
clearGroup(group = "selected")
} #END CONDITIONAL
}) #END OBSERVE EVENT
}) #END SHINYAPP
In the above example, every clicked polygon turns red. If a previously-selected red polygon is clicked again, every red polygon is cleared from the map, leaving the initial white polygon renderings.
I can accomplish the desired selecting/deselecting effect when I'm working with only one polygon at a time by using the string layerId "selected" (commented out in the above code), but doing that removes my ability to select and highlight multiple polygons at the same time.
I'm open to any and all suggestions!
The answer lies in layerIds. I wasn't understanding how these were applied to my polygons and removing shapes--understanding this is key. This might not be the most elegant solution, but it gets the job done!
In the below code, the initial map rendering of Rwanda has a layerId
of rwa@data$NAME_1
, which are the region names. You can see this in action with the label
also being set as rwa@data$NAME_1
. So in the below image, the leftmost polygon is labeled as Iburengerazuba, its attribute in the NAME_1
column. This layerId sets the click$id
for any click events you have on this initial map rendering. So, just as this polygon is labeled Iburengerazuba, its click$id
will also be set as Iburengerazuba. As stated in the Leaflet Shiny documentation, if you've got more than one polygon, this needs to be a vectorized argument. If you only need to select and deselect ONE polygon (so only one region at a time, in this example), you could use a layerId
string, as I mentioned in my question (such as layerId = "selected"
).
Next up is the observeEvent
for your shape click. Thanks to the help of user @John Paul, I figured out how to save all click events (click ids specifically in this case) made on the map. I saved those in a reactive vector, then subset my shapefile by those click ids. The code is pretty thoroughly commented, so hopefully anyone else looking for this same solution can figure out exactly what's going on.
The final bit of code (housed in the if...else
conditional statement) is probably the most confusing. Let's look at the else
portion of the code first. (Note: Your initial map click is going to trigger this event because there's no way for the if
conditions to have been met upon first click.) If any white polygon is clicked, the addPolygons()
call is triggered, adding the clicked polygon onto the map with different styling (in this case, it's red). This is plotting an entirely different polygon on top of the leafletProxy
object!
The key to removing the red clicked polygons is giving these polygons a different layerId
than the initial map rendering. Note that in the above image, the white polygon that was labeled Iburengerazuba is now labeled as 3. This is because the layerId
in the second addPolygons
call is set as CC_1
INSTEAD OF NAME_1
. So, bottom layer white map has a NAME_1 layerID and therefore NAME_1 click ids, whereas any red clicked polygon plotted on top of that has a CC_1 layerId and therefore CC_1 click ids.
The if
statements states that if your click$id
already exists in the clickedPolys
polygon, that this shape is removed. This is kind of confusing, so again, it might help to go through each line of code and play around with it to truly understand.
Again using the above example, clicking the leftmost polygon adds the layerId
Iburengerazuba to the clickedIds$ids
vector. This click event triggers a second map drawing, plotting the clicked polygon on top of itself in a different style and with a layerId
of 3 (from the CC_1
column). We want to say that if any red polygon is clicked twice (if(click$id %in% clickedPolys@data$CC_1)
), it counts as a deselection, and that polygon should be removed from the map. So if you click on the red leftmost polygon with a layerId
of 3, the clickedIds$ids
vector will be comprised of Iburengerazuba
and 3
. Iburengerazuba in the NAME_1
column of the clickedPolys
polygon corresponds to 3 in the CC_1
column, triggering the if
statement. The call removeShape(layerId = click$id)
means to remove the shape that corresponds to that click$id. So in this case, the clickedPolys
polygon with a CC_1
layerId
of 3.
Keep in mind that every click id, both NAME_1
and CC_1
are being recorded in your clickedIds$ids
vector. This vector is subsetting your Rwanda shapefile to map all clicked polygons, so as you're clicking polygons, the clickedPolys
polygon is dynamically updating (use print
calls to check every bit of code if this isn't making sense to you!). Removing any double-clicked shape isn't enough to plot everything correctly--you need to remove deselected layerIds, both NAME_1 and CC_1, from the clickedIds$ids
vector. I matched each deselected CC_1 layerId
to its corresponding NAME_1
value and removed both of those attributes from the clickedIds$ids
vector so that they are removed from the clickedPolys
polygon.
Voila! Now you can select and deselect any polygons you want!
library(raster)
library(shiny)
library(leaflet)
#load shapefile
rwa <- getData("GADM", country = "RWA", level = 1)
shinyApp(
ui = fluidPage(
leafletOutput("map")
),
server <- function(input, output, session){
#create empty vector to hold all click ids
clickedIds <- reactiveValues(ids = vector())
#initial map output
output$map <- renderLeaflet({
leaflet() %>%
addTiles() %>%
addPolygons(data = rwa,
fillColor = "white",
fillOpacity = 1,
color = "black",
stroke = T,
weight = 1,
layerId = rwa@data$NAME_1,
group = "regions",
label = rwa@data$NAME_1)
}) #END RENDER LEAFLET
observeEvent(input$map_shape_click, {
#create object for clicked polygon
click <- input$map_shape_click
#define leaflet proxy for second regional level map
proxy <- leafletProxy("map")
#append all click ids in empty vector
clickedIds$ids <- c(clickedIds$ids, click$id)
#shapefile with all clicked polygons - original shapefile subsetted by all admin names from the click list
clickedPolys <- rwa[rwa@data$NAME_1 %in% clickedIds$ids, ]
#if the current click ID [from CC_1] exists in the clicked polygon (if it has been clicked twice)
if(click$id %in% clickedPolys@data$CC_1){
#define vector that subsets NAME that matches CC_1 click ID
nameMatch <- clickedPolys@data$NAME_1[clickedPolys@data$CC_1 == click$id]
#remove the current click$id AND its name match from the clickedPolys shapefile
clickedIds$ids <- clickedIds$ids[!clickedIds$ids %in% click$id]
clickedIds$ids <- clickedIds$ids[!clickedIds$ids %in% nameMatch]
#remove that highlighted polygon from the map
proxy %>% removeShape(layerId = click$id)
} else {
#map highlighted polygons
proxy %>% addPolygons(data = clickedPolys,
fillColor = "red",
fillOpacity = 1,
weight = 1,
color = "black",
stroke = T,
label = clickedPolys@data$CC_1,
layerId = clickedPolys@data$CC_1)
} #END CONDITIONAL
}) #END OBSERVE EVENT
}) #END SHINYAPP
这篇关于使用 Leaflet/Shiny 选择和取消选择多个多边形时更改样式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!