Skip to content

added crosstalk filters to leaflet cluster markers #949

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -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).
Expand Down
125 changes: 125 additions & 0 deletions inst/htmlwidgets/assets/leaflet.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}
Expand Down
124 changes: 124 additions & 0 deletions javascript/src/methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down