Fix map UX: instant load, stable hex grid, and clickable hexagons

- Skip fly-to animation on initial data load (fitBounds duration: 0)
- Replace turf.hexGrid with origin-anchored manual hex generation so
  hexagons stay stable across pan/zoom instead of flickering
- Quantize zoom to integers so hex pattern only changes at discrete levels
- Scale search radius with cell size so low-zoom hexagons find their data
- Make hexagons clickable with pointer cursor; each hex shows the exact
  listings it aggregated via stored search bounds
- Remove unused SEARCH_BUFFER constant
This commit is contained in:
Viktor Barzin 2026-02-07 21:59:02 +00:00
parent 150342bb9e
commit 42d34fd21a
No known key found for this signature in database
GPG key ID: 0EB088298288D958
3 changed files with 93 additions and 45 deletions

View file

@ -157,7 +157,7 @@ export function Map(props: MapProps) {
mapRef.current?.fitBounds([
[minlng, minlat],
[maxlng, maxlat]
]);
], { duration: 0 });
}
lastDataLengthRef.current = subset.features.length;
@ -179,8 +179,24 @@ export function Map(props: MapProps) {
lastDataLengthRef.current = 0;
updateHeatmap();
});
mapRef.current.on('click', function (e: mapboxgl.MapMouseEvent) {
openListingsDialog(e.lngLat.lng, e.lngLat.lat);
mapRef.current.on('click', 'hexgrid-heatmap', function (e: mapboxgl.MapLayerMouseEvent) {
if (e.features && e.features.length > 0) {
const props = e.features[0].properties;
if (props) {
openListingsDialog(e.lngLat.lng, e.lngLat.lat, {
minX: props.searchMinX,
minY: props.searchMinY,
maxX: props.searchMaxX,
maxY: props.searchMaxY
});
}
}
});
mapRef.current.on('mouseenter', 'hexgrid-heatmap', function () {
if (mapRef.current) mapRef.current.getCanvas().style.cursor = 'pointer';
});
mapRef.current.on('mouseleave', 'hexgrid-heatmap', function () {
if (mapRef.current) mapRef.current.getCanvas().style.cursor = '';
});
return () => {
if (updateTimeoutRef.current) {
@ -293,16 +309,10 @@ export function Map(props: MapProps) {
.text(metricInfo.high);
}
function openListingsDialog(longitude: number, latitude: number) {
function openListingsDialog(longitude: number, latitude: number, searchBounds: { minX: number; minY: number; maxX: number; maxY: number }) {
if (!heatmapRef.current || !mapRef.current) return;
const searchBuffer = HEATMAP_CONFIG.SEARCH_BUFFER;
const properties = heatmapRef.current._tree.search({
minX: longitude - searchBuffer,
maxX: longitude + searchBuffer,
minY: latitude - searchBuffer,
maxY: latitude + searchBuffer
});
const properties = heatmapRef.current._tree.search(searchBounds);
if (properties.length > 0) {
const listingDialogPopup = getListingDialog(properties);
new mapboxgl.Popup()