VenueLayoutCanvas
A self-managed semantic-zoom venue map for meters-based layouts. Renders rect / polygon / polyline (roads + closed track band) / circle footprints in a meters viewBox, in painter's-algorithm layers (areas → roads → track → grandstands → objects). It drives its own pan/zoom (wheel / drag / pinch / double-click + a zoom-control cluster), so it knows meters-per-pixel and stays readable at every scale: only landmark labels at fit, more revealed as you zoom (LOD tiers + greedy collision), per-kind icons at object centers, a dot-collapse for tiny features, a scale bar, a north compass, and an optional legend that also filters kinds. Theme-token driven.
Import
import { VenueLayoutCanvas, type LayoutZone, SAMPLE_LAYOUT } from "@roger-emerson/ploxum-venue";Peer deps: react, react-dom, motion, lucide-react; depends on @roger-emerson/ploxum-utils. SAMPLE_LAYOUT is a ready-made synthetic festival layout for trying it out.
Usage
Give it a sized, overflow-hidden parent — it fills h-full w-full and owns pan/zoom itself (do not wrap it in MapViewport).
<div className="h-[560px] overflow-hidden">
<VenueLayoutCanvas
objects={SAMPLE_LAYOUT.objects}
bounds={SAMPLE_LAYOUT.bounds}
defaultView={SAMPLE_LAYOUT.defaultView}
anchorBearingDeg={SAMPLE_LAYOUT.anchorBearingDeg}
focusedZoneId={focused}
onZoneSelect={setFocused}
badgeLabel="Live"
badgeAccent
showLegend
showScaleBar
showZoomControls
/>
</div>;For a glanceable mini-map, drop the chrome (showLegend/showScaleBar/showZoomControls default to false) — it still fit-frames and shows landmark labels.
Props
VenueLayoutCanvas
| Prop | Type | Default | Description |
|---|---|---|---|
objects | LayoutZone[] | — | Footprint objects to render. |
bounds | { width: number; height: number } | — | Meters extent; the view frames the larger dimension at fit. |
defaultView | { center: { x; y }; spanM } | — | Initial framing center (span is overridden to fit bounds). |
anchorBearingDeg | number | 0 | Compass bearing map-up points toward; rotates the north arrow. |
focusedZoneId | string | null | null | Controlled selection (glow + emphasis). |
onZoneSelect | (id: string | null) => void | — | Selection handler; null on background click / Escape. |
showLabels | boolean | true | Zoom-aware LOD labels with collision. |
showCompass | boolean | true | North arrow (rotated by anchorBearingDeg). |
showLegend | boolean | false | Collapsible legend — a vertical dropdown menu whose rows toggle per-kind visibility. |
showScaleBar | boolean | false | Live meters scale bar (bottom-left). |
showZoomControls | boolean | false | + / − / reset cluster (bottom-right). |
isolatedZoneId | string | null | null | Dim every object except this id. |
badgeLabel / badgeAccent | string / boolean | — / false | Top-left badge text + amber accent. |
title | string | "Venue map" | Accessible <title>. |
className | string | — | Merged onto the root <div>. |
LayoutZone
{ id, label?, kind, status, footprint, intensity? }. kind is a VenueLayoutKind (stage, gate, vendor, amenity, parking, security, vip, track, grandstand, art, artCar, ride, medical, water, restroom, camping, road, tower, zone) — drives color + icon + layer. footprint is one of:
{ kind: "rect", center, width, depth, rotationDeg? }{ kind: "polygon", points }{ kind: "polyline", points, widthM, closed? }— open = road centerline;closed= a band ring (track){ kind: "circle", center, radius }
All coordinates are meters.
Design rules
- Semantic zoom is the readability mechanism: at fit only
stage/track/gate(+ big areas) are labeled; vendor/medical/etc. labels appear as you zoom in, always sized in constant pixels. A greedy collision pass drops any label that would overlap. - Small features collapse to an icon marker below ~9px on screen, so nothing becomes an illegible squiggle.
- The component owns pan/zoom (viewBox-based). It is not wrapped in
MapViewport— that's for the rect-onlyVenueCanvas. - Per-kind palette mirrors the e2c2 venue colors, with
stagetaking the ploxum amber brand; status ≠okcolors the stroke.
