Skip to content

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.
  • x grows to the right and y grows 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[]
}
  • level decides how floors stack. The order of the floors array doesn't matter. Levels don't need to be consecutive, but they must be unique.
  • height is floor-to-floor. In 3D, walls are height − 20 cm tall (the slab between floors is SLAB_THICKNESS, 20 cm), and each floor sits on top of the ones below it.
  • All floors share the same x / y coordinate 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'
FunctionDescription
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_TYPESLists 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_FINISHESA pool's settings with defaults filled in; its rounded outline; the finishes.
registerPack, packItem, registeredPacks, packCatalogs, furnitureCatalogAdd-on packs.
polygonLabelPoint(points, holes?)A good point inside a room for a label (keeps clear of holes, e.g. pools).
VERSIONThe installed library version.
RAILING_THICKNESSDefault thickness of a railing (6 cm).
PLAN_FORMAT_VERSIONThe current FloorPlan JSON format version (25).
DEFAULT_FLOOR_HEIGHT, SLAB_THICKNESS280 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.

VersionChange
25Plans 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".
24Single-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.

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