From b26f4ea24ef9c1ca53d3bc5337d923f02ace7e99 Mon Sep 17 00:00:00 2001 From: Sebastian Gatscha Date: Fri, 14 Jun 2024 01:16:31 +0200 Subject: [PATCH 1/9] docs: fix roxygen warnings --- NAMESPACE | 12 ++++++++++++ R/features.R | 12 ++++++++++++ R/logo.R | 4 ---- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index fa77694..74c559d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,17 @@ # Generated by roxygen2: do not edit by hand +S3method(leafem::addLineFeatures,leaflet) +S3method(leafem::addLineFeatures,leaflet_proxy) +S3method(leafem::addLineFeatures,mapdeck) +S3method(leafem::addLineFeatures,mapview) +S3method(leafem::addPointFeatures,leaflet) +S3method(leafem::addPointFeatures,leaflet_proxy) +S3method(leafem::addPointFeatures,mapdeck) +S3method(leafem::addPointFeatures,mapview) +S3method(leafem::addPolygonFeatures,leaflet) +S3method(leafem::addPolygonFeatures,leaflet_proxy) +S3method(leafem::addPolygonFeatures,mapdeck) +S3method(leafem::addPolygonFeatures,mapview) export(addCOG) export(addCopyExtent) export(addExtent) diff --git a/R/features.R b/R/features.R index 0e90bdf..4fdc3f4 100644 --- a/R/features.R +++ b/R/features.R @@ -95,6 +95,7 @@ addPointFeatures = function(map, ...) UseMethod("addPointFeatures") ### Point Features leaflet +#' @exportS3Method leafem::addPointFeatures addPointFeatures.leaflet <- function(map, data, pane, @@ -125,12 +126,15 @@ addPointFeatures.leaflet <- function(map, } ### Point Features leaflet_proxy +#' @exportS3Method leafem::addPointFeatures addPointFeatures.leaflet_proxy <- addPointFeatures.leaflet ### Point Features mapview +#' @exportS3Method leafem::addPointFeatures addPointFeatures.mapview = addPointFeatures.leaflet ### Point Features mapdeck +#' @exportS3Method leafem::addPointFeatures addPointFeatures.mapdeck <- function(map, data, ...) { @@ -151,6 +155,7 @@ addPointFeatures.mapdeck <- function(map, addLineFeatures = function(map, ...) UseMethod("addLineFeatures") ### Line Features leaflet +#' @exportS3Method leafem::addLineFeatures addLineFeatures.leaflet <- function(map, data, pane, @@ -177,12 +182,15 @@ addLineFeatures.leaflet <- function(map, } ### Line Features leaflet_proxy +#' @exportS3Method leafem::addLineFeatures addLineFeatures.leaflet_proxy <- addLineFeatures.leaflet ### Line Features mapview +#' @exportS3Method leafem::addLineFeatures addLineFeatures.mapview = addLineFeatures.leaflet ### Line Features mapdeck +#' @exportS3Method leafem::addLineFeatures addLineFeatures.mapdeck <- function(map, data, ...) { @@ -202,6 +210,7 @@ addLineFeatures.mapdeck <- function(map, addPolygonFeatures = function(map, ...) UseMethod("addPolygonFeatures") ### Polygon Features leaflet +#' @exportS3Method leafem::addPolygonFeatures addPolygonFeatures.leaflet <- function(map, data, pane, @@ -228,12 +237,15 @@ addPolygonFeatures.leaflet <- function(map, } ### Polygon Features leaflet_proxy +#' @exportS3Method leafem::addPolygonFeatures addPolygonFeatures.leaflet_proxy <- addPolygonFeatures.leaflet ### Polygon Features mapview +#' @exportS3Method leafem::addPolygonFeatures addPolygonFeatures.mapview = addPolygonFeatures.leaflet ### Polygon Features mapdeck +#' @exportS3Method leafem::addPolygonFeatures addPolygonFeatures.mapdeck <- function(map, data, ...) { diff --git a/R/logo.R b/R/logo.R index 750354b..ab714a2 100644 --- a/R/logo.R +++ b/R/logo.R @@ -123,7 +123,6 @@ addLogo <- function(map, } #' updateLogo -#' @inheritParams addLogo #' @rdname addLogo #' @export updateLogo <- function(map, img, layerId) { @@ -137,7 +136,6 @@ updateLogo <- function(map, img, layerId) { } #' removeLogo -#' @inheritParams addLogo #' @rdname addLogo #' @export removeLogo <- function(map, layerId) { @@ -149,7 +147,6 @@ removeLogo <- function(map, layerId) { } #' hideLogo -#' @inheritParams addLogo #' @rdname addLogo #' @export hideLogo <- function(map, layerId) { @@ -161,7 +158,6 @@ hideLogo <- function(map, layerId) { } #' showLogo -#' @inheritParams addLogo #' @rdname addLogo #' @export showLogo <- function(map, layerId) { From a65eb6fca45c6251a2087702478de9cc748b2b45 Mon Sep 17 00:00:00 2001 From: Sebastian Gatscha Date: Sun, 8 Sep 2024 23:06:29 +0200 Subject: [PATCH 2/9] layerviewcontrol --- NAMESPACE | 1 + R/layerviewcontrol.R | 152 ++++++++++++++++++ .../lib/layerviewcontrol/layerviewcontrol.js | 71 ++++++++ inst/test_layerviewcontrol.R | 129 +++++++++++++++ man/addLayerViewControl.Rd | 114 +++++++++++++ 5 files changed, 467 insertions(+) create mode 100644 R/layerviewcontrol.R create mode 100644 inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js create mode 100644 inst/test_layerviewcontrol.R create mode 100644 man/addLayerViewControl.Rd diff --git a/NAMESPACE b/NAMESPACE index 4cec218..10fe72c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -21,6 +21,7 @@ export(addGeoRaster) export(addGeotiff) export(addHomeButton) export(addImageQuery) +export(addLayerViewControl) export(addLocalFile) export(addLogo) export(addMouseCoordinates) diff --git a/R/layerviewcontrol.R b/R/layerviewcontrol.R new file mode 100644 index 0000000..a18806f --- /dev/null +++ b/R/layerviewcontrol.R @@ -0,0 +1,152 @@ +#' Add Custom View Controls to Leaflet Map +#' +#' This function adds custom views per group and optional home buttons for zooming to specific layers +#' or bounding boxes in a `leaflet` map. +#' +#' @param map A `leaflet` or `mapview` object to which view controls will be added. +#' @param view_settings A list specifying the view settings for each layer. Each list element should contain +#' either: +#' \itemize{ +#' \item \code{coords}: A vector of length 2 (latitude, longitude) for setting the view, or length 4 +#' (bounding box: lat1, lng1, lat2, lng2) for fitting the bounds. +#' \item \code{zoom}: The zoom level (used for `setView`). +#' \item \code{fly} (optional): A logical indicating whether to use `flyTo` or `flyToBounds` instead of `setView` or `fitBounds`. +#' \item \code{options} (optional): Additional options to pass to `setView`, `fitBounds`, or `flyTo`. +#' } +#' @param home_btns Logical. If `TRUE`, adds a "home" button next to each layer name in the layer control. +#' Clicking the home button zooms the map to the view specified for that layer in \code{view_settings}. +#' @param home_btn_options A list of options to customize the home button appearance and behavior. +#' Possible options include: +#' - `text`: The text or emoji to display on the button (default is '🏠'). +#' - `cursor`: CSS cursor style for the button (default is 'pointer'). +#' - `class`: CSS class name for the button (default is 'leaflet-home-btn'). +#' - `styles`: Semicolon separated CSS-string (default is 'float: inline-end;'). +#' +#' @return A modified `leaflet` map object with view controls and home buttons. +#' +#' @details +#' This function generates JavaScript that listens for `overlayadd` or `baselayerchange` events +#' and automatically sets the view or zoom level according to the specified \code{view_settings}. +#' If `home_btns` is enabled, a home button is added next to each layer in the layer control. +#' When clicked, it zooms the map to the predefined view of that layer. +#' +#' @examples +#' library(sf) +#' library(leaflet) +#' library(leafem) +#' +#' # Example data ########## +#' breweries91 <- st_as_sf(breweries91) +#' lines <- st_as_sf(atlStorms2005) +#' polys <- st_as_sf(leaflet::gadmCHE) +#' +#' # View settings ########## +#' view_settings <- list( +#' "Base_tiles1" = list( +#' coords = c(20, 50) +#' , zoom = 3 +#' ), +#' "Base_tiles2" = list( +#' coords = c(-110, 50) +#' , zoom = 5 +#' ), +#' "breweries91" = list( +#' coords = as.numeric(st_coordinates(st_centroid(st_union(breweries91)))) +#' , zoom = 8 +#' ), +#' "atlStorms2005" = list( +#' coords = as.numeric(st_bbox(lines)) +#' , options = list(padding = c(110, 110)) +#' ), +#' "gadmCHE" = list( +#' coords = as.numeric(st_bbox(polys)) +#' , options = list(padding = c(2, 2)) +#' , fly = TRUE +#' ) +#' ) +#' +#' leaflet() %>% +#' ## Baselayer +#' addTiles(group = "Base_tiles1") %>% +#' addProviderTiles("CartoDB", group = "Base_tiles2") %>% +#' +#' ## Overlays +#' addCircleMarkers(data = breweries91, group = "breweries91") %>% +#' addPolylines(data = lines, group = "atlStorms2005") %>% +#' addPolygons(data = polys, group = "gadmCHE") %>% +#' +#' ## LayerViewControl +#' addLayerViewControl( +#' view_settings, home_btns = TRUE, +#' home_btn_options = list( +#' "Base_tiles1" = list(text = '🏡', cursor = 'ns-resize', class = 'homebtn'), +#' "Base_tiles2" = list(text = '❤️', cursor = 'pointer'), +#' "atlStorms2005" = list(text = '🌎', cursor = 'all-scroll'), +#' "breweries91" = list(text = '🌎', styles = 'background-color: red'), +#' "gadmCHE" = list(text = '🌎', styles = 'float: none;') +#' ) +#' ) %>% +#' +#' ## LayersControl +#' addLayersControl( +#' baseGroups = c("Base_tiles1", "Base_tiles2"), +#' overlayGroups = c("breweries91", "atlStorms2005", "gadmCHE"), +#' options = layersControlOptions(collapsed = FALSE, autoZIndex = TRUE) +#' ) +#' +#' @export +addLayerViewControl <- function(map, view_settings, home_btns = FALSE, home_btn_options = list()) { + + # Initialize data structures for view settings and home buttons + view_data <- list() + home_data <- list() + + # Loop over each layer to populate view_data and home_data + for (layer in names(view_settings)) { + setting <- view_settings[[layer]] + + # Store coordinates and zoom options for setView or fitBounds + if (length(setting$coords) == 2) { + view_data[[layer]] <- list( + coords = setting$coords, + zoom = setting$zoom, + fly = setting[["fly"]] %||% FALSE, + options = setting$options + ) + } else if (length(setting$coords) == 4) { + view_data[[layer]] <- list( + bounds = setting$coords, + fly = setting[["fly"]] %||% FALSE, + options = setting$options + ) + } + + # Store home button data if enabled + if (isTRUE(home_btns)) { + home_data[[layer]] <- as.list(c( + layer = layer, home_btn_options[[layer]] + )) + } + } + + # Add deps & Pass view and home button data using invokeMethod + map$dependencies <- c(map$dependencies, layerViewControlDependencies()) + leaflet::invokeMethod( + map, + NULL, + 'addLayerViewControl', + view_data, + home_data + ) +} + + +layerViewControlDependencies <- function() { + list( + htmltools::htmlDependency( + "layerViewControl", + '0.0.1', + system.file("htmlwidgets/lib/layerviewcontrol", package = "leafem"), + script = c("layerviewcontrol.js") + )) +} diff --git a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js new file mode 100644 index 0000000..1057dbb --- /dev/null +++ b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js @@ -0,0 +1,71 @@ +LeafletWidget.methods.addLayerViewControl = function(viewSettings, homeSettings, fixLegends) { + const map = this; + + // Handle view settings for each layer on 'overlayadd' or 'baselayerchange' + map.on('overlayadd baselayerchange', function(e) { + let layerName = e.name; + let setting = viewSettings[layerName]; + + if (setting) { + if (setting.coords) { + if (setting.fly) { + map.flyTo([setting.coords[1], setting.coords[0]], setting.zoom, setting.options); + } else { + map.setView([setting.coords[1], setting.coords[0]], setting.zoom, setting.options); + } + } else if (setting.bounds) { + let bounds = [[setting.bounds[1], setting.bounds[0]], [setting.bounds[3], setting.bounds[2]]]; + if (setting.fly) { + map.flyToBounds(bounds, setting.options); + } else { + map.fitBounds(bounds, setting.options); + } + } + } + }); + + // Handle home buttons after the map has rendered + if (homeSettings) { + setTimeout(function() { + for (let layer in homeSettings) { + let homeButtonOptions = homeSettings[layer]; + let homeButton = document.createElement('span'); + homeButton.innerHTML = homeButtonOptions.text || '🏠'; + homeButton.style.cursor = homeButtonOptions.cursor || 'pointer'; + homeButton.className = homeButtonOptions.class || 'leaflet-home-btn'; + homeButton.dataset.layer = layer; + console.log("homeButtonOptions.styles"); console.log(homeButtonOptions.styles) + homeButton.style.cssText += homeButtonOptions.styles || 'float: inline-end;'; + + // Find the corresponding label for the layer + let labels = document.querySelectorAll('.leaflet-control-layers label'); + labels.forEach(function(label) { + if (label.textContent.trim() === layer) { + label.querySelector('div').appendChild(homeButton); + } + }); + homeButton.addEventListener('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + let layerName = this.dataset.layer; + let setting = viewSettings[layerName]; + if (setting && setting.coords) { + if (setting.fly) { + map.flyTo([setting.coords[1], setting.coords[0]], setting.zoom, setting.options); + } else { + map.setView([setting.coords[1], setting.coords[0]], setting.zoom, setting.options); + } + } else if (setting && setting.bounds) { + let bounds = [[setting.bounds[1], setting.bounds[0]], [setting.bounds[3], setting.bounds[2]]]; + if (setting.fly) { + map.flyToBounds(bounds, setting.options); + } else { + map.fitBounds(bounds, setting.options); + } + } + }); + } + }, 100); + } + +}; \ No newline at end of file diff --git a/inst/test_layerviewcontrol.R b/inst/test_layerviewcontrol.R new file mode 100644 index 0000000..6bfe5a7 --- /dev/null +++ b/inst/test_layerviewcontrol.R @@ -0,0 +1,129 @@ +library(sf) +library(shiny) +library(leaflet) +library(leafem) + +# Example data ########## +breweries91 <- st_as_sf(breweries91) +lines <- st_as_sf(atlStorms2005) +polys <- st_as_sf(leaflet::gadmCHE) +overlay1 <- "Overlay with Legend (orange)" +overlay2 <- "Overlay with Legend (blue)" + +n = 300 +df1 = data.frame(id = 1:n, + x = rnorm(n, 20, 3), + y = rnorm(n, -49, 1.8)) +pts = st_as_sf(df1, coords = c("x", "y"), crs = 4326) +dfnew <- local({ + n <- 300; x <- rnorm(n, mean = 30); y <- rnorm(n, 50) + z <- sqrt(x ^ 2 + y ^ 2); z[sample(n, 10)] <- NA + data.frame(x, y, z) +}) +palnew <- colorNumeric("OrRd", dfnew$z) +palnew2 <- colorNumeric("Blues", dfnew$z) + +# View settings: Each entry is a list with 'coords', 'zoom', and optional 'options' (e.g., padding) ########## +view_settings <- list( + "Base_tiles1" = list( + coords = c(20, 50) + , zoom = 3 + ), + "Base_tiles2" = list( + coords = c(-110, 50) + , zoom = 5 + ), + "breweries91" = list( + coords = as.numeric(st_coordinates(st_centroid(st_union(breweries91)))) + , zoom = 8 + , options = NULL + ), + "atlStorms2005" = list( + coords = as.numeric(st_bbox(lines)) + # , options = list(padding = c(10, 10), maxZoom = 6) + ), + "gadmCHE" = list( + coords = as.numeric(st_bbox(polys)) + , options = list(padding = c(10, 10)) + , fly = TRUE + ), + "random_points" = list( + coords = as.numeric(st_coordinates(st_centroid(st_union(pts)))) + , zoom = 7 + , fly = TRUE + ), + overlay1 = list( + coords = c(mean(dfnew$x), mean(dfnew$y)) + , zoom = 7 + ) , + overlay2 = list( + coords = c(mean(dfnew$x), mean(dfnew$y)) + , zoom = 7 + ) +) +names(view_settings)[names(view_settings)=="overlay1"] <- overlay1 +names(view_settings)[names(view_settings)=="overlay2"] <- overlay2 + +# Create leaflet map and apply the layer control function ######### +ui <- fluidPage( + tags$head(tags$style(" + .homebtn, .leaflet-home-btn { + float: inline-end; + } + .home-btn-layer3 { + background-color: gray; + padding; 4px + } + .home-btn-layer3 { + background-image: url(https://png.pngtree.com/png-clipart/20190904/original/pngtree-zoom-in-icon-png-image_4490537.jpg); + content: ''; + color: transparent; + width: 22px; + height: 22px; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + } + ")), + leafletOutput("map") +) + +server <- function(input, output, session) { + output$map <- renderLeaflet({ + leaflet() %>% + ## Baselayer + addTiles(group = "Base_tiles1") %>% + addProviderTiles("CartoDB", group = "Base_tiles2") %>% + + ## Overlays + addCircleMarkers(data = breweries91, group = "breweries91") %>% + addCircleMarkers(data = pts, group = "random_points", color = "red", weight = 1) %>% + addPolylines(data = lines, group = "atlStorms2005") %>% + addPolygons(data = polys, group = "gadmCHE") %>% + addCircleMarkers(data = dfnew, ~x, ~y, color = ~palnew(z), group = overlay1) %>% + addCircleMarkers(data = dfnew, ~x, ~y, color = ~palnew2(z), group = overlay2) %>% + addLegend(data = dfnew, pal = palnew, values = ~z, group = overlay1, position = "bottomleft") %>% + addLegend(data = dfnew, pal = palnew2, values = ~z, group = overlay2, position = "bottomleft") %>% + + ## LayerViewControl + addLayerViewControl(view_settings, home_btns = TRUE, + home_btn_options = list( + "Base_tiles1" = list(text = '🏡', cursor = 'ns-resize', class = 'homebtn home-btn-layer1'), + "Base_tiles2" = list(text = '❤️', cursor = 'pointer', class = 'homebtn home-btn-layer2'), + "random_points" = list(text = '🌎', cursor = 'all-scroll', class = 'homebtn home-btn-layer3'), + "Overlay with Legend (orange)" = list(text = '🚊', cursor = 'all-scroll', class = 'homebtn home-btn-layer3'), + "Overlay with Legend (blue)" = list(text = '🚊', cursor = 'all-scroll', class = 'homebtn home-btn-layer3') + )) %>% + + ## LayersControl + addLayersControl( + baseGroups = c("Base_tiles1", "Base_tiles2" + ), + overlayGroups = c("breweries91", "random_points", + overlay1, overlay2, + "atlStorms2005", "gadmCHE"), + options = layersControlOptions(collapsed = FALSE, autoZIndex = TRUE) + ) + }) +} +shinyApp(ui, server) diff --git a/man/addLayerViewControl.Rd b/man/addLayerViewControl.Rd new file mode 100644 index 0000000..c5e2156 --- /dev/null +++ b/man/addLayerViewControl.Rd @@ -0,0 +1,114 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/layerviewcontrol.R +\name{addLayerViewControl} +\alias{addLayerViewControl} +\title{Add Custom View Controls to Leaflet Map} +\usage{ +addLayerViewControl( + map, + view_settings, + home_btns = FALSE, + home_btn_options = list() +) +} +\arguments{ +\item{map}{A `leaflet` or `mapview` object to which view controls will be added.} + +\item{view_settings}{A list specifying the view settings for each layer. Each list element should contain +either: +\itemize{ + \item \code{coords}: A vector of length 2 (latitude, longitude) for setting the view, or length 4 + (bounding box: lat1, lng1, lat2, lng2) for fitting the bounds. + \item \code{zoom}: The zoom level (used for `setView`). + \item \code{fly} (optional): A logical indicating whether to use `flyTo` or `flyToBounds` instead of `setView` or `fitBounds`. + \item \code{options} (optional): Additional options to pass to `setView`, `fitBounds`, or `flyTo`. +}} + +\item{home_btns}{Logical. If `TRUE`, adds a "home" button next to each layer name in the layer control. +Clicking the home button zooms the map to the view specified for that layer in \code{view_settings}.} + +\item{home_btn_options}{A list of options to customize the home button appearance and behavior. +Possible options include: +- `text`: The text or emoji to display on the button (default is '🏠'). +- `cursor`: CSS cursor style for the button (default is 'pointer'). +- `class`: CSS class name for the button (default is 'leaflet-home-btn').} +} +\value{ +A modified `leaflet` map object with view controls and home buttons. +} +\description{ +This function adds custom views per group and optional home buttons for zooming to specific layers +or bounding boxes in a `leaflet` map. +} +\details{ +This function generates JavaScript that listens for `overlayadd` or `baselayerchange` events +and automatically sets the view or zoom level according to the specified \code{view_settings}. +If `home_btns` is enabled, a home button is added next to each layer in the layer control. +When clicked, it zooms the map to the predefined view of that layer. +} +\examples{ +library(sf) +library(shiny) +library(leaflet) +library(leafem) + +# Example data ########## +breweries91 <- st_as_sf(breweries91) +lines <- st_as_sf(atlStorms2005) +polys <- st_as_sf(leaflet::gadmCHE) + +# View settings ########## +view_settings <- list( + "Base_tiles1" = list( + coords = c(20, 50) + , zoom = 3 + ), + "Base_tiles2" = list( + coords = c(-110, 50) + , zoom = 5 + ), + "breweries91" = list( + coords = as.numeric(st_coordinates(st_centroid(st_union(breweries91)))) + , zoom = 8 + ), + "atlStorms2005" = list( + coords = as.numeric(st_bbox(lines)) + , options = list(padding = c(110, 110)) + ), + "gadmCHE" = list( + coords = as.numeric(st_bbox(polys)) + , options = list(padding = c(2, 2)) + , fly = TRUE + ) +) + +leaflet() \%>\% + ## Baselayer + addTiles(group = "Base_tiles1") \%>\% + addProviderTiles("CartoDB", group = "Base_tiles2") \%>\% + + ## Overlays + addCircleMarkers(data = breweries91, group = "breweries91") \%>\% + addPolylines(data = lines, group = "atlStorms2005") \%>\% + addPolygons(data = polys, group = "gadmCHE") \%>\% + + ## LayerViewControl + addLayerViewControl( + view_settings, home_btns = TRUE, + home_btn_options = list( + "Base_tiles1" = list(text = '🏡', cursor = 'ns-resize', class = 'homebtn'), + "Base_tiles2" = list(text = '❤️', cursor = 'pointer'), + "atlStorms2005" = list(text = '🌎', cursor = 'all-scroll'), + "breweries91" = list(text = '🌎', styles = 'background-color: red'), + "gadmCHE" = list(text = '🌎', styles = 'float: none;') + ) + ) \%>\% + + ## LayersControl + addLayersControl( + baseGroups = c("Base_tiles1", "Base_tiles2"), + overlayGroups = c("breweries91", "atlStorms2005", "gadmCHE"), + options = layersControlOptions(collapsed = FALSE, autoZIndex = TRUE) + ) + +} From 1274b6288824e254eae5348a1f98925f6dff63ee Mon Sep 17 00:00:00 2001 From: Sebastian Gatscha Date: Mon, 9 Sep 2024 10:16:52 +0200 Subject: [PATCH 3/9] build docs, rm %||% --- R/layerviewcontrol.R | 4 +- inst/test_layerviewcontrol.R | 129 ----------------------------------- man/addLayerViewControl.Rd | 4 +- 3 files changed, 4 insertions(+), 133 deletions(-) delete mode 100644 inst/test_layerviewcontrol.R diff --git a/R/layerviewcontrol.R b/R/layerviewcontrol.R index a18806f..e315fa1 100644 --- a/R/layerviewcontrol.R +++ b/R/layerviewcontrol.R @@ -110,13 +110,13 @@ addLayerViewControl <- function(map, view_settings, home_btns = FALSE, home_btn_ view_data[[layer]] <- list( coords = setting$coords, zoom = setting$zoom, - fly = setting[["fly"]] %||% FALSE, + fly = ifelse(is.null(setting[["fly"]]), FALSE, setting[["fly"]]), options = setting$options ) } else if (length(setting$coords) == 4) { view_data[[layer]] <- list( bounds = setting$coords, - fly = setting[["fly"]] %||% FALSE, + fly = ifelse(is.null(setting[["fly"]]), FALSE, setting[["fly"]]), options = setting$options ) } diff --git a/inst/test_layerviewcontrol.R b/inst/test_layerviewcontrol.R deleted file mode 100644 index 6bfe5a7..0000000 --- a/inst/test_layerviewcontrol.R +++ /dev/null @@ -1,129 +0,0 @@ -library(sf) -library(shiny) -library(leaflet) -library(leafem) - -# Example data ########## -breweries91 <- st_as_sf(breweries91) -lines <- st_as_sf(atlStorms2005) -polys <- st_as_sf(leaflet::gadmCHE) -overlay1 <- "Overlay with Legend (orange)" -overlay2 <- "Overlay with Legend (blue)" - -n = 300 -df1 = data.frame(id = 1:n, - x = rnorm(n, 20, 3), - y = rnorm(n, -49, 1.8)) -pts = st_as_sf(df1, coords = c("x", "y"), crs = 4326) -dfnew <- local({ - n <- 300; x <- rnorm(n, mean = 30); y <- rnorm(n, 50) - z <- sqrt(x ^ 2 + y ^ 2); z[sample(n, 10)] <- NA - data.frame(x, y, z) -}) -palnew <- colorNumeric("OrRd", dfnew$z) -palnew2 <- colorNumeric("Blues", dfnew$z) - -# View settings: Each entry is a list with 'coords', 'zoom', and optional 'options' (e.g., padding) ########## -view_settings <- list( - "Base_tiles1" = list( - coords = c(20, 50) - , zoom = 3 - ), - "Base_tiles2" = list( - coords = c(-110, 50) - , zoom = 5 - ), - "breweries91" = list( - coords = as.numeric(st_coordinates(st_centroid(st_union(breweries91)))) - , zoom = 8 - , options = NULL - ), - "atlStorms2005" = list( - coords = as.numeric(st_bbox(lines)) - # , options = list(padding = c(10, 10), maxZoom = 6) - ), - "gadmCHE" = list( - coords = as.numeric(st_bbox(polys)) - , options = list(padding = c(10, 10)) - , fly = TRUE - ), - "random_points" = list( - coords = as.numeric(st_coordinates(st_centroid(st_union(pts)))) - , zoom = 7 - , fly = TRUE - ), - overlay1 = list( - coords = c(mean(dfnew$x), mean(dfnew$y)) - , zoom = 7 - ) , - overlay2 = list( - coords = c(mean(dfnew$x), mean(dfnew$y)) - , zoom = 7 - ) -) -names(view_settings)[names(view_settings)=="overlay1"] <- overlay1 -names(view_settings)[names(view_settings)=="overlay2"] <- overlay2 - -# Create leaflet map and apply the layer control function ######### -ui <- fluidPage( - tags$head(tags$style(" - .homebtn, .leaflet-home-btn { - float: inline-end; - } - .home-btn-layer3 { - background-color: gray; - padding; 4px - } - .home-btn-layer3 { - background-image: url(https://png.pngtree.com/png-clipart/20190904/original/pngtree-zoom-in-icon-png-image_4490537.jpg); - content: ''; - color: transparent; - width: 22px; - height: 22px; - background-size: cover; - background-position: center; - background-repeat: no-repeat; - } - ")), - leafletOutput("map") -) - -server <- function(input, output, session) { - output$map <- renderLeaflet({ - leaflet() %>% - ## Baselayer - addTiles(group = "Base_tiles1") %>% - addProviderTiles("CartoDB", group = "Base_tiles2") %>% - - ## Overlays - addCircleMarkers(data = breweries91, group = "breweries91") %>% - addCircleMarkers(data = pts, group = "random_points", color = "red", weight = 1) %>% - addPolylines(data = lines, group = "atlStorms2005") %>% - addPolygons(data = polys, group = "gadmCHE") %>% - addCircleMarkers(data = dfnew, ~x, ~y, color = ~palnew(z), group = overlay1) %>% - addCircleMarkers(data = dfnew, ~x, ~y, color = ~palnew2(z), group = overlay2) %>% - addLegend(data = dfnew, pal = palnew, values = ~z, group = overlay1, position = "bottomleft") %>% - addLegend(data = dfnew, pal = palnew2, values = ~z, group = overlay2, position = "bottomleft") %>% - - ## LayerViewControl - addLayerViewControl(view_settings, home_btns = TRUE, - home_btn_options = list( - "Base_tiles1" = list(text = '🏡', cursor = 'ns-resize', class = 'homebtn home-btn-layer1'), - "Base_tiles2" = list(text = '❤️', cursor = 'pointer', class = 'homebtn home-btn-layer2'), - "random_points" = list(text = '🌎', cursor = 'all-scroll', class = 'homebtn home-btn-layer3'), - "Overlay with Legend (orange)" = list(text = '🚊', cursor = 'all-scroll', class = 'homebtn home-btn-layer3'), - "Overlay with Legend (blue)" = list(text = '🚊', cursor = 'all-scroll', class = 'homebtn home-btn-layer3') - )) %>% - - ## LayersControl - addLayersControl( - baseGroups = c("Base_tiles1", "Base_tiles2" - ), - overlayGroups = c("breweries91", "random_points", - overlay1, overlay2, - "atlStorms2005", "gadmCHE"), - options = layersControlOptions(collapsed = FALSE, autoZIndex = TRUE) - ) - }) -} -shinyApp(ui, server) diff --git a/man/addLayerViewControl.Rd b/man/addLayerViewControl.Rd index c5e2156..4ed3da4 100644 --- a/man/addLayerViewControl.Rd +++ b/man/addLayerViewControl.Rd @@ -31,7 +31,8 @@ Clicking the home button zooms the map to the view specified for that layer in \ Possible options include: - `text`: The text or emoji to display on the button (default is '🏠'). - `cursor`: CSS cursor style for the button (default is 'pointer'). -- `class`: CSS class name for the button (default is 'leaflet-home-btn').} +- `class`: CSS class name for the button (default is 'leaflet-home-btn'). +- `styles`: Semicolon separated CSS-string (default is 'float: inline-end;').} } \value{ A modified `leaflet` map object with view controls and home buttons. @@ -48,7 +49,6 @@ When clicked, it zooms the map to the predefined view of that layer. } \examples{ library(sf) -library(shiny) library(leaflet) library(leafem) From 29dafd8eb4a9917afd434fed3e99249ca576baf1 Mon Sep 17 00:00:00 2001 From: Sebastian Gatscha Date: Mon, 9 Sep 2024 11:08:16 +0200 Subject: [PATCH 4/9] new arg setviewonselect --- R/layerviewcontrol.R | 7 ++- .../lib/layerviewcontrol/layerviewcontrol.js | 53 +++++++------------ man/addLayerViewControl.Rd | 5 +- 3 files changed, 28 insertions(+), 37 deletions(-) diff --git a/R/layerviewcontrol.R b/R/layerviewcontrol.R index e315fa1..8d5a916 100644 --- a/R/layerviewcontrol.R +++ b/R/layerviewcontrol.R @@ -15,6 +15,7 @@ #' } #' @param home_btns Logical. If `TRUE`, adds a "home" button next to each layer name in the layer control. #' Clicking the home button zooms the map to the view specified for that layer in \code{view_settings}. +#' @param setviewonselect Logical. If `TRUE` (default) sets the view when the layer is selected. #' @param home_btn_options A list of options to customize the home button appearance and behavior. #' Possible options include: #' - `text`: The text or emoji to display on the button (default is '🏠'). @@ -95,7 +96,8 @@ #' ) #' #' @export -addLayerViewControl <- function(map, view_settings, home_btns = FALSE, home_btn_options = list()) { +addLayerViewControl <- function(map, view_settings, home_btns = FALSE, + home_btn_options = list(), setviewonselect = TRUE) { # Initialize data structures for view settings and home buttons view_data <- list() @@ -136,7 +138,8 @@ addLayerViewControl <- function(map, view_settings, home_btns = FALSE, home_btn_ NULL, 'addLayerViewControl', view_data, - home_data + home_data, + setviewonselect ) } diff --git a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js index 1057dbb..d65e710 100644 --- a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js +++ b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js @@ -1,26 +1,12 @@ -LeafletWidget.methods.addLayerViewControl = function(viewSettings, homeSettings, fixLegends) { +LeafletWidget.methods.addLayerViewControl = function(viewSettings, homeSettings, setviewonselect) { const map = this; // Handle view settings for each layer on 'overlayadd' or 'baselayerchange' map.on('overlayadd baselayerchange', function(e) { let layerName = e.name; let setting = viewSettings[layerName]; - - if (setting) { - if (setting.coords) { - if (setting.fly) { - map.flyTo([setting.coords[1], setting.coords[0]], setting.zoom, setting.options); - } else { - map.setView([setting.coords[1], setting.coords[0]], setting.zoom, setting.options); - } - } else if (setting.bounds) { - let bounds = [[setting.bounds[1], setting.bounds[0]], [setting.bounds[3], setting.bounds[2]]]; - if (setting.fly) { - map.flyToBounds(bounds, setting.options); - } else { - map.fitBounds(bounds, setting.options); - } - } + if (setting && setviewonselect) { + handleView(map, setting); } }); @@ -32,11 +18,9 @@ LeafletWidget.methods.addLayerViewControl = function(viewSettings, homeSettings, let homeButton = document.createElement('span'); homeButton.innerHTML = homeButtonOptions.text || '🏠'; homeButton.style.cursor = homeButtonOptions.cursor || 'pointer'; + homeButton.style.cssText += homeButtonOptions.styles || 'float: inline-end;'; homeButton.className = homeButtonOptions.class || 'leaflet-home-btn'; homeButton.dataset.layer = layer; - console.log("homeButtonOptions.styles"); console.log(homeButtonOptions.styles) - homeButton.style.cssText += homeButtonOptions.styles || 'float: inline-end;'; - // Find the corresponding label for the layer let labels = document.querySelectorAll('.leaflet-control-layers label'); labels.forEach(function(label) { @@ -49,23 +33,24 @@ LeafletWidget.methods.addLayerViewControl = function(viewSettings, homeSettings, event.stopPropagation(); let layerName = this.dataset.layer; let setting = viewSettings[layerName]; - if (setting && setting.coords) { - if (setting.fly) { - map.flyTo([setting.coords[1], setting.coords[0]], setting.zoom, setting.options); - } else { - map.setView([setting.coords[1], setting.coords[0]], setting.zoom, setting.options); - } - } else if (setting && setting.bounds) { - let bounds = [[setting.bounds[1], setting.bounds[0]], [setting.bounds[3], setting.bounds[2]]]; - if (setting.fly) { - map.flyToBounds(bounds, setting.options); - } else { - map.fitBounds(bounds, setting.options); - } + if (setting) { + handleView(map, setting) } }); } }, 100); } -}; \ No newline at end of file +}; + +// Helper function to handle setting view or bounds +function handleView(map, setting) { + if (setting.coords) { + const method = setting.fly ? 'flyTo' : 'setView'; + map[method]([setting.coords[1], setting.coords[0]], setting.zoom, setting.options); + } else if (setting.bounds) { + const method = setting.fly ? 'flyToBounds' : 'fitBounds'; + const bounds = [[setting.bounds[1], setting.bounds[0]], [setting.bounds[3], setting.bounds[2]]]; + map[method](bounds, setting.options); + } +} \ No newline at end of file diff --git a/man/addLayerViewControl.Rd b/man/addLayerViewControl.Rd index 4ed3da4..ed30e0a 100644 --- a/man/addLayerViewControl.Rd +++ b/man/addLayerViewControl.Rd @@ -8,7 +8,8 @@ addLayerViewControl( map, view_settings, home_btns = FALSE, - home_btn_options = list() + home_btn_options = list(), + setviewonselect = TRUE ) } \arguments{ @@ -33,6 +34,8 @@ Possible options include: - `cursor`: CSS cursor style for the button (default is 'pointer'). - `class`: CSS class name for the button (default is 'leaflet-home-btn'). - `styles`: Semicolon separated CSS-string (default is 'float: inline-end;').} + +\item{setviewonselect}{Logical. If `TRUE` (default) sets the view when the layer is selected.} } \value{ A modified `leaflet` map object with view controls and home buttons. From 5d9b4b4eec03c9b2aad48041ad128c63b4fcb064 Mon Sep 17 00:00:00 2001 From: Sebastian Gatscha Date: Tue, 10 Sep 2024 00:03:02 +0200 Subject: [PATCH 5/9] feat: opacityslider & legend-inhale --- .Rbuildignore | 1 + .gitignore | 1 + R/layerviewcontrol.R | 84 ++++++++---- inst/.gitignore | 1 + .../lib/layerviewcontrol/layerviewcontrol.css | 6 + .../lib/layerviewcontrol/layerviewcontrol.js | 121 ++++++++++++++---- man/addLayerViewControl.Rd | 77 ++++++++--- 7 files changed, 222 insertions(+), 69 deletions(-) create mode 100644 inst/.gitignore create mode 100644 inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.css diff --git a/.Rbuildignore b/.Rbuildignore index 947ace5..debbe42 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -7,3 +7,4 @@ ^_pkgdown\.yml$ ^docs$ ^pkgdown$ +^private$ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 234f028..366c77c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .RData .Ruserdata docs +private diff --git a/R/layerviewcontrol.R b/R/layerviewcontrol.R index 8d5a916..b64e3f0 100644 --- a/R/layerviewcontrol.R +++ b/R/layerviewcontrol.R @@ -1,9 +1,10 @@ -#' Add Custom View Controls to Leaflet Map +#' Extend Layers Control in Leaflet Map #' -#' This function adds custom views per group and optional home buttons for zooming to specific layers -#' or bounding boxes in a `leaflet` map. +#' This function extends an existing layers control in a `leaflet` map by adding custom views, home buttons, +#' opacity controls, and legends. It enhances the functionality of a layers control created with `leaflet` +#' or `leaflet.extras`. #' -#' @param map A `leaflet` or `mapview` object to which view controls will be added. +#' @param map A `leaflet` or `mapview` object to which the extended layers control will be added. #' @param view_settings A list specifying the view settings for each layer. Each list element should contain #' either: #' \itemize{ @@ -23,13 +24,28 @@ #' - `class`: CSS class name for the button (default is 'leaflet-home-btn'). #' - `styles`: Semicolon separated CSS-string (default is 'float: inline-end;'). #' -#' @return A modified `leaflet` map object with view controls and home buttons. +#' @param opacityControl A list specifying the opacity control settings for each layer. Each list element should contain: +#' \itemize{ +#' \item \code{min}: Minimum opacity value (default is 0). +#' \item \code{max}: Maximum opacity value (default is 1). +#' \item \code{step}: Step size for the opacity slider (default is 0.1). +#' \item \code{default}: Default opacity value (default is 1). +#' \item \code{width}: Width of the opacity slider (default is '100%'). +#' \item \code{class}: CSS class name for the slider (default is 'leaflet-opacity-slider'). +#' } +#' +#' @param includelegends Logical. If `TRUE` (default), appends legends to the layer control. Legends are matched +#' to layers by their group name. The legends need to be added with corresponding layer IDs. +#' +#' @return A modified `leaflet` map object with extended layers control including view controls, home buttons, opacity controls, and legends. #' #' @details #' This function generates JavaScript that listens for `overlayadd` or `baselayerchange` events #' and automatically sets the view or zoom level according to the specified \code{view_settings}. #' If `home_btns` is enabled, a home button is added next to each layer in the layer control. #' When clicked, it zooms the map to the predefined view of that layer. +#' The opacity control slider allows users to adjust the opacity of layers. The legend will be appended +#' to the corresponding layer control, matched by the layer's group name. #' #' @examples #' library(sf) @@ -44,28 +60,45 @@ #' # View settings ########## #' view_settings <- list( #' "Base_tiles1" = list( -#' coords = c(20, 50) -#' , zoom = 3 +#' coords = c(20, 50), +#' zoom = 3 #' ), #' "Base_tiles2" = list( -#' coords = c(-110, 50) -#' , zoom = 5 +#' coords = c(-110, 50), +#' zoom = 5 #' ), #' "breweries91" = list( -#' coords = as.numeric(st_coordinates(st_centroid(st_union(breweries91)))) -#' , zoom = 8 +#' coords = as.numeric(st_coordinates(st_centroid(st_union(breweries91)))), +#' zoom = 8 #' ), #' "atlStorms2005" = list( -#' coords = as.numeric(st_bbox(lines)) -#' , options = list(padding = c(110, 110)) +#' coords = as.numeric(st_bbox(lines)), +#' options = list(padding = c(110, 110)) #' ), #' "gadmCHE" = list( -#' coords = as.numeric(st_bbox(polys)) -#' , options = list(padding = c(2, 2)) -#' , fly = TRUE +#' coords = as.numeric(st_bbox(polys)), +#' options = list(padding = c(2, 2)), +#' fly = TRUE +#' ) +#' ) +#' +#' # Opacity control settings ########## +#' opacityControl <- list( +#' "breweries91" = list( +#' min = 0, +#' max = 1, +#' step = 0.1, +#' default = 1, +#' width = '100%', +#' class = 'opacity-slider' #' ) #' ) #' +#' # Legends ########## +#' legends <- list( +#' "breweries91" = "
Legend for breweries
" +#' ) +#' #' leaflet() %>% #' ## Baselayer #' addTiles(group = "Base_tiles1") %>% @@ -76,8 +109,8 @@ #' addPolylines(data = lines, group = "atlStorms2005") %>% #' addPolygons(data = polys, group = "gadmCHE") %>% #' -#' ## LayerViewControl -#' addLayerViewControl( +#' ## Extend Layers Control +#' extendLayersControl( #' view_settings, home_btns = TRUE, #' home_btn_options = list( #' "Base_tiles1" = list(text = '🏡', cursor = 'ns-resize', class = 'homebtn'), @@ -85,7 +118,9 @@ #' "atlStorms2005" = list(text = '🌎', cursor = 'all-scroll'), #' "breweries91" = list(text = '🌎', styles = 'background-color: red'), #' "gadmCHE" = list(text = '🌎', styles = 'float: none;') -#' ) +#' ), +#' opacityControl = opacityControl, +#' includelegends = TRUE #' ) %>% #' #' ## LayersControl @@ -97,7 +132,9 @@ #' #' @export addLayerViewControl <- function(map, view_settings, home_btns = FALSE, - home_btn_options = list(), setviewonselect = TRUE) { + home_btn_options = list(), setviewonselect = TRUE, + opacityControl = list(), + includelegends = TRUE) { # Initialize data structures for view settings and home buttons view_data <- list() @@ -139,7 +176,9 @@ addLayerViewControl <- function(map, view_settings, home_btns = FALSE, 'addLayerViewControl', view_data, home_data, - setviewonselect + setviewonselect, + opacityControl, + includelegends ) } @@ -150,6 +189,7 @@ layerViewControlDependencies <- function() { "layerViewControl", '0.0.1', system.file("htmlwidgets/lib/layerviewcontrol", package = "leafem"), - script = c("layerviewcontrol.js") + script = "layerviewcontrol.js", + stylesheet = "layerviewcontrol.css" )) } diff --git a/inst/.gitignore b/inst/.gitignore new file mode 100644 index 0000000..038d718 --- /dev/null +++ b/inst/.gitignore @@ -0,0 +1 @@ +testing diff --git a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.css b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.css new file mode 100644 index 0000000..678d6ce --- /dev/null +++ b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.css @@ -0,0 +1,6 @@ +.leaflet-control-layers-overlays .legend { + width: 55% !important; + margin: 0px 68px !important; + padding: 0 !important; + box-shadow: unset !important; +} \ No newline at end of file diff --git a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js index d65e710..5780d33 100644 --- a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js +++ b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js @@ -1,46 +1,113 @@ -LeafletWidget.methods.addLayerViewControl = function(viewSettings, homeSettings, setviewonselect) { +LeafletWidget.methods.addLayerViewControl = function(viewSettings, homeSettings, setviewonselect, opacityControl, includelegends) { const map = this; + // Utility function to find the correct label element + function findLabel(layerName) { + return Array.from(document.querySelectorAll('.leaflet-control-layers label:not([class])')).find(label => + $(label).find("span")[0].textContent.trim() === layerName + ); + } + + // Utility function to append a child to the label element + function appendToLabel(layer, childElement) { + const label = findLabel(layer); + if (label) { + let labelDiv = label.querySelector('div') || document.createElement('div'); + labelDiv.appendChild(childElement); + label.appendChild(labelDiv); + } + } + + // Helper function to check if the layer is active + function isLayerActive(layerName) { + return !!map.layerManager._groupContainers[layerName]; + } + // Handle view settings for each layer on 'overlayadd' or 'baselayerchange' map.on('overlayadd baselayerchange', function(e) { - let layerName = e.name; - let setting = viewSettings[layerName]; - if (setting && setviewonselect) { - handleView(map, setting); - } + let setting = viewSettings[e.name]; + if (setting && setviewonselect) handleView(map, setting); }); - // Handle home buttons after the map has rendered + // Handle home buttons if (homeSettings) { - setTimeout(function() { - for (let layer in homeSettings) { - let homeButtonOptions = homeSettings[layer]; + setTimeout(() => { + Object.entries(homeSettings).forEach(([layer, options]) => { let homeButton = document.createElement('span'); - homeButton.innerHTML = homeButtonOptions.text || '🏠'; - homeButton.style.cursor = homeButtonOptions.cursor || 'pointer'; - homeButton.style.cssText += homeButtonOptions.styles || 'float: inline-end;'; - homeButton.className = homeButtonOptions.class || 'leaflet-home-btn'; - homeButton.dataset.layer = layer; - // Find the corresponding label for the layer - let labels = document.querySelectorAll('.leaflet-control-layers label'); - labels.forEach(function(label) { - if (label.textContent.trim() === layer) { - label.querySelector('div').appendChild(homeButton); - } + Object.assign(homeButton.style, { + cursor: options.cursor || 'pointer', + cssText: options.styles || 'float: inline-end;' }); + homeButton.className = options.class || 'leaflet-home-btn'; + homeButton.dataset.layer = layer; + homeButton.innerHTML = options.text || '🏠'; + + appendToLabel(layer, homeButton); + homeButton.addEventListener('click', function(event) { event.preventDefault(); event.stopPropagation(); - let layerName = this.dataset.layer; - let setting = viewSettings[layerName]; - if (setting) { - handleView(map, setting) - } + let setting = viewSettings[this.dataset.layer]; + if (setting) handleView(map, setting); }); - } + }); }, 100); } + // Handle opacity control + if (opacityControl) { + setTimeout(() => { + Object.entries(opacityControl).forEach(([layer, options]) => { + let sliderContainer = document.createElement('div'); + let slider = document.createElement('input'); + Object.assign(slider, { + type: 'range', + min: options.min || 0, + max: options.max || 1, + step: options.step || 0.1, + value: options.default || 1, + style: `width: ${options.width || '100%'};` + }); + slider.className = options.class || 'leaflet-opacity-slider'; + sliderContainer.style.display = 'none'; + sliderContainer.appendChild(slider); + + appendToLabel(layer, sliderContainer); + + slider.addEventListener('input', function() { + let opacityVal = parseFloat(this.value); + Object.values(map.layerManager._byGroup[layer]).forEach(layer => { + if (layer.setOpacity) { + layer.setOpacity(opacityVal); + } else if (layer.setStyle) { + layer.setStyle({ opacity: opacityVal, fillOpacity: opacityVal }); + } + }); + }); + + // Show/hide the slider based on layer visibility + function updateSliderVisibility() { + sliderContainer.style.display = isLayerActive(layer) ? 'block' : 'none'; + } + map.on('overlayadd overlayremove baselayerchange', updateSliderVisibility); + updateSliderVisibility(); + }); + }, 100); + } + + // Handle legends + if (includelegends) { + function moveLegends() { + Object.entries(map.controls._controlsById).forEach(([controlId, control]) => { + let legendContainer = control._container; + if (legendContainer) { + appendToLabel(controlId, legendContainer) + } + }); + } + setTimeout(moveLegends, 20); + map.on('overlayadd baselayerchange', () => setTimeout(moveLegends, 20)); + } }; // Helper function to handle setting view or bounds diff --git a/man/addLayerViewControl.Rd b/man/addLayerViewControl.Rd index ed30e0a..bd8d6b2 100644 --- a/man/addLayerViewControl.Rd +++ b/man/addLayerViewControl.Rd @@ -2,18 +2,20 @@ % Please edit documentation in R/layerviewcontrol.R \name{addLayerViewControl} \alias{addLayerViewControl} -\title{Add Custom View Controls to Leaflet Map} +\title{Extend Layers Control in Leaflet Map} \usage{ addLayerViewControl( map, view_settings, home_btns = FALSE, home_btn_options = list(), - setviewonselect = TRUE + setviewonselect = TRUE, + opacityControl = list(), + includelegends = TRUE ) } \arguments{ -\item{map}{A `leaflet` or `mapview` object to which view controls will be added.} +\item{map}{A `leaflet` or `mapview` object to which the extended layers control will be added.} \item{view_settings}{A list specifying the view settings for each layer. Each list element should contain either: @@ -36,19 +38,35 @@ Possible options include: - `styles`: Semicolon separated CSS-string (default is 'float: inline-end;').} \item{setviewonselect}{Logical. If `TRUE` (default) sets the view when the layer is selected.} + +\item{opacityControl}{A list specifying the opacity control settings for each layer. Each list element should contain: +\itemize{ + \item \code{min}: Minimum opacity value (default is 0). + \item \code{max}: Maximum opacity value (default is 1). + \item \code{step}: Step size for the opacity slider (default is 0.1). + \item \code{default}: Default opacity value (default is 1). + \item \code{width}: Width of the opacity slider (default is '100%'). + \item \code{class}: CSS class name for the slider (default is 'leaflet-opacity-slider'). +}} + +\item{includelegends}{Logical. If `TRUE` (default), appends legends to the layer control. Legends are matched +to layers by their group name. The legends need to be added with corresponding layer IDs.} } \value{ -A modified `leaflet` map object with view controls and home buttons. +A modified `leaflet` map object with extended layers control including view controls, home buttons, opacity controls, and legends. } \description{ -This function adds custom views per group and optional home buttons for zooming to specific layers -or bounding boxes in a `leaflet` map. +This function extends an existing layers control in a `leaflet` map by adding custom views, home buttons, +opacity controls, and legends. It enhances the functionality of a layers control created with `leaflet` +or `leaflet.extras`. } \details{ This function generates JavaScript that listens for `overlayadd` or `baselayerchange` events and automatically sets the view or zoom level according to the specified \code{view_settings}. If `home_btns` is enabled, a home button is added next to each layer in the layer control. When clicked, it zooms the map to the predefined view of that layer. +The opacity control slider allows users to adjust the opacity of layers. The legend will be appended +to the corresponding layer control, matched by the layer's group name. } \examples{ library(sf) @@ -63,28 +81,45 @@ polys <- st_as_sf(leaflet::gadmCHE) # View settings ########## view_settings <- list( "Base_tiles1" = list( - coords = c(20, 50) - , zoom = 3 + coords = c(20, 50), + zoom = 3 ), "Base_tiles2" = list( - coords = c(-110, 50) - , zoom = 5 + coords = c(-110, 50), + zoom = 5 ), "breweries91" = list( - coords = as.numeric(st_coordinates(st_centroid(st_union(breweries91)))) - , zoom = 8 + coords = as.numeric(st_coordinates(st_centroid(st_union(breweries91)))), + zoom = 8 ), "atlStorms2005" = list( - coords = as.numeric(st_bbox(lines)) - , options = list(padding = c(110, 110)) + coords = as.numeric(st_bbox(lines)), + options = list(padding = c(110, 110)) ), "gadmCHE" = list( - coords = as.numeric(st_bbox(polys)) - , options = list(padding = c(2, 2)) - , fly = TRUE + coords = as.numeric(st_bbox(polys)), + options = list(padding = c(2, 2)), + fly = TRUE + ) +) + +# Opacity control settings ########## +opacityControl <- list( + "breweries91" = list( + min = 0, + max = 1, + step = 0.1, + default = 1, + width = '100\%', + class = 'opacity-slider' ) ) +# Legends ########## +legends <- list( + "breweries91" = "
Legend for breweries
" +) + leaflet() \%>\% ## Baselayer addTiles(group = "Base_tiles1") \%>\% @@ -95,8 +130,8 @@ leaflet() \%>\% addPolylines(data = lines, group = "atlStorms2005") \%>\% addPolygons(data = polys, group = "gadmCHE") \%>\% - ## LayerViewControl - addLayerViewControl( + ## Extend Layers Control + extendLayersControl( view_settings, home_btns = TRUE, home_btn_options = list( "Base_tiles1" = list(text = '🏡', cursor = 'ns-resize', class = 'homebtn'), @@ -104,7 +139,9 @@ leaflet() \%>\% "atlStorms2005" = list(text = '🌎', cursor = 'all-scroll'), "breweries91" = list(text = '🌎', styles = 'background-color: red'), "gadmCHE" = list(text = '🌎', styles = 'float: none;') - ) + ), + opacityControl = opacityControl, + includelegends = TRUE ) \%>\% ## LayersControl From 288460359f9000fdf7695b6d8e4ae5c94936ead6 Mon Sep 17 00:00:00 2001 From: Sebastian Gatscha Date: Tue, 10 Sep 2024 10:47:04 +0200 Subject: [PATCH 6/9] rename extendLayerControl, fix js --- NAMESPACE | 2 +- R/layerviewcontrol.R | 6 +- .../lib/layerviewcontrol/layerviewcontrol.js | 76 +++++++++++-------- ...erViewControl.Rd => extendLayerControl.Rd} | 8 +- 4 files changed, 51 insertions(+), 41 deletions(-) rename man/{addLayerViewControl.Rd => extendLayerControl.Rd} (98%) diff --git a/NAMESPACE b/NAMESPACE index 10fe72c..c5c3fa8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -21,7 +21,6 @@ export(addGeoRaster) export(addGeotiff) export(addHomeButton) export(addImageQuery) -export(addLayerViewControl) export(addLocalFile) export(addLogo) export(addMouseCoordinates) @@ -36,6 +35,7 @@ export(addStaticLabels) export(addTileFolder) export(clip2sfc) export(colorOptions) +export(extendLayerControl) export(garnishMap) export(hideLogo) export(imagequeryOptions) diff --git a/R/layerviewcontrol.R b/R/layerviewcontrol.R index b64e3f0..d6e4089 100644 --- a/R/layerviewcontrol.R +++ b/R/layerviewcontrol.R @@ -110,7 +110,7 @@ #' addPolygons(data = polys, group = "gadmCHE") %>% #' #' ## Extend Layers Control -#' extendLayersControl( +#' extendLayerControl( #' view_settings, home_btns = TRUE, #' home_btn_options = list( #' "Base_tiles1" = list(text = '🏡', cursor = 'ns-resize', class = 'homebtn'), @@ -131,7 +131,7 @@ #' ) #' #' @export -addLayerViewControl <- function(map, view_settings, home_btns = FALSE, +extendLayerControl <- function(map, view_settings, home_btns = FALSE, home_btn_options = list(), setviewonselect = TRUE, opacityControl = list(), includelegends = TRUE) { @@ -173,7 +173,7 @@ addLayerViewControl <- function(map, view_settings, home_btns = FALSE, leaflet::invokeMethod( map, NULL, - 'addLayerViewControl', + 'extendLayerControl', view_data, home_data, setviewonselect, diff --git a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js index 5780d33..1f3c221 100644 --- a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js +++ b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js @@ -1,28 +1,6 @@ -LeafletWidget.methods.addLayerViewControl = function(viewSettings, homeSettings, setviewonselect, opacityControl, includelegends) { +LeafletWidget.methods.extendLayerControl = function(viewSettings, homeSettings, setviewonselect, opacityControl, includelegends) { const map = this; - // Utility function to find the correct label element - function findLabel(layerName) { - return Array.from(document.querySelectorAll('.leaflet-control-layers label:not([class])')).find(label => - $(label).find("span")[0].textContent.trim() === layerName - ); - } - - // Utility function to append a child to the label element - function appendToLabel(layer, childElement) { - const label = findLabel(layer); - if (label) { - let labelDiv = label.querySelector('div') || document.createElement('div'); - labelDiv.appendChild(childElement); - label.appendChild(labelDiv); - } - } - - // Helper function to check if the layer is active - function isLayerActive(layerName) { - return !!map.layerManager._groupContainers[layerName]; - } - // Handle view settings for each layer on 'overlayadd' or 'baselayerchange' map.on('overlayadd baselayerchange', function(e) { let setting = viewSettings[e.name]; @@ -51,11 +29,18 @@ LeafletWidget.methods.addLayerViewControl = function(viewSettings, homeSettings, if (setting) handleView(map, setting); }); }); - }, 100); + }, 20); } // Handle opacity control if (opacityControl) { + // Helper function to check if the layer is active + function isLayerActive(layerName) { + return Object.keys(map.layerManager._groupContainers).some(function(layer) { + return layer === layerName; + }); + } + setTimeout(() => { Object.entries(opacityControl).forEach(([layer, options]) => { let sliderContainer = document.createElement('div'); @@ -85,14 +70,20 @@ LeafletWidget.methods.addLayerViewControl = function(viewSettings, homeSettings, }); }); - // Show/hide the slider based on layer visibility - function updateSliderVisibility() { - sliderContainer.style.display = isLayerActive(layer) ? 'block' : 'none'; + // Initialize slider visibility based on the current state of the layer + if (isLayerActive(layer)) { + sliderContainer.style.display = 'block'; } - map.on('overlayadd overlayremove baselayerchange', updateSliderVisibility); - updateSliderVisibility(); + + // Handle layer visibility + map.on('overlayadd overlayremove baselayerchange', function(e) { + if (e.name === layer) { + sliderContainer.style.display = (e.type === 'overlayadd' || e.type === 'baselayerchange') ? 'block' : 'none'; + } + }); + }); - }, 100); + }, 30); } // Handle legends @@ -105,12 +96,14 @@ LeafletWidget.methods.addLayerViewControl = function(viewSettings, homeSettings, } }); } - setTimeout(moveLegends, 20); + setTimeout(moveLegends, 40); map.on('overlayadd baselayerchange', () => setTimeout(moveLegends, 20)); } }; -// Helper function to handle setting view or bounds + + +// function to handle setting view or bounds function handleView(map, setting) { if (setting.coords) { const method = setting.fly ? 'flyTo' : 'setView'; @@ -120,4 +113,21 @@ function handleView(map, setting) { const bounds = [[setting.bounds[1], setting.bounds[0]], [setting.bounds[3], setting.bounds[2]]]; map[method](bounds, setting.options); } -} \ No newline at end of file +} + +// function to find the correct label element +function findLabel(layerName) { + return Array.from(document.querySelectorAll('.leaflet-control-layers label:not([class])')).find(label => + $(label).find("span")[0].textContent.trim() === layerName + ); +} + +// function to append a child to the label element +function appendToLabel(layer, childElement) { + const label = findLabel(layer); + if (label) { + let labelDiv = label.querySelector('div') || document.createElement('div'); + labelDiv.appendChild(childElement); + label.appendChild(labelDiv); + } +} diff --git a/man/addLayerViewControl.Rd b/man/extendLayerControl.Rd similarity index 98% rename from man/addLayerViewControl.Rd rename to man/extendLayerControl.Rd index bd8d6b2..17faca9 100644 --- a/man/addLayerViewControl.Rd +++ b/man/extendLayerControl.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/layerviewcontrol.R -\name{addLayerViewControl} -\alias{addLayerViewControl} +\name{extendLayerControl} +\alias{extendLayerControl} \title{Extend Layers Control in Leaflet Map} \usage{ -addLayerViewControl( +extendLayerControl( map, view_settings, home_btns = FALSE, @@ -131,7 +131,7 @@ leaflet() \%>\% addPolygons(data = polys, group = "gadmCHE") \%>\% ## Extend Layers Control - extendLayersControl( + extendLayerControl( view_settings, home_btns = TRUE, home_btn_options = list( "Base_tiles1" = list(text = '🏡', cursor = 'ns-resize', class = 'homebtn'), From fa696cf6880158d09f8f5b50f4e3415ed063fa32 Mon Sep 17 00:00:00 2001 From: Sebastian Gatscha Date: Tue, 10 Sep 2024 11:05:01 +0200 Subject: [PATCH 7/9] fix for grouped and fix css --- .../lib/layerviewcontrol/layerviewcontrol.css | 5 +++-- .../lib/layerviewcontrol/layerviewcontrol.js | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.css b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.css index 678d6ce..0f80562 100644 --- a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.css +++ b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.css @@ -1,6 +1,7 @@ .leaflet-control-layers-overlays .legend { - width: 55% !important; - margin: 0px 68px !important; + width: 100% !important; padding: 0 !important; box-shadow: unset !important; + margin: 0; + left: 13px; } \ No newline at end of file diff --git a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js index 1f3c221..49106ba 100644 --- a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js +++ b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js @@ -20,7 +20,7 @@ LeafletWidget.methods.extendLayerControl = function(viewSettings, homeSettings, homeButton.dataset.layer = layer; homeButton.innerHTML = options.text || '🏠'; - appendToLabel(layer, homeButton); + appendToLabelHome(layer, homeButton); homeButton.addEventListener('click', function(event) { event.preventDefault(); @@ -131,3 +131,16 @@ function appendToLabel(layer, childElement) { label.appendChild(labelDiv); } } + +// function to append a child to the label element +function appendToLabelHome(layer, childElement) { + const label = findLabel(layer); + if (label) { + let labelDiv = label.querySelector('div') + if (labelDiv) { + labelDiv.appendChild(childElement); + } else { + label.appendChild(childElement); + } + } +} From de9deb2036485b04d348290601578f1654bb9ae8 Mon Sep 17 00:00:00 2001 From: Sebastian Gatscha Date: Tue, 10 Sep 2024 11:05:40 +0200 Subject: [PATCH 8/9] add example --- inst/test_layerviewcontrol.R | 169 +++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 inst/test_layerviewcontrol.R diff --git a/inst/test_layerviewcontrol.R b/inst/test_layerviewcontrol.R new file mode 100644 index 0000000..545a28c --- /dev/null +++ b/inst/test_layerviewcontrol.R @@ -0,0 +1,169 @@ +library(sf) +library(shiny) +library(leaflet) +library(leaflet.extras) +library(leafem) +options("shiny.autoreload" = TRUE) + +# Example data ########## +breweries91 <- st_as_sf(breweries91) +lines <- st_as_sf(atlStorms2005) +polys <- st_as_sf(leaflet::gadmCHE) +overlay1 <- "Overlay with Legend (orange)" +overlay2 <- "Overlay with Legend (blue)" + +n = 300 +df1 = data.frame(id = 1:n, + x = rnorm(n, 20, 3), + y = rnorm(n, -49, 1.8)) +pts = st_as_sf(df1, coords = c("x", "y"), crs = 4326) +dfnew <- local({ + n <- 300; x <- rnorm(n, mean = 30); y <- rnorm(n, 50) + z <- sqrt(x ^ 2 + y ^ 2); z[sample(n, 10)] <- NA + data.frame(x, y, z) +}) +palnew <- colorNumeric("OrRd", dfnew$z) +palnew2 <- colorNumeric("Blues", dfnew$z) + +# View settings: Each entry is a list with 'coords', 'zoom', and optional 'options' (e.g., padding) ########## +view_settings <- list( + "Base_tiles1" = list( + coords = c(20, 50) + , zoom = 3 + ), + "Base_tiles2" = list( + coords = c(-110, 50) + , zoom = 5 + ), + "breweries91" = list( + coords = as.numeric(st_coordinates(st_centroid(st_union(breweries91)))) + , zoom = 8 + , options = NULL + ), + "atlStorms2005" = list( + coords = as.numeric(st_bbox(lines)) + # , options = list(padding = c(10, 10), maxZoom = 6) + ), + "gadmCHE" = list( + coords = as.numeric(st_bbox(polys)) + , options = list(padding = c(10, 10)) + , fly = TRUE + ), + "random_points" = list( + coords = as.numeric(st_coordinates(st_centroid(st_union(pts)))) + , zoom = 7 + , fly = TRUE + ), + overlay1 = list( + coords = c(mean(dfnew$x), mean(dfnew$y)) + , zoom = 7 + ) , + overlay2 = list( + coords = c(mean(dfnew$x), mean(dfnew$y)) + , zoom = 7 + ) +) +names(view_settings)[names(view_settings)=="overlay1"] <- overlay1 +names(view_settings)[names(view_settings)=="overlay2"] <- overlay2 + +# Create leaflet map and apply the layer control function ######### +ui <- fluidPage( + ## Custom CSS ########### + tags$head(tags$style(" + .home-btn-layer3 { + background-color: gray; + padding; 4px + } + .home-btn-layer3 { + background-image: url(https://png.pngtree.com/png-clipart/20190904/original/pngtree-zoom-in-icon-png-image_4490537.jpg); + content: ''; + color: transparent; + width: 22px; + height: 22px; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + } + ")), + ## Input + Map ########### + tags$div(style="display: inline-flex", + selectInput("layercontrol", "Layer Control", + choices = c("layercontrol", "groupedlayercontrol")), + shiny::checkboxInput("includelegends", "Inlcude Legends", value = TRUE), + shiny::checkboxInput("homebtns", "Home Buttons", value = TRUE), + shiny::checkboxInput("setviewonselect", "Set View on select", value = TRUE), + ), + leafletOutput("map", height = 800) +) + +server <- function(input, output, session) { + output$map <- renderLeaflet({ + m <- leaflet() %>% + ## Baselayer ########## + addTiles(group = "Base_tiles1") %>% + addProviderTiles("CartoDB", group = "Base_tiles2") %>% + + ## Overlays ########## + addCircleMarkers(data = breweries91, group = "breweries91") %>% + addCircleMarkers(data = pts, group = "random_points", color = "red", weight = 1) %>% + addPolylines(data = lines, group = "atlStorms2005") %>% + addPolygons(data = polys, group = "gadmCHE") %>% + addCircleMarkers(data = dfnew, ~x, ~y, color = ~palnew(z), group = overlay1) %>% + addCircleMarkers(data = dfnew, ~x, ~y, color = ~palnew2(z), group = overlay2) %>% + addLegend(data = dfnew, pal = palnew, layerId = overlay1, values = ~z, group = overlay1, position = "bottomleft") %>% + addLegend(data = dfnew, pal = palnew2, layerId = overlay2, values = ~z, group = overlay2, position = "bottomleft") %>% + + ## extendLayerControl ########## + extendLayerControl(view_settings + , includelegends = input$includelegends + , home_btns = input$homebtns + , setviewonselect = input$setviewonselect + , home_btn_options = list( + "Base_tiles1" = list(text = '🏡', cursor = 'ns-resize', class = 'homebtn home-btn-layer1'), + "Base_tiles2" = list(text = '❤️', cursor = 'pointer', class = 'homebtn home-btn-layer2'), + "random_points" = list(text = '🌎', cursor = 'all-scroll', class = 'homebtn home-btn-layer3'), + "Overlay with Legend (orange)" = list(text = '🚊', cursor = 'all-scroll', class = 'homebtn home-btn-layer3'), + "Overlay with Legend (blue)" = list(text = '🚊', cursor = 'all-scroll', class = 'homebtn home-btn-layer3') + ) + , opacityControl = list( + "random_points" = list(min= 0, max= 1, step= 0.01, default= 0.7, width= '140px'), + "Overlay with Legend (orange)" = list(min= 0.1, max= 0.8, step= 0.1, default= 1), + "Overlay with Legend (blue)" = list(default= 0.5) + ) + ) + + ## LayersControls ########## + if (input$layercontrol == "layercontrol") { + m %>% + addLayersControl( + baseGroups = c("Base_tiles1", "Base_tiles2" + ), + overlayGroups = c("breweries91", "random_points", + overlay1, overlay2, + "atlStorms2005", "gadmCHE"), + options = layersControlOptions(collapsed = FALSE, autoZIndex = TRUE) + ) + } else { + m %>% + addGroupedLayersControl( + baseGroups = c("Base_tiles1","Base_tiles2"), + overlayGroups = list( + "Group1" = c("breweries91","random_points", + overlay1, overlay2), + "Group2" = c("atlStorms2005", "gadmCHE")), + position = "topright", + options = groupedLayersControlOptions(groupCheckboxes = TRUE, + collapsed = FALSE, + groupsCollapsable = TRUE, + groupsExpandedClass = "glyphicon glyphicon-chevron-down", + groupsCollapsedClass = "glyphicon glyphicon-chevron-right", + sortLayers = FALSE, + sortGroups = FALSE, + sortBaseLayers = FALSE, + exclusiveGroups = "Group2") + ) + } + + }) +} +shinyApp(ui, server) From ce56b82c4e1ec0a75e0876d5802d63fbb7611791 Mon Sep 17 00:00:00 2001 From: Sebastian Gatscha Date: Tue, 10 Sep 2024 16:38:16 +0200 Subject: [PATCH 9/9] fix for leaflegend --- .../lib/layerviewcontrol/layerviewcontrol.css | 3 +- .../lib/layerviewcontrol/layerviewcontrol.js | 14 ++++++++ inst/test_layerviewcontrol.R | 35 +++++++++++++++---- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.css b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.css index 0f80562..3aec75f 100644 --- a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.css +++ b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.css @@ -4,4 +4,5 @@ box-shadow: unset !important; margin: 0; left: 13px; -} \ No newline at end of file +} + diff --git a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js index 49106ba..e7d615d 100644 --- a/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js +++ b/inst/htmlwidgets/lib/layerviewcontrol/layerviewcontrol.js @@ -95,6 +95,20 @@ LeafletWidget.methods.extendLayerControl = function(viewSettings, homeSettings, appendToLabel(controlId, legendContainer) } }); + + // Fix for leaflegend package + let elements = document.querySelectorAll('[class*="leaflegend-group"]'); + elements.forEach(function(element) { + // Find the class that starts with 'leaflegend-group-' + let groupClass = Array.from(element.classList).find(cls => cls.startsWith('leaflegend-group-')); + + if (groupClass) { + // Extract everything after 'leaflegend-group-' + let groupName = groupClass.split('leaflegend-group-')[1]; + appendToLabel(groupName, element) + } + }); + } setTimeout(moveLegends, 40); map.on('overlayadd baselayerchange', () => setTimeout(moveLegends, 20)); diff --git a/inst/test_layerviewcontrol.R b/inst/test_layerviewcontrol.R index 545a28c..d80ea89 100644 --- a/inst/test_layerviewcontrol.R +++ b/inst/test_layerviewcontrol.R @@ -2,13 +2,16 @@ library(sf) library(shiny) library(leaflet) library(leaflet.extras) +library(leaflegend) library(leafem) options("shiny.autoreload" = TRUE) # Example data ########## breweries91 <- st_as_sf(breweries91) lines <- st_as_sf(atlStorms2005) -polys <- st_as_sf(leaflet::gadmCHE) +data("gadmCHE") +gadmCHE@data$x <- sample(c('A', 'B', 'C'), nrow(gadmCHE@data), replace = TRUE) +polys <- st_as_sf(gadmCHE) overlay1 <- "Overlay with Legend (orange)" overlay2 <- "Overlay with Legend (blue)" @@ -96,8 +99,14 @@ ui <- fluidPage( leafletOutput("map", height = 800) ) +## Server ################## server <- function(input, output, session) { output$map <- renderLeaflet({ + + factorPal <- colorFactor(c('#1f77b4', '#ff7f0e' , '#2ca02c'), gadmCHE@data$x) + binPal <- colorBin('Set1', lines$MaxWind, bins = 4) + quantPal <- colorQuantile('Reds', lines$MaxWind, n = 3) + m <- leaflet() %>% ## Baselayer ########## addTiles(group = "Base_tiles1") %>% @@ -105,12 +114,24 @@ server <- function(input, output, session) { ## Overlays ########## addCircleMarkers(data = breweries91, group = "breweries91") %>% - addCircleMarkers(data = pts, group = "random_points", color = "red", weight = 1) %>% - addPolylines(data = lines, group = "atlStorms2005") %>% - addPolygons(data = polys, group = "gadmCHE") %>% - addCircleMarkers(data = dfnew, ~x, ~y, color = ~palnew(z), group = overlay1) %>% - addCircleMarkers(data = dfnew, ~x, ~y, color = ~palnew2(z), group = overlay2) %>% - addLegend(data = dfnew, pal = palnew, layerId = overlay1, values = ~z, group = overlay1, position = "bottomleft") %>% + addCircleMarkers(data = pts, opacity = 1, fillOpacity = .4, + group = "random_points", color = "red", weight = 1) %>% + # addLegendSize(values = 1, color = 'red', shape = 'circle', breaks = 1, group = "random_points", layerId="random_points") %>% + addLegendImage(images = makeSymbol(shape="circle", color = "red", opacity = 1, fillOpacity = .4, width = 10), + labels = "", group = "random_points", layerId="random_points", + orientation = 'horizontal') %>% + addPolylines(data = lines, color = ~quantPal(MaxWind), label=~MaxWind, group = "atlStorms2005") %>% + addLegendQuantile(data = lines, pal = quantPal, values = ~MaxWind, numberFormat = NULL, + group = "atlStorms2005", position = 'topright') %>% + addPolygons(data = polys, color = ~factorPal(x), label=~x, group = "gadmCHE") %>% + addLegendFactor(pal = factorPal, shape = 'polygon', fillOpacity = .5, + opacity = 0, values = ~x, + position = 'topright', data = gadmCHE, group = 'gadmCHE') %>% + addCircleMarkers(data = dfnew, ~x, ~y, color = ~palnew(z), label=~z, group = overlay1) %>% + addCircleMarkers(data = dfnew, ~x, ~y, color = ~palnew2(z), label=~z, group = overlay2) %>% + addLegendNumeric(orientation = "horizontal", width = 180, height = 20, + data = dfnew, pal = palnew, layerId = overlay1, + values = ~z, group = overlay1, position = "bottomleft") %>% addLegend(data = dfnew, pal = palnew2, layerId = overlay2, values = ~z, group = overlay2, position = "bottomleft") %>% ## extendLayerControl ##########