Skip to content
Merged
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
8 changes: 5 additions & 3 deletions app/components/MapLegend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ export function MapLegend({
) : selectedLabel ? (
<>
Viewing <span className="font-semibold text-oa-navy">{selectedLabel}</span>
. Click another area to change · drag to pan · scroll to zoom.
. Click another area to change · click-drag to pan · Ctrl+scroll or
pinch to zoom.
</>
) : (
<span className="text-oa-grey-600">
Click an area to filter, or use the location filter. Drag to pan ·
scroll or pinch to zoom.
Click an area to filter, or use the location filter. Click-drag to
pan · Ctrl+scroll or pinch to zoom · two fingers on touch. Normal
scroll moves the page.
</span>
)}
</p>
Expand Down
26 changes: 21 additions & 5 deletions app/components/OpportunityMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,20 @@ export function OpportunityMap({
const zoom = d3
.zoom<SVGSVGElement, unknown>()
.scaleExtent([1, 14])
// Page scroll wins for one-finger touch and normal trackpad/mouse wheel.
// Map pan/zoom: primary-button drag, Ctrl/Meta+wheel (pinch), two-finger touch.
.filter((event) => {
if (event.type === "wheel") {
// Pinch is delivered as wheel+ctrlKey; plain two-finger scroll must
// reach the page, so only accept modified wheel here.
return event.ctrlKey || event.metaKey;
}
if (event.type === "touchstart" || event.type === "touchmove") {
return event.touches.length >= 2;
}
// Primary mouse button drag pans the map.
return event.button === 0;
})
.on("zoom", (event) => {
zoomRoot.attr("transform", event.transform.toString());
});
Expand Down Expand Up @@ -398,7 +412,7 @@ export function OpportunityMap({
<figure className="relative flex h-full min-h-0 w-full flex-col">
<div
ref={containerRef}
className="relative h-full min-h-[480px] w-full flex-1 touch-none outline-none [-webkit-tap-highlight-color:transparent] [&_svg]:outline-none [&_svg:focus]:outline-none [&_path]:outline-none"
className="relative h-full min-h-120 w-full flex-1 outline-none [-webkit-tap-highlight-color:transparent] [&_svg]:outline-none [&_svg:focus]:outline-none [&_path]:outline-none"
style={{ background: "linear-gradient(165deg, #e4ecf4 0%, #d6e2ec 45%, #c8d6e2 100%)" }}
>
{isAutoFramed && (
Expand Down Expand Up @@ -441,7 +455,7 @@ export function OpportunityMap({

<svg
ref={svgRef}
className={`h-full w-full touch-none cursor-grab outline-none focus:outline-none active:cursor-grabbing ${status !== "ready" ? "opacity-0" : ""}`}
className={`h-full w-full cursor-grab outline-none focus:outline-none active:cursor-grabbing ${status !== "ready" ? "opacity-0" : ""}`}
aria-labelledby="map-title"
aria-describedby={tooltipId}
tabIndex={-1}
Expand All @@ -461,9 +475,11 @@ export function OpportunityMap({

<figcaption className="sr-only" id="map-title">
Choropleth map of opportunities per {boundaryNoun(loadedBoundaryType)}.
Click an area to filter by that location, or use the location filter.
Drag to pan and scroll or pinch to zoom; zoom buttons are available
after the filters in the tab order.
Click or tap an area to filter by that location, or use the location
filter. Click and drag to pan. Pinch or hold Ctrl and scroll to zoom; on
touch, use two fingers to move or zoom the map. One finger or a normal
trackpad scroll moves the page. Zoom buttons are available after the
filters in the tab order.
</figcaption>

<MapLegend
Expand Down
16 changes: 16 additions & 0 deletions docs/explorer-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ Clicking an area calls `onAreaSelect` with a name, optional code, and boundary t

`scopeAreaNames` highlights which areas are in the current selection. `selectedDistrict` is used when exactly one area or trust is chosen. Zoom controls and the legend sit in `MapZoomControls.tsx` and `MapLegend.tsx`.

## Touch and scroll

The map sits on a long page, so everyday scrolling must move the page, not the map.

In `OpportunityMap.tsx`:

- The map container does **not** use `touch-none`, so the browser can scroll on one finger
- D3 zoom has a `.filter(...)` so:
- **touch pan/zoom** only starts with **two or more** fingers
- **wheel zoom** only when **Ctrl** or **Meta** is held (trackpad pinch). A normal two-finger trackpad scroll moves the page
- **mouse:** primary-button click-drag pans the map
- The `+` / `-` / reset buttons always zoom
- A short tap still selects an area (existing click vs drag threshold)

Same rules on phones, tablets, and large screens.

## What this is not

The map does not call Monitor for geometry. It only uses Monitor for the counts. Changing the boundary files in `app/data/` is how you update the basemap.
Loading