Appearance
Data model
A plan is a plain JSON object. Store it in a database, send it over an API or keep it in a file. The components never store data themselves.
The format is the same in every package: a plan saved from the Vue editor opens in the React viewer or the HTML elements, and the other way round.
Units and coordinates
- All lengths and positions are in centimetres.
xgrows to the right andygrows downwards, as on screen.- The origin
(0, 0)can be anywhere; plans don't need to start at zero. - Rotations are in degrees, clockwise.
Click events and markers use the same coordinate system, so a plan-click at { x: 420, y: 260 } is the same point as a marker at { x: 420, y: 260 }.
FloorPlan
A plan is a building made of one or more floors. Each floor holds its own walls, rooms, doors, windows and furniture.
ts
interface FloorPlan {
version: 25 // format version, see below
name?: string
floors: Floor[] // at least one
}
interface Floor {
id: string
name: string // e.g. "Ground floor", "Level 2", "Basement"
level: number // 0 = ground floor, 1 = the floor above, -1 = first basement…
height: number // floor-to-floor height in cm (default 280, includes a 20 cm slab)
walls: Wall[]
rooms: Room[]
openings: Opening[] // doors and windows
furniture: Furniture[]
}leveldecides how floors stack. The order of thefloorsarray doesn't matter. Levels don't need to be consecutive, but they must be unique.heightis floor-to-floor. In 3D, walls areheight − 20 cmtall (the slab between floors isSLAB_THICKNESS, 20 cm), and each floor sits on top of the ones below it.- All floors share the same
x/ycoordinate system, so a point at{ x: 420, y: 260 }is in the same place on every floor. That's what keeps walls and stairs lined up.
See Multi-floor buildings for how floors work in the components.
Floor vs floor finish
Floor is a storey of the building. A room's floor property (below) is something else: its floor finish, such as wood or tile.
Wall
ts
interface Wall {
id: string
start: { x: number; y: number }
end: { x: number; y: number }
thickness: number // cm, e.g. 24 for exterior, 12 for interior walls
kind?: 'wall' | 'railing' // default 'wall'
}Walls that share an end point join cleanly. The editor moves connected ends together.
A railing is a low balustrade for balconies, terraces and mezzanines. It's drawn as a thin double line in 2D and as a 1.05 m glass balustrade in 3D. Railings can't hold doors or windows. The default railing thickness is RAILING_THICKNESS (6 cm).
Room
ts
interface Room {
id: string
name: string
points: { x: number; y: number }[] // polygon outline, any shape (L, U…), clockwise or anticlockwise
color?: string // any CSS colour; defaults to the built-in palette
floor?: 'plain' | 'wood' | 'tile' | 'stone' | 'carpet' | 'concrete' | 'grass'
pool?: Pool // makes the room a swimming pool (see below)
}
interface Pool { // every field is optional
finish?: 'mosaic' | 'plaster' | 'slate' // default 'mosaic'
shallow?: number // water depth (cm) at the shallow end, default 110
deep?: number // water depth (cm) at the deep end, default 200
deepEnd?: 'left' | 'right' | 'top' | 'bottom' // default 'right'
corner?: number // corner radius (cm), default 0
raised?: boolean // default: sunk on the ground floor, raised above it
glass?: number[] // raised pools: glass edges (edge i runs from point i to i + 1)
}A pool's points are the water's edge; the stone edging is drawn around it. See Swimming pools.
Rooms are just areas: they don't need walls. That's how outdoor areas such as gardens work. Area is calculated from the polygon (polygonArea(room.points) gives cm²).
Opening (door or window)
ts
interface Opening {
id: string
wallId: string // the wall it sits in
type: 'door' | 'double-door' | 'sliding-door' | 'window'
offset: number // cm from the wall's start to the opening's centre
width: number // cm
flipX?: boolean // hinge on the other end
flipY?: boolean // swing to the other side of the wall
}If a wall gets shorter, its openings are kept inside it. Deleting a wall deletes its openings.
Furniture
ts
interface Furniture {
id: string
kind: FurnitureKind | PackFurnitureKind // 'bed' | 'sofa' | 'armchair' | 'chair' | 'table' | 'dining-table' | 'round-table' | 'desk'
// | 'nightstand' | 'wardrobe' | 'kitchen' | 'counter' | 'fridge' | 'toilet' | 'sink'
// | 'bathtub' | 'shower' | 'rug' | 'tv' | 'stairs' | 'elevator'
// plants: 'plant' (leafy plant) | 'monstera' | 'palm' | 'olive-tree' | 'grass'
// add-on packs: namespaced, e.g. 'garden/oak'
x: number // centre
y: number
width: number // size along the item's own x axis
depth: number // size along the item's own y axis; the "back" (bed headboard, sofa back) faces -y
rotation: number // degrees clockwise
}Items from add-on packs use a kind namespaced by their pack, such as garden/oak.
Example
json
{
"version": 25,
"name": "Studio",
"floors": [
{
"id": "ground",
"name": "Ground floor",
"level": 0,
"height": 280,
"walls": [
{ "id": "w1", "start": { "x": 0, "y": 0 }, "end": { "x": 500, "y": 0 }, "thickness": 24 },
{ "id": "w2", "start": { "x": 500, "y": 0 }, "end": { "x": 500, "y": 400 }, "thickness": 24 },
{ "id": "w3", "start": { "x": 500, "y": 400 }, "end": { "x": 0, "y": 400 }, "thickness": 24 },
{ "id": "w4", "start": { "x": 0, "y": 400 }, "end": { "x": 0, "y": 0 }, "thickness": 24 }
],
"rooms": [
{ "id": "r1", "name": "Studio", "floor": "wood", "points": [
{ "x": 0, "y": 0 }, { "x": 500, "y": 0 }, { "x": 500, "y": 400 }, { "x": 0, "y": 400 }
] }
],
"openings": [
{ "id": "o1", "wallId": "w3", "type": "door", "offset": 250, "width": 90 },
{ "id": "o2", "wallId": "w1", "type": "window", "offset": 250, "width": 140 }
],
"furniture": [
{ "id": "f1", "kind": "bed", "x": 380, "y": 120, "width": 160, "depth": 205, "rotation": 90 }
]
}
]
}Helper functions
The same helpers are exported by all three packages: @jbaysolutions/floorplan-vue, @jbaysolutions/floorplan-react and @jbaysolutions/floorplan-elements. Import them from the package you use. The 3D-only exports (registerPack3D, THEMES_3D, modelling) come from each package's /3d entry, for example @jbaysolutions/floorplan-react/3d.
ts
import { createEmptyPlan, normalizePlan, polygonArea } from '@jbaysolutions/floorplan-vue'
// or '@jbaysolutions/floorplan-react', or '@jbaysolutions/floorplan-elements'| Function | Description |
|---|---|
createEmptyPlan(name?) | A new, empty plan with one ground floor. |
createFloor(level?, name?) | A new, empty floor. |
normalizePlan(json) | Copies a plan, fills in anything missing and upgrades older formats. Use it when loading stored data. |
sortedFloors(plan) | Floors from the lowest level to the highest. |
defaultFloor(plan) | The ground floor (or the lowest floor above ground, or the lowest floor). |
floorElevations(plan) | Map of floor id → elevation in cm, with the ground floor at 0. |
floorLabel(level) / defaultFloorName(level) | "G", "1", "B1" / "Ground floor", "Floor 1", "Basement 1". |
markerOnFloor(marker, floor, plan) | Whether a marker belongs to a floor. |
clonePlan(plan) | Deep copy. |
uid(prefix?) | Unique id generator for new items. |
planBounds(floor) / buildingBounds(plan) | { minX, minY, maxX, maxY } of one floor / of all floors together. |
polygonArea(points) | Area in cm². formatArea(cm2) formats it as "12.4 m²". |
pointInPolygon(point, points) | Whether a point is inside a room. |
formatLength(cm) | "3.45 m". |
FURNITURE_CATALOG, FLOOR_TYPES, OPENING_TYPES | Lists of the built-in options (handy for your own UIs). |
FIXTURE_KINDS, isFixture(kind) | Kinds that are part of the building (stairs, elevators): they stay visible when furniture is hidden. |
poolSettings(pool, level?), poolOutline(points, corner), POOL_FINISHES | A pool's settings with defaults filled in; its rounded outline; the finishes. |
registerPack, packItem, registeredPacks, packCatalogs, furnitureCatalog | Add-on packs. |
polygonLabelPoint(points, holes?) | A good point inside a room for a label (keeps clear of holes, e.g. pools). |
VERSION | The installed library version. |
RAILING_THICKNESS | Default thickness of a railing (6 cm). |
PLAN_FORMAT_VERSION | The current FloorPlan JSON format version (25). |
DEFAULT_FLOOR_HEIGHT, SLAB_THICKNESS | 280 cm and 20 cm. |
The types (FloorPlan, Floor, Wall, Room, Pool, Opening, Furniture, Marker…) are exported from every package too.
Versioning of the format
The current format version is 25, also exported as PLAN_FORMAT_VERSION. When the format changes, the version number goes up, and normalizePlan upgrades older plans to the current format when they're loaded.
| Version | Change |
|---|---|
25 | Plans hold floors. A version 24 plan (walls, rooms, openings and furniture at the top level) loads as a building with one ground floor whose id is "ground". |
24 | Single-floor plans. |
The components call normalizePlan themselves, so old plans display correctly as they are. Save the plan the editor emits to store it in the new format.