diff --git a/NEWS.md b/NEWS.md index d19b8f52f..842d3515b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # leaflet (development version) +* `{leaflet}` cluster markers now filter according to `{crosstalk}` filters. + * `{leaflet}` no longer install sp by default and attempts to convert object to sf internally before creating a map and warns when it fails conversion (@olivroy, #942). * The `breweries91`, `atlStorms2005`, and `gadmCHE` datasets are now `{sf}` objects (@olivroy, #944). diff --git a/inst/htmlwidgets/assets/leaflet.js b/inst/htmlwidgets/assets/leaflet.js index 79dbe714a..de80e05b3 100644 --- a/inst/htmlwidgets/assets/leaflet.js +++ b/inst/htmlwidgets/assets/leaflet.js @@ -1483,6 +1483,100 @@ function unpackStrings(iconset) { } function addMarkers(map, df, group, clusterOptions, clusterId, markerFunc) { + function updateClusterMarkers(filtered, ctGroup, clusterGroup, ctKeyColumn) { + // For safety, check required parameters + if (!filtered) { + return; + } + + if (!clusterGroup) { + return; + } // Handle both null/undefined and empty array cases for filter values + + + let filterValues = filtered.value && Array.isArray(filtered.value) ? filtered.value.map(function (v) { + return String(v); + }) : []; + + let showAllMarkers = false; + if (filterValues.length === 0) { + showAllMarkers = true; + } + + + let options = Object.assign({}, clusterOptions); // Create a fresh cluster + + let newClusterGroup = _leaflet2["default"].markerClusterGroup.layerSupport(options); + + if (options.freezeAtZoom) { + let freezeAtZoom = options.freezeAtZoom; + newClusterGroup.freezeAtZoom(freezeAtZoom); + } + + newClusterGroup.clusterLayerStore = new _clusterLayerStore2["default"](newClusterGroup); // If showing all markers, use all rows; otherwise filter + + let rowsToShow = []; + + for (let i = 0; i < df.nrow(); i++) { + // When showing all markers OR marker key is in filter values + let ctKey = String(df.get(i, ctKeyColumn) || ""); + let shouldShow = showAllMarkers || filterValues.includes(ctKey); + + if (shouldShow) { + rowsToShow.push(i); + } + } + + + for (let _i = 0, _rowsToShow = rowsToShow; _i < _rowsToShow.length; _i++) { + let _i2 = _rowsToShow[_i]; + + if (_jquery2["default"].isNumeric(df.get(_i2, "lat")) && _jquery2["default"].isNumeric(df.get(_i2, "lng"))) { + // Create a fresh marker - safer than trying to reuse existing ones + let marker = markerFunc(df, _i2); + let thisId = df.get(_i2, "layerId"); // Add popup content if in the dataframe + + let popup = df.get(_i2, "popup"); + let popupOptions = df.get(_i2, "popupOptions"); + + if (popup !== null) { + if (popupOptions !== null) { + marker.bindPopup(popup, popupOptions); + } else { + marker.bindPopup(popup); + } + } // Add label/tooltip if in the dataframe + + + let label = df.get(_i2, "label"); + let labelOptions = df.get(_i2, "labelOptions"); + + if (label !== null) { + if (labelOptions !== null) { + if (labelOptions.permanent) { + marker.bindTooltip(label, labelOptions).openTooltip(); + } else { + marker.bindTooltip(label, labelOptions); + } + } else { + marker.bindTooltip(label); + } + } // Add to cluster with appropriate ID + + + newClusterGroup.clusterLayerStore.add(marker, thisId); + } + } // Replace the old cluster group with the new one + + + map.removeLayer(clusterGroup); + map.addLayer(newClusterGroup); // Update the layer manager reference to the new cluster group + + map.layerManager.removeLayer("cluster", clusterId); + map.layerManager.addLayer(newClusterGroup, "cluster", clusterId, group); + return newClusterGroup; + } + (function () { var _this3 = this; @@ -1556,6 +1650,37 @@ function addMarkers(map, df, group, clusterOptions, clusterId, markerFunc) { _loop2(i); } + if (cluster && df.get(0, "ctGroup")) { + let ctGroup = df.get(0, "ctGroup"); + let ctKeyColumn = "ctKey"; + + // Check if global crosstalk is available + if (window.crosstalk || global.crosstalk) { + let crosstalkObj = window.crosstalk || global.crosstalk; // Create a proper filter handle instead of using group().on() + + let filterHandle = new crosstalkObj.FilterHandle(ctGroup); // Subscribe to filter changes using the handle + + filterHandle.on("change", function (e) { + let newClusterGroup = updateClusterMarkers(e, ctGroup, clusterGroup, ctKeyColumn); + + if (newClusterGroup) { + clusterGroup = newClusterGroup; + } + }); // Initial setup + + + if (filterHandle.filteredKeys) { + let newClusterGroup = updateClusterMarkers({ + value: filterHandle.filteredKeys + }, ctGroup, clusterGroup, ctKeyColumn); + + if (newClusterGroup) { + clusterGroup = newClusterGroup; + } + } + } + } + if (cluster) { this.layerManager.addLayer(clusterGroup, "cluster", clusterId, group); } diff --git a/javascript/src/methods.js b/javascript/src/methods.js index 91d8ea70b..687ceb0cc 100644 --- a/javascript/src/methods.js +++ b/javascript/src/methods.js @@ -148,6 +148,95 @@ function unpackStrings(iconset) { } function addMarkers(map, df, group, clusterOptions, clusterId, markerFunc) { + function updateClusterMarkers(filtered, ctGroup, clusterGroup, ctKeyColumn) { + // For safety, check required parameters + if (!filtered) { + return; + } + + if (!clusterGroup) { + return; + } + + // Handle both null/undefined and empty array cases for filter values + const filterValues = filtered.value && Array.isArray(filtered.value) + ? filtered.value.map(v => String(v)) + : []; + + const showAllMarkers = filtered.value === null || filtered.value === undefined; + + // Store the original cluster options to reuse them + const options = Object.assign({}, clusterOptions); + + // Create a fresh cluster + const newClusterGroup = L.markerClusterGroup.layerSupport(options); + if (options.freezeAtZoom) { + let freezeAtZoom = options.freezeAtZoom; + newClusterGroup.freezeAtZoom(freezeAtZoom); + } + newClusterGroup.clusterLayerStore = new ClusterLayerStore(newClusterGroup); + + // If showing all markers, use all rows; otherwise filter + const rowsToShow = []; + for (let i = 0; i < df.nrow(); i++) { + // When showing all markers OR marker key is in filter values + const ctKey = String(df.get(i, ctKeyColumn) || ""); + const shouldShow = showAllMarkers || filterValues.includes(ctKey); + + if (shouldShow) { + rowsToShow.push(i); + } + } + + // Process rows that should be shown + for (let i of rowsToShow) { + if ($.isNumeric(df.get(i, "lat")) && $.isNumeric(df.get(i, "lng"))) { + // Create a fresh marker - safer than trying to reuse existing ones + const marker = markerFunc(df, i); + const thisId = df.get(i, "layerId"); + + // Add popup content if in the dataframe + let popup = df.get(i, "popup"); + let popupOptions = df.get(i, "popupOptions"); + if (popup !== null) { + if (popupOptions !== null) { + marker.bindPopup(popup, popupOptions); + } else { + marker.bindPopup(popup); + } + } + + // Add label/tooltip if in the dataframe + let label = df.get(i, "label"); + let labelOptions = df.get(i, "labelOptions"); + if (label !== null) { + if (labelOptions !== null) { + if (labelOptions.permanent) { + marker.bindTooltip(label, labelOptions).openTooltip(); + } else { + marker.bindTooltip(label, labelOptions); + } + } else { + marker.bindTooltip(label); + } + } + + // Add to cluster with appropriate ID + newClusterGroup.clusterLayerStore.add(marker, thisId); + } + } + + // Replace the old cluster group with the new one + map.removeLayer(clusterGroup); + map.addLayer(newClusterGroup); + + // Update the layer manager reference to the new cluster group + map.layerManager.removeLayer("cluster", clusterId); + map.layerManager.addLayer(newClusterGroup, "cluster", clusterId, group); + + return newClusterGroup; + } + (function() { let clusterGroup = this.layerManager.getLayer("cluster", clusterId), cluster = clusterOptions !== null; @@ -203,6 +292,41 @@ function addMarkers(map, df, group, clusterOptions, clusterId, markerFunc) { } } + // Set up crosstalk filtering for clusters + if (cluster && df.get(0, "ctGroup")) { + let ctGroup = df.get(0, "ctGroup"); + let ctKeyColumn = "ctKey"; + + + // Check if global crosstalk is available + if (window.crosstalk || global.crosstalk) { + let crosstalkObj = window.crosstalk || global.crosstalk; + + // Create a proper filter handle instead of using group().on() + let filterHandle = new crosstalkObj.FilterHandle(ctGroup); + + // Subscribe to filter changes using the handle + filterHandle.on("change", function(e) { + const newClusterGroup = updateClusterMarkers(e, ctGroup, clusterGroup, ctKeyColumn); + if (newClusterGroup) { + clusterGroup = newClusterGroup; + } + }); + + // Initial setup + if (filterHandle.filteredKeys) { + const newClusterGroup = updateClusterMarkers( + {value: filterHandle.filteredKeys}, + ctGroup, + clusterGroup, + ctKeyColumn + ); + if (newClusterGroup) { + clusterGroup = newClusterGroup; + } + } + } + } if (cluster) { this.layerManager.addLayer(clusterGroup, "cluster", clusterId, group); }