Appearance
2D viewer
A read-only 2D view of a plan, with pan & zoom, click coordinates and markers.
vue
<script setup lang="ts">
import { FloorPlanViewer, type PlanClickEvent } from '@jbaysolutions/floorplan-vue'
function onClick(e: PlanClickEvent) {
console.log(e.x, e.y, e.room?.name)
}
</script>
<template>
<div style="height: 500px">
<FloorPlanViewer :plan="plan" :markers="markers" theme="dark" @plan-click="onClick" />
</div>
</template>tsx
import { FloorplanViewer, type PlanClickEvent } from '@jbaysolutions/floorplan-react'
function onClick(e: PlanClickEvent) {
console.log(e.x, e.y, e.room?.name)
}
<FloorplanViewer plan={plan} markers={markers} theme="dark" onPlanClick={onClick} style={{ height: 500 }} />html
<floorplan-viewer theme="dark" style="height: 500px"></floorplan-viewer>
<script type="module">
const viewer = document.querySelector('floorplan-viewer')
viewer.plan = plan
viewer.markers = markers
viewer.addEventListener('plan-click', (e) => console.log(e.detail.x, e.detail.y, e.detail.room?.name))
</script>Click anywhere on the hotel below to see the coordinates, room and floor it reports. Switch floors with the buttons at the top right.
Loading the live demo…
Click the plan to see the event.
Getting click coordinates
ts
import type { PlanClickEvent } from '@jbaysolutions/floorplan-vue' // or floorplan-react / floorplan-elements
function onClick({ x, y, room, floor, originalEvent }: PlanClickEvent) {
// x, y: plan coordinates in centimetres (1 decimal by default)
// room: the Room under the click, or null outside any room
// floor: the Floor that was clicked
}Coordinates are in plan space, not screen pixels, so they don't change with zoom, pan or screen size, and match the coordinates in the data model. Dragging to pan does not fire plan-click.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
plan | FloorPlan | null | — | The plan to show. |
floor (v-model:floor) | string | null | ground floor | Id of the floor shown. |
showFloorSwitcher | boolean | 'auto' | 'auto' | Floor buttons (top right). 'auto' shows them for multi-floor plans. |
markers | Marker[] | [] | Images / icons to show. See Markers. |
theme | 'light' | 'dark' | 'blueprint' | 'paper' | 'mist' | 'ink' | 'graphite' | 'light' | See Themes. |
showGrid | boolean | false | Background grid. |
showDimensions | boolean | false | Wall length labels. |
showRoomLabels | boolean | true | Room names. |
showRoomArea | boolean | true | Room areas under the names. |
showFurniture | boolean | true | Furniture layer. Stairs and elevators are part of the building and always show (FIXTURE_KINDS). |
showControls | boolean | true | Zoom buttons and scale bar. |
showCoordinates | boolean | true | Live cursor coordinates (bottom left). |
interactive | boolean | true | Allow pan & zoom. Clicks still work when false. |
autoFit | boolean | true | Re-fit when the plan's outer bounds change. |
markersDraggable | boolean | false | Let users drag markers. |
markerSize | number | 40 | Default marker size in px. |
markerVariant | 'badge' | 'pin' | 'plain' | 'dot' | 'badge' | Default marker style. |
alwaysShowLabels | boolean | false | Show marker labels without hovering. |
selectedMarkerId | string | number | null | null | Highlights one marker. |
highlightRooms | string[] | [] | Room ids to highlight. |
hoverRooms | boolean | true | Highlight the room under the cursor. |
fitPadding | number | 48 | Padding in px when fitting. |
minZoom / maxZoom | number | 0.03 / 8 | Zoom limits (1 = 1 cm per px). |
cursor | string | 'crosshair' | CSS cursor over the plan. |
precision | number | 1 | Decimal places in emitted coordinates. |
formatLength | (cm) => string | "3.45 m" | Formatter for dimensions. |
In React the props have the same names (showGrid, markersDraggable={true}). On the <floorplan-viewer> element they are properties with the same names (viewer.showGrid = true); simple values also work as kebab-case attributes (show-grid, marker-size="36"). Objects, arrays and functions (plan, markers, highlightRooms, formatLength) must be set as properties.
Events
| Event | Payload | Description |
|---|---|---|
plan-click | { x, y, room, floor, originalEvent } | Click on the plan (not fired after a drag). |
marker-click | (marker, event) | A marker was clicked. |
marker-drag | { marker, x, y, room, floor } | Fired continuously while dragging a marker. |
marker-move | { marker, x, y, room, floor } | A marker drag finished. Update your data here. |
room-hover | Room | null | The pointer entered or left a room. |
update:floor | string | The user switched floors. |
The same events in React and on the element:
| Vue | React | HTML element (e.detail) |
|---|---|---|
@plan-click | onPlanClick(e) | plan-click: the event object { x, y, room, floor, originalEvent } |
@marker-click | onMarkerClick(marker, originalEvent) | marker-click: { marker, originalEvent } |
@marker-drag | onMarkerDrag(e) | marker-drag: the event object { marker, x, y, room, floor } |
@marker-move | onMarkerMove(e) | marker-move: the event object { marker, x, y, room, floor } |
@room-hover | onRoomHover(room) | room-hover: { room } |
v-model:floor (update:floor) | floor + onFloorChange(floorId) | floor property + floor-change: { floor } |
vue
<FloorPlanViewer
:plan="plan"
v-model:floor="floorId"
@marker-click="(marker) => (selected = marker.id)"
@room-hover="(room) => (hovered = room?.name ?? '')"
/>tsx
<FloorplanViewer
plan={plan}
floor={floorId}
onFloorChange={setFloorId}
onMarkerClick={(marker) => setSelected(marker.id)}
onRoomHover={(room) => setHovered(room?.name ?? '')}
style={{ height: 500 }}
/>js
viewer.addEventListener('marker-click', (e) => (selected = e.detail.marker.id))
viewer.addEventListener('room-hover', (e) => (hovered = e.detail.room?.name ?? ''))
viewer.addEventListener('floor-change', (e) => (floorId = e.detail.floor))Methods
In Vue, call methods through a template ref. In React, the ref is the <floorplan-viewer> element itself (useRef<FloorplanViewerElement>(null)). On the HTML element, call them on the element.
| Method | Description |
|---|---|
fit() | Animate to show the whole plan. |
focus(x, y, zoom?) | Fly to a plan point. In React and on the element: focusPoint(x, y, zoom?), so the element's own focus() keeps working. |
focusMarker(id, zoom?) | Fly to a marker (switches to its floor). |
focusRoom(id) | Frame a room (switches to its floor). |
setFloor(id) | Show another floor. |
zoomIn() / zoomOut() | Step the zoom. |
planToScreen(x, y) | Screen position (px, relative to the viewer) of a plan point, e.g. to place your own overlay. |
clientToPlan(clientX, clientY) | Convert a screen position (e.g. from a drop event) into plan coordinates. |
roomAt({ x, y }) | The room containing a plan point, or null. |
Slots
| Slot | Props | Description |
|---|---|---|
marker | { marker, selected, dragging, zoom } | Replace the marker template. See Markers. In React: renderMarker; on the element: markerRenderer. |
| default | — | Extra content drawn on top of the viewer (your own overlays, buttons…). Vue only. |
Interaction
- Mouse: drag to pan, scroll to zoom (zooms towards the cursor).
- Touch: one finger pans, two fingers pinch-zoom.
- Trackpad: pinch zooms.
Example: highlight a room from a list
vue
<FloorPlanViewer ref="viewer" :plan="plan" :highlight-rooms="[selectedRoomId]" />
<template v-for="f in plan.floors" :key="f.id">
<li v-for="r in f.rooms" :key="r.id" @click="selectedRoomId = r.id; viewer.focusRoom(r.id)">{{ f.name }} · {{ r.name }}</li>
</template>tsx
const viewer = useRef<FloorplanViewerElement>(null)
const [selectedRoomId, setSelectedRoomId] = useState('')
<FloorplanViewer ref={viewer} plan={plan} highlightRooms={[selectedRoomId]} style={{ height: 500 }} />
{plan.floors.flatMap((f) =>
f.rooms.map((r) => (
<li key={r.id} onClick={() => { setSelectedRoomId(r.id); viewer.current?.focusRoom(r.id) }}>
{f.name} · {r.name}
</li>
)),
)}js
const viewer = document.querySelector('floorplan-viewer')
const list = document.querySelector('#rooms')
for (const f of plan.floors) {
for (const r of f.rooms) {
const li = document.createElement('li')
li.textContent = `${f.name} · ${r.name}`
li.onclick = () => {
viewer.highlightRooms = [r.id]
viewer.focusRoom(r.id)
}
list.append(li)
}
}