Skip to content

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 ​

PropTypeDefaultDescription
planFloorPlan | null—The plan to show.
floor (v-model:floor)string | nullground floorId of the floor shown.
showFloorSwitcherboolean | 'auto''auto'Floor buttons (top right). 'auto' shows them for multi-floor plans.
markersMarker[][]Images / icons to show. See Markers.
theme'light' | 'dark' | 'blueprint' | 'paper' | 'mist' | 'ink' | 'graphite''light'See Themes.
showGridbooleanfalseBackground grid.
showDimensionsbooleanfalseWall length labels.
showRoomLabelsbooleantrueRoom names.
showRoomAreabooleantrueRoom areas under the names.
showFurniturebooleantrueFurniture layer. Stairs and elevators are part of the building and always show (FIXTURE_KINDS).
showControlsbooleantrueZoom buttons and scale bar.
showCoordinatesbooleantrueLive cursor coordinates (bottom left).
interactivebooleantrueAllow pan & zoom. Clicks still work when false.
autoFitbooleantrueRe-fit when the plan's outer bounds change.
markersDraggablebooleanfalseLet users drag markers.
markerSizenumber40Default marker size in px.
markerVariant'badge' | 'pin' | 'plain' | 'dot''badge'Default marker style.
alwaysShowLabelsbooleanfalseShow marker labels without hovering.
selectedMarkerIdstring | number | nullnullHighlights one marker.
highlightRoomsstring[][]Room ids to highlight.
hoverRoomsbooleantrueHighlight the room under the cursor.
fitPaddingnumber48Padding in px when fitting.
minZoom / maxZoomnumber0.03 / 8Zoom limits (1 = 1 cm per px).
cursorstring'crosshair'CSS cursor over the plan.
precisionnumber1Decimal 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 ​

EventPayloadDescription
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-hoverRoom | nullThe pointer entered or left a room.
update:floorstringThe user switched floors.

The same events in React and on the element:

VueReactHTML element (e.detail)
@plan-clickonPlanClick(e)plan-click: the event object { x, y, room, floor, originalEvent }
@marker-clickonMarkerClick(marker, originalEvent)marker-click: { marker, originalEvent }
@marker-dragonMarkerDrag(e)marker-drag: the event object { marker, x, y, room, floor }
@marker-moveonMarkerMove(e)marker-move: the event object { marker, x, y, room, floor }
@room-hoveronRoomHover(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.

MethodDescription
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 ​

SlotPropsDescription
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)
  }
}

JBay Floorplan is free for open source and companies under €1M yearly revenue. Licensing and pricing