From c9b461977d25be5d0e4c1b22179511bcaaecc44c Mon Sep 17 00:00:00 2001 From: Evan Jones Date: Wed, 28 May 2025 22:06:44 -0500 Subject: [PATCH] Added support for a `cluster_map_options` configuration, which allows full customization of the Leaflet `markerClusterGroup()`. This allows finer control of clustering behavior and when close markers will be combined ("spiderfying") See: https://github.com/Leaflet/Leaflet.markercluster?tab=readme-ov-file#options --- README.md | 21 +++++++++++++++++++ datasette_cluster_map/__init__.py | 6 ++++++ .../static/datasette-cluster-map.js | 11 ++++++++-- tests/test_cluster_map.py | 6 ++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 697bd4c..219e9d9 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,27 @@ from [USGS_WC_eartag_deployments_2009-2011] The map defaults to being displayed above the main results table on the page. You can use the `"container"` plugin setting to provide a CSS selector indicating an element that the map should be appended to instead. +### Customizing marker cluster group options + +You can customize the [Leaflet.markercluster](https://github.com/Leaflet/Leaflet.markercluster?tab=readme-ov-file#options) options by supplying a `cluster_map_options` dictionary in your plugin configuration. These options are merged with the plugin's defaults (`chunkedLoading: true`, `maxClusterRadius: 50`); your values override the defaults. + +For example, to make fewer, larger clusters, you can increase [`maxClusterRadius`](https://github.com/Leaflet/Leaflet.markercluster?tab=readme-ov-file#other-options): + +```json +{ + "plugins": { + "datasette-cluster-map": { + "cluster_map_options": { + "maxClusterRadius": 80 + } + } + } +} +``` + +See the [Leaflet.markercluster options documentation](https://github.com/Leaflet/Leaflet.markercluster?tab=readme-ov-file#options) for all available options. + + ## Custom tile layers You can customize the tile layer used by the maps using the `tile_layer` and `tile_layer_options` configuration settings. For example, to use the [OpenTopoMap](https://opentopomap.org/) you can use these settings: diff --git a/datasette_cluster_map/__init__.py b/datasette_cluster_map/__init__.py index d18f4ea..87af7f8 100644 --- a/datasette_cluster_map/__init__.py +++ b/datasette_cluster_map/__init__.py @@ -44,6 +44,12 @@ def extra_body_script(database, table, columns, view_name, datasette): json.dumps(config.get("tile_layer_options") or TILE_LAYER_OPTIONS) ) ) + + # Add cluster_map_options if present + if "cluster_map_options" in config: + js.append( + f"window.DATASETTE_CLUSTER_MAP_OPTIONS = {json.dumps(config['cluster_map_options'])};" + ) if config.get("container"): js.append( "window.DATASETTE_CLUSTER_MAP_CONTAINER = {};".format( diff --git a/datasette_cluster_map/static/datasette-cluster-map.js b/datasette_cluster_map/static/datasette-cluster-map.js index cf063cd..dbd809c 100644 --- a/datasette_cluster_map/static/datasette-cluster-map.js +++ b/datasette_cluster_map/static/datasette-cluster-map.js @@ -298,10 +298,17 @@ const addClusterMap = (latitudeColumn, longitudeColumn) => { let progressDiv = document.createElement("div"); progressDiv.style.marginBottom = "2em"; el.parentNode.insertBefore(progressDiv, el.nextSibling); - let markerClusterGroup = L.markerClusterGroup({ + // Merge user-supplied cluster_map_options with defaults + const defaultClusterOptions = { chunkedLoading: true, maxClusterRadius: 50, - }); + }; + const userClusterOptions = (typeof window.DATASETTE_CLUSTER_MAP_OPTIONS === 'object' && window.DATASETTE_CLUSTER_MAP_OPTIONS !== null) + ? window.DATASETTE_CLUSTER_MAP_OPTIONS + : {}; + let markerClusterGroup = L.markerClusterGroup( + Object.assign({}, defaultClusterOptions, userClusterOptions) + ); map.addLayer(markerClusterGroup); let path = location.pathname + ".json" + location.search; const qs = "_size=max&_labels=on&_extra=count&_extra=next_url&_shape=objects"; diff --git a/tests/test_cluster_map.py b/tests/test_cluster_map.py index 2d0d64d..7ec8d54 100644 --- a/tests/test_cluster_map.py +++ b/tests/test_cluster_map.py @@ -98,6 +98,12 @@ def db_path(tmp_path_factory): 'window.DATASETTE_CLUSTER_MAP_LONGITUDE_COLUMN = "lng";', ], ), + # Test cluster_map_options emits correct JS + ( + {"cluster_map_options": {"spiderfyOnMaxZoom": False}}, + "places", + ['window.DATASETTE_CLUSTER_MAP_OPTIONS = {"spiderfyOnMaxZoom": false};'], + ), ], ) async def test_plugin_config(db_path, config, table, expected_fragments):