-
Notifications
You must be signed in to change notification settings - Fork 4
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
Clustering #10
Comments
Hey @eug-vs did you find a way to make it work? |
Hey @florian-lefebvre, no, I decided to delay this feature until we come up with the proper way to do it. But honestly I didn't put much effort into it yet, I will try at some point and post an update here once I get something. |
Thanks for the answer! Meanwhile, I managed to get something working without this package but it's really hacky |
@florian-lefebvre good job! Wondering if it's something like this
or did you come up with a different approach? |
import { useMap, Marker } from 'react-leaflet' import { renderToString } from 'react-dom/server'
import { createRoot } from 'react-dom/client'
import { Icon } from '@iconify/react'
import type { Location } from '~/types/api'
import { useEffect } from 'react'
import L from 'leaflet'
import MarkerClusterGroup from 'react-leaflet-cluster'
import { useAtom } from 'jotai'
import {
filteredLocationsAtom,
selectedLocationAtom,
} from '~/stores/locations-filters'
let GLOBAL_ID = 0
const CLUSTER_CLICK_EVENT_KEY = 'leaftlet-cluster-click'
function CustomMarker({ location }: { location: Location }) {
const id = `custom-marker-${GLOBAL_ID++}`
const map = useMap()
const [, setSelectedLocation] = useAtom(selectedLocationAtom)
function render() {
;(async () => {
await Promise.resolve(0)
const el = document.getElementById(id)
if (!el || el.children.length !== 0) return
const root = createRoot(el)
root.render(<Icon icon={location.type.icon} />)
})()
}
map.on('zoomend', () => {
render()
})
useEffect(() => {
const handler = () => {
render()
}
handler()
document.addEventListener(CLUSTER_CLICK_EVENT_KEY, handler, {
passive: true,
})
return () => {
document.removeEventListener(CLUSTER_CLICK_EVENT_KEY, handler)
}
})
return (
<Marker
icon={L.divIcon({
html: renderToString(
<div
id={id}
className="absolute top-1/2 left-1/2 flex h-10 w-10 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-primary-300 font-serif text-lg"
></div>
),
})}
position={[location.point.latitude, location.point.longitude]}
eventHandlers={{
click: (event) => {
setSelectedLocation(location)
// map.setView(event.latlng, map.getZoom() + 2, {
// animate: true,
// })
},
}}
/>
)
}
export default function MapContent() {
const [locations] = useAtom(filteredLocationsAtom)
return (
<MarkerClusterGroup
chunkedLoading
showCoverageOnHover={false}
onClick={(e: any) => {
document.dispatchEvent(new Event(CLUSTER_CLICK_EVENT_KEY))
}}
>
{locations.map((location, i) => (
<CustomMarker key={i} location={location} />
))}
</MarkerClusterGroup>
)
} |
Wow that definitely looks, as you said, really hacky 😆 |
Honestly it's working not that bad, just have some edge cases where createRoot is called several times on the same node. |
After my last comment, I realized I had a waaaay better alternative for my specific use case: web components. Here is my new code using iconify: import { Marker } from 'react-leaflet'
import type { Location } from '~/types/api'
import L from 'leaflet'
import MarkerClusterGroup from 'react-leaflet-cluster'
import { useAtom } from 'jotai'
import {
filteredLocationsAtom,
selectedLocationAtom,
} from '~/stores/locations-filters'
import 'iconify-icon'
function CustomMarker({ location }: { location: Location }) {
const [, setSelectedLocation] = useAtom(selectedLocationAtom)
return (
<Marker
icon={L.divIcon({
html: `
<div class="absolute top-1/2 left-1/2 flex h-10 w-10 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-primary-300 font-serif text-lg">
<iconify-icon icon="${location.type.icon}"></iconify-icon>
</div>
`,
})}
position={[location.point.latitude, location.point.longitude]}
eventHandlers={{
click: (event) => {
setSelectedLocation(location)
// map.setView(event.latlng, map.getZoom() + 2, {
// animate: true,
// })
},
}}
/>
)
}
export default function MapContent() {
const [locations] = useAtom(filteredLocationsAtom)
return (
<MarkerClusterGroup chunkedLoading showCoverageOnHover={false}>
{locations.map((location, i) => (
<CustomMarker key={i} location={location} />
))}
</MarkerClusterGroup>
)
} But generally speaking, I think that wrapping the react component into a web component might be the best way: https://reactjs.org/docs/web-components.html#using-react-in-your-web-componentshttps://reactjs.org/docs/web-components.html#using-react-in-your-web-components |
Is there a solution for clustering? |
Something like this? https://www.regiolangues.fr/carte It can be done by not actually using this module. For the explanation, check out this article: https://florian-lefebvre.dev/projects/regiolangues#spotlight-interactive-map Here is an example from a project: import { Marker } from 'react-leaflet'
import type { Location } from '~/types/api'
import L from 'leaflet'
import MarkerClusterGroup from 'react-leaflet-cluster'
import { useAtomValue, useSetAtom } from 'jotai'
import {
filteredLocationsAtom,
selectedLocationAtom,
} from '~/stores/locations-filters'
import { useMemo } from 'react'
import { renderToString } from 'react-dom/server'
import MarkerIcon from '~/components/InteractiveMap/MarkerIcon'
import useLeaflet from '~/hooks/use-leaflet'
function CustomMarker({ location }: { location: Location }) {
const setSelectedLocation = useSetAtom(selectedLocationAtom)
return (
<Marker
icon={L.divIcon({
html: renderToString(
<MarkerIcon type={location.type} location={location} />
),
})}
position={[location.point.latitude, location.point.longitude]}
eventHandlers={{
click: (event) => {
setSelectedLocation(location)
},
}}
/>
)
}
export default function MapContent() {
const locations = useAtomValue(filteredLocationsAtom)
const { handleGeoURLSearchParams } = useLeaflet()
handleGeoURLSearchParams()
const _locations = useMemo(
() =>
locations.map((location, i) => (
<CustomMarker key={i} location={location} />
)),
[locations]
)
return (
<MarkerClusterGroup
chunkedLoading
showCoverageOnHover={false}
maxClusterRadius={80}
spiderfyOnMaxZoom={true}
disableClusteringAtZoom={null}
animate={true}
animateAddingMarkers={false}
>
{_locations}
</MarkerClusterGroup>
)
} |
There is also a simple example about clustering https://www.youtube.com/watch?v=hkrnyDg3nxg |
I know this is out of scope of this project, but is there any way to get the clustering work? All major clustering libraries for
react-leaflet
don't seem to work with this wrapper (e.g https://github.com/akursat/react-leaflet-cluster), or maybe I'm doing something wrong. I've tried wrappingMarkerLayer
intoMarkerClusterGroup
and vice-versa. There's always an option to reinvent the wheel and add some clustering logic before the markers are rendered, but I guess we'd lose animations this way. Any tip on this will be helpful.This project is a gem, thanks for open-sourcing it! 🔥
The text was updated successfully, but these errors were encountered: