npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@maciejwegrzynek/planorama

v0.0.6

Published

2D planning library for creating interactive layouts like parkings, gardens, and playgrounds using Konva.js

Readme

Planorama

A TypeScript library for creating and managing 2D plans like parkings, gardens, playgrounds, and more using Konva.js and reactive state management.

What is Planorama?

Planorama is a powerful yet simple library that lets you create interactive 2D plans for any layout - parking lots, office spaces, gardens, playgrounds, seating arrangements, and more. Built on top of Konva.js with reactive state management, it handles all the complexity of canvas rendering, item manipulation, and user interactions.

Getting Started is Simple

Creating your first 2D plan requires just a few steps:

  1. Define a background - Load your floor plan, map, or any SVG/image as the base layer
  2. Define items - Create reusable item types (parking spots, desks, trees, equipment, etc.)
  3. Build your UI - Use React, Vue, Svelte, or vanilla JavaScript to create controls
  4. Start creating - Let users place, move, rotate, and arrange items on the canvas

That's it! Planorama handles viewport navigation, item selection, transformations, and all the canvas complexity for you.

Three Interaction Modes

Planorama operates in three distinct modes to separate different user interactions:

  • Viewport Mode - Navigate and explore the canvas by panning and zooming
  • Select Mode - Select, move, rotate, and transform existing items
  • Create Mode - Place new items on the canvas

You can switch between modes programmatically using setStageMode() to control the user experience. See Use Cases for practical examples of how these modes work together in real applications.

Demo & Live Examples

Table of Contents

Installation

npm install @maciejwegrzynek/planorama

Quick Start

import { setPlanorama } from '@maciejwegrzynek/planorama';

const container = document.querySelector('#planorama-stage')!;

const itemsConfig = [
  {
    name: 'parking-spot',
    width: 90,
    height: 180,
    src: 'assets/spot.svg',
    scale: { x: 1, y: 1 },
  },
];

const { setStageMode, setCreatorCurrentItem } = setPlanorama({
  stageContainer: container,
  itemsConfig,
  backgroundConfig: {
    src: 'assets/background.svg',
    scale: 1.9,
  },
  onViewportChange: (data) => {},
  onItemMouseClick: (item) => {
    console.log('Item clicked:', item);
  },
});

// Set mode to create items
setStageMode('create');

// Configure an item to create
setCreatorCurrentItem(itemsConfig[0]);

Use Cases

Planorama is designed to handle various 2D planning scenarios where administrators need to create layouts and users need to interact with specific areas or objects. Here are some practical applications:

Parking Management System

Admin Role:

  • Create a new parking facility by uploading a floor plan or satellite image as the background
  • Define parking spots by placing spot items on the canvas
  • Add additional elements like entrances, exits, payment kiosks, EV charging stations, and disabled parking zones
  • Configure spot properties (size, type, restrictions, pricing)
  • Organize spots using alignment tools for consistent spacing

User Role:

  • Browse the interactive parking map
  • View real-time availability of parking spots
  • Click to select and book a specific parking spot
  • See spot details (dimensions, features, distance to entrance)
  • Receive visual confirmation of their reserved spot

Office Space Planning

Admin Role:

  • Upload office floor plans as the background layer
  • Place and arrange desks, meeting rooms, and collaborative spaces
  • Add facilities like printers, coffee stations, emergency exits, and restrooms
  • Define hot-desking zones vs. assigned workstations
  • Configure room capacities and equipment lists

User Role:

  • View the office layout and available desks
  • Book a desk for specific dates (hot-desking)
  • See which colleagues are seated nearby
  • Find meeting rooms and check their availability
  • Navigate to facilities like printers or break rooms

Event Venue & Seating Management

Admin Role:

  • Import venue layout (theater, conference hall, stadium)
  • Create seating sections with different categories (VIP, standard, accessible)
  • Place tables for banquet-style events
  • Define stage, entrance, exit, and emergency assembly points
  • Set pricing tiers for different seating areas

User Role:

  • Browse available seats with interactive venue map
  • Select specific seats based on view preferences
  • Book multiple adjacent seats for groups
  • View seat details (row, number, view angle, accessibility)
  • Receive seat assignment confirmation with visual reference

Garden & Landscape Design

Admin Role:

  • Load property survey or satellite imagery as background
  • Place various plant types, trees, and shrubs on the layout
  • Add hardscape elements like paths, patios, water features, and fences
  • Organize plants in rows or circular patterns using alignment tools
  • Annotate with planting zones and maintenance areas

User Role:

  • Visualize the proposed garden design
  • Select specific plants to view details (species, care requirements, mature size)
  • Provide feedback by highlighting areas for changes
  • Track planting progress by marking completed sections
  • Export the final design for implementation

Playground & Recreation Area Planning

Admin Role:

  • Create playground layout with equipment placement
  • Define play zones (toddler area, climbing structures, swings, slides)
  • Add safety elements like soft surfaces, fencing, and benches
  • Mark shaded areas and water fountains
  • Set age-appropriate zones with safety clearances

User Role:

  • Explore playground features interactively
  • View equipment specifications and age recommendations
  • Report maintenance issues by clicking on specific equipment
  • Plan visits based on available facilities
  • Access accessibility information for inclusive play

Warehouse & Inventory Management

Admin Role:

  • Upload warehouse floor plan
  • Define storage zones, shelving units, and pallet positions
  • Create aisles, loading docks, and packing stations
  • Label zones by product category or temperature requirements
  • Configure storage capacity and weight limits

User Role:

  • Locate specific inventory items on the warehouse map
  • View stock levels by clicking on storage locations
  • Plan picking routes for order fulfillment
  • Update inventory status after stock movements
  • Identify available storage space for new shipments

API Reference

Initialization

setPlanorama(config: PlanoramaConfig): Planorama

Initializes and returns the Planorama instance with all API methods.

Parameters:

  • config (PlanoramaConfig): Configuration object containing the stage container and optional callbacks

Returns: Planorama - Instance with all API methods

Example:

const itemsConfig = [
  {
    name: 'parking-spot',
    width: 90,
    height: 180,
    src: 'assets/spot.svg',
    scale: { x: 1, y: 1 },
  },
];

const planorama = setPlanorama({
  stageContainer: document.querySelector('#planorama-stage')!,
  itemsConfig,
  backgroundConfig: {
    src: 'assets/background.svg',
    scale: 1.9,
  },
  debug: false, // Optional: Enable debug logging
  onViewportChange: (data) => console.log('Viewport:', data),
  onItemMouseClick: (item) => console.log('Clicked:', item),
});

// Or destructure methods you need
const { setStageMode, setCreatorCurrentItem } = planorama;

Stage Management

setStageScale

setStageScale(scale: Vector2d): void

Sets the zoom level of the stage.

Parameters:

  • scale (Vector2d): Object with x and y scale values (1 = 100%, 0.5 = 50%, 2 = 200%)

Example:

const { setStageScale } = planorama;

// Zoom to 150%
setStageScale({ x: 1.5, y: 1.5 });

// Zoom out to 50%
setStageScale({ x: 0.5, y: 0.5 });

setStagePosition

setStagePosition(position: Vector2d): void

Centers the stage on a specific position.

Parameters:

  • position (Vector2d): Target position coordinates

Example:

const { setStagePosition } = planorama;

setStagePosition({ x: 500, y: 300 });

centerStageOnObjectById

centerStageOnObjectById(id: string): void

Centers the viewport on a specific item.

Parameters:

  • id (string): Unique identifier of the item (e.g., 'item-abc123')

Example:

const { centerStageOnObjectById } = planorama;

centerStageOnObjectById('item-parking-spot-1');

centerOnItems

centerOnItems(duration?: number): void

Centers the viewport on all items and automatically calculates the optimal zoom level to fit them on screen with padding.

Parameters:

  • duration (number, optional): Animation duration in seconds. If omitted, defaults to 0.3s

Example:

const { centerOnItems } = planorama;

// After adding multiple items, fit them all on screen
centerOnItems();

// With custom animation duration (0.5 seconds)
centerOnItems(0.5);

// Instant fit without animation
centerOnItems(0);

Notes:

  • Automatically calculates the bounding box of all items
  • Adjusts zoom level to fit all items with 50px padding
  • Animates the transition smoothly (default: 0.3s duration)
  • Will not zoom in beyond 1:1 scale
  • If no items exist, a warning is logged (in debug mode)

setStageMode

setStageMode(mode: StageMode): void

Changes the interaction mode of the stage.

Parameters:

  • mode (StageMode): One of: 'viewport', 'create', or 'select'
    • 'viewport': Navigate and pan the stage
    • 'create': Create new items by clicking
    • 'select': Select and manipulate existing items

Example:

const { setStageMode } = planorama;

// Switch to selection mode
setStageMode('select');

// Switch to creation mode
setStageMode('create');

// Switch to viewport navigation mode
setStageMode('viewport');

Item Alignment

setXAlignment

setXAlignment(gap?: number): void

Aligns selected items horizontally with equal spacing.

Parameters:

  • gap (number, optional): Space between items in pixels. If omitted, uses the value from setGap

Example:

const { setXAlignment } = planorama;

// Align with default gap
setXAlignment();

// Align with custom 20px gap
setXAlignment(20);

setYAlignment

setYAlignment(gap?: number): void

Aligns selected items vertically with equal spacing.

Parameters:

  • gap (number, optional): Space between items in pixels. If omitted, uses the value from setGap

Example:

const { setYAlignment } = planorama;

// Align with default gap
setYAlignment();

// Align with custom 15px gap
setYAlignment(15);

setAlignmentInCols

setAlignmentInCols(cols?: number, gap?: number): void

Aligns selected items in a grid layout with specified columns and spacing.

Parameters:

  • cols (number, optional): Number of columns. If omitted, uses the value from setColumns
  • gap (number, optional): Space between items in pixels. If omitted, uses the value from setGap

Example:

const { setAlignmentInCols } = planorama;

// Align in 3 columns with default gap
setAlignmentInCols();

// Align in 4 columns with 10px gap
setAlignmentInCols(4, 10);

// Align in 5 columns with default gap
setAlignmentInCols(5);

spreadItemsByCircle

spreadItemsByCircle(spreadOpts?: SpreadByOpts): void

Arranges selected items in a circular pattern.

Parameters:

Example:

const { spreadItemsByCircle } = planorama;

// Spread with default options
spreadItemsByCircle();

// Spread in a circle with 300px radius and outward rotation
spreadItemsByCircle({
  radius: 300,
  withRotation: 'outside',
});

// Spread in a circle without rotation
spreadItemsByCircle({
  radius: 500,
  withRotation: null,
});

Item Creation

setCreatorCurrentItem

setCreatorCurrentItem(config: ItemConfig): void

Sets the item template for creation mode. After setting, items can be created by clicking on the stage when in 'create' mode.

Parameters:

  • config (ItemConfig): Configuration for the item to create

Example:

const { setCreatorCurrentItem, setStageMode } = planorama;

setCreatorCurrentItem({
  name: 'parking-spot',
  width: 90,
  height: 180,
  src: 'assets/parking.svg',
  scale: { x: 1.875, y: 1.875 },
  label: {
    fontSize: 20,
    fontFamily: 'Helvetica',
    fillColor: '#666',
  },
  background: {
    backgroundColor: 'rgba(196, 183, 203, 0.5)',
    strokeColor: 'rgba(196, 183, 203, 0.8)',
    strokeWidth: 1.5,
  },
});

// Switch to create mode to start placing items
setStageMode('create');

Item Manipulation

setRotation

setRotation(rotationAngle?: number): void

Rotates selected items by the specified angle.

Parameters:

  • rotationAngle (number, optional): Rotation angle in degrees. If omitted, uses the value from setRotationAngle

Example:

const { setRotation } = planorama;

// Rotate by stored angle
setRotation();

// Rotate by 45 degrees
setRotation(45);

// Rotate by -90 degrees (counter-clockwise)
setRotation(-90);

cloneSelectedItems

cloneSelectedItems(): void

Duplicates all currently selected items. The cloned items appear slightly offset from the originals.

Example:

const { cloneSelectedItems } = planorama;

// Select items first, then clone them
cloneSelectedItems();

deleteSelectedItems

deleteSelectedItems(): void

Removes all currently selected items from the stage.

Example:

const { deleteSelectedItems } = planorama;

// Select items first, then delete them
deleteSelectedItems();

discardSelection

discardSelection(): void

Clears the current selection without modifying any items.

Example:

const { discardSelection } = planorama;

discardSelection();

selectItemsById

selectItemsById(ids: string[] | string): void

Programmatically selects items by their IDs.

Parameters:

  • ids (string[] | string): Single item ID or array of item IDs to select

Example:

const { selectItemsById } = planorama;

// Select a single item
selectItemsById('item-abc123');

// Select multiple items
selectItemsById(['item-abc123', 'item-def456', 'item-ghi789']);

updateItemById

updateItemById(itemId: string, updates: ItemUpdatePayload): void

Updates properties of an existing item by its ID.

Parameters:

  • itemId (string): Unique identifier of the item to update
  • updates (ItemUpdatePayload): Object containing properties to update

Example:

const { updateItemById } = planorama;

updateItemById('item-abc123', {
  background: {
    backgroundColor: 'rgba(255, 0, 0, 0.5)',
    strokeColor: 'rgba(255, 0, 0, 0.8)',
    strokeWidth: 2,
  },
  label: {
    text: 'A-101',
    fontSize: 24,
    fillColor: '#000000',
  },
});

See also: UPDATE_ITEM_EXAMPLE.md for detailed documentation


exportAllItems

exportAllItems(callback: (items: PlanoramaItem[]) => void): void

Exports all items from the stage as an array of PlanoramaItem objects. This is useful for saving, analyzing, or processing all items at once.

Parameters:

  • callback (function): Callback function that receives the array of items

Example:

const { exportAllItems } = planorama;

// Export all items and log them
exportAllItems((items) => {
  console.log('Total items:', items.length);
  console.log('Items data:', items);
});

// Save items to JSON
exportAllItems((items) => {
  const json = JSON.stringify(items, null, 2);
  localStorage.setItem('planorama-items', json);
  console.log('Items saved');
});

// Get all item IDs
exportAllItems((items) => {
  const ids = items.map((item) => item.id);
  console.log('Item IDs:', ids);
});

// Filter items by type
exportAllItems((items) => {
  const parkingSpots = items.filter((item) => item.type === 'parking-spot');
  const computers = items.filter((item) => item.type === 'computer-item');

  console.log(`Parking spots: ${parkingSpots.length}`);
  console.log(`Computers: ${computers.length}`);
});

// Export items with specific properties
exportAllItems((items) => {
  const exportData = items.map((item) => ({
    id: item.id,
    type: item.type,
    position: { x: item.transform.x, y: item.transform.y },
    rotation: item.transform.rotation,
    label: item.itemProps.label?.text,
  }));

  downloadJSON(exportData, 'planorama-export.json');
});

See also: PlanoramaItem type definition


importItems

importItems(items: PlanoramaItem[]): void

Imports items into the stage. This method checks if items already exist by ID and either updates them or creates new ones. Useful for loading saved data or restoring a previous state.

Parameters:

Example:

const { importItems } = planorama;

// Load items from localStorage
const savedData = localStorage.getItem('planorama-items');
if (savedData) {
  const items = JSON.parse(savedData);
  importItems(items);
}

// Import items from an API
fetch('/api/planorama-data')
  .then((res) => res.json())
  .then((items) => importItems(items));

// Restore a specific state
const savedState = [
  {
    id: 'item-1',
    type: 'parking-spot',
    transform: { x: 100, y: 200, rotation: 0 },
    itemProps: {
      label: { text: 'A-1' },
    },
  },
];
importItems(savedState);

Notes:

  • If an item with the same ID already exists, it will be updated with the provided properties
  • If an item doesn't exist, it will be created at the specified position and rotation
  • The item type must match one of the itemsConfig entries provided during initialization

Configuration

setGap

setGap(gap: number): void

Sets the default gap (spacing) between items for alignment operations.

Parameters:

  • gap (number): Gap size in pixels

Example:

const { setGap } = planorama;

setGap(15);

setColumns

setColumns(cols: number): void

Sets the default number of columns for grid alignment operations.

Parameters:

  • cols (number): Number of columns (minimum: 1)

Example:

const { setColumns } = planorama;

setColumns(4);

setRotationAngle

setRotationAngle(angle: number): void

Sets the default rotation angle for rotation operations.

Parameters:

  • angle (number): Rotation angle in degrees

Example:

const { setRotationAngle } = planorama;

// Set default rotation to 90 degrees
setRotationAngle(90);

// Set default rotation to 45 degrees
setRotationAngle(45);

setSpreadByOpts

setSpreadByOpts(opts: SpreadByOpts): void

Sets the default options for circular spread operations.

Parameters:

Example:

const { setSpreadByOpts } = planorama;

setSpreadByOpts({
  radius: 400,
  withRotation: 'outside',
});

Callbacks

All callbacks are optional and can be provided in the initialization config.

onViewportChange

onViewportChange?: (data: { scale: Vector2d; position: Vector2d }) => void

Called when the viewport scale or position changes.

Callback Data:

  • scale (Vector2d): Current zoom level
  • position (Vector2d): Current viewport position

Example:

const planorama = setPlanorama({
  stageContainer: container,
  onViewportChange: (data) => {
    console.log('Scale:', data.scale);
    console.log('Position:', data.position);
  },
});

onViewModeChange

onViewModeChange?: (mode: StageMode) => void

Called when the stage mode changes.

Callback Data:

  • mode (StageMode): The new mode ('viewport', 'create', or 'select')

Example:

const planorama = setPlanorama({
  stageContainer: container,
  onViewModeChange: (mode) => {
    console.log('Mode changed to:', mode);
    // Update UI to reflect current mode
  },
});

onItemMouseOver

onItemMouseOver?: (item: PlanoramaItem) => void

Called when the mouse hovers over an item.

Callback Data:

Example:

const planorama = setPlanorama({
  stageContainer: container,
  onItemMouseOver: (item) => {
    console.log('Hovering over:', item.id, item.type);
    // Show tooltip
    tooltip.textContent = item.id;
    tooltip.style.left = item.itemCenter.x + 'px';
    tooltip.style.top = item.itemCenter.y + 'px';
    tooltip.classList.add('visible');
  },
});

onItemMouseOut

onItemMouseOut?: (item: PlanoramaItem) => void

Called when the mouse leaves an item.

Callback Data:

Example:

const planorama = setPlanorama({
  stageContainer: container,
  onItemMouseOut: (item) => {
    console.log('Mouse left:', item.id);
    // Hide tooltip
    tooltip.classList.remove('visible');
  },
});

onItemMouseClick

onItemMouseClick?: (item: PlanoramaItem) => void

Called when an item is clicked.

Callback Data:

  • item (PlanoramaItem): Object containing item details including id, type, itemProps, boundingBox, etc.

Example:

const { centerStageOnObjectById } = setPlanorama({
  stageContainer: container,
  itemsConfig,
  onItemMouseClick: (item) => {
    console.log('Clicked item:', item.id);
    // Show item properties dialog
    showDialog(item);

    // Center on this item
    centerStageOnObjectById(item.id);
  },
});

onItemsSelected

onItemsSelected?: (items: Group[]) => void

Called when items are selected or selection changes.

Callback Data:

  • items (Group[]): Array of Konva Group objects representing selected items

Example:

const planorama = setPlanorama({
  stageContainer: container,
  onItemsSelected: (items) => {
    console.log(`${items.length} items selected`);
    // Update UI to show selection tools
    if (items.length > 0) {
      selectionTools.classList.add('visible');
    } else {
      selectionTools.classList.remove('visible');
    }
  },
});

onCreatorStart

onCreatorStart?: (data: any) => void

Called when item creation starts (mouse down in create mode).

Callback Data:

  • data: Object containing creation start information

Example:

const planorama = setPlanorama({
  stageContainer: container,
  onCreatorStart: (data) => {
    console.log('Started creating items');
    creatorIndicator.classList.add('visible');
  },
});

onCreatorMove

onCreatorMove?: (data: any) => void

Called continuously as the mouse moves during item creation.

Callback Data:

  • data: Object containing:
    • count: Number of items being created
    • rotation: Current rotation angle
    • centerRight: Position for UI indicators

Example:

const planorama = setPlanorama({
  stageContainer: container,
  onCreatorMove: (data) => {
    // Update creation indicator
    indicator.style.left = data.centerRight.x + 'px';
    indicator.style.top = data.centerRight.y + 'px';
    indicator.textContent = `Items: ${data.count}, Rotation: ${data.rotation}°`;
  },
});

onCreatorEnd

onCreatorEnd?: (data: any) => void

Called when item creation completes (mouse up in create mode).

Callback Data:

  • data: Object containing creation completion information

Example:

const planorama = setPlanorama({
  stageContainer: container,
  onCreatorEnd: (data) => {
    console.log('Finished creating items');
    creatorIndicator.classList.remove('visible');
  },
});

onTransformChange

onTransformChange?: (data: TransformData) => void

Called continuously during transformation (rotation, scaling, moving) of selected items.

Callback Data:

  • data: Object containing:
    • rotation: Current rotation angle
    • rectPoints: Object with corner positions (topLeft, topRight, bottomLeft, bottomRight)

Example:

const planorama = setPlanorama({
  stageContainer: container,
  onTransformChange: (data) => {
    // Show rotation indicator
    rotationIndicator.style.left = data.rectPoints.topRight.x + 'px';
    rotationIndicator.style.top = data.rectPoints.topRight.y + 'px';
    rotationIndicator.textContent = `${data.rotation.toFixed(1)}°`;
  },
});

onTransformEnd

onTransformEnd?: (data: TransformData) => void

Called when transformation completes.

Callback Data:

  • data: Object containing final transformation state

Example:

const planorama = setPlanorama({
  stageContainer: container,
  onTransformEnd: (data) => {
    console.log('Transform ended at rotation:', data.rotation);
    rotationIndicator.classList.remove('visible');
  },
});

onTransformStart

onTransformStart?: (data: TransformData) => void

Called when transformation begins.

Callback Data:

  • data: Object containing initial transformation state

Example:

const planorama = setPlanorama({
  stageContainer: container,
  onTransformStart: (data) => {
    console.log('Transform started');
    rotationIndicator.classList.add('visible');
  },
});

Type Definitions

Vector2d

Represents a 2D coordinate or scale.

interface Vector2d {
  x: number;
  y: number;
}

Example:

const position: Vector2d = { x: 100, y: 200 };
const scale: Vector2d = { x: 1.5, y: 1.5 };

StageMode

Defines the interaction mode of the stage.

type StageMode = 'viewport' | 'create' | 'select';
  • 'viewport': Pan and navigate the stage
  • 'create': Click to create items
  • 'select': Select and manipulate items

ItemConfig

Configuration for creating an item.

interface ItemConfig {
  name: string; // Unique identifier for item type
  width: number; // Item width in pixels
  height: number; // Item height in pixels
  src: string; // Path to SVG or image file
  scale: { x: number; y: number }; // Scale multipliers
  label?: ItemLabelConfig; // Optional label configuration
  background?: ItemBackgroundColorConfig; // Optional background styling
}

Example:

const itemConfig: ItemConfig = {
  name: 'parking-spot',
  width: 90,
  height: 180,
  src: 'assets/parking.svg',
  scale: { x: 1, y: 1 },
  label: {
    fontSize: 20,
    fontFamily: 'Arial',
    fillColor: '#333',
  },
};

BackgroundConfig

Configuration for the stage background image.

interface BackgroundConfig {
  src: string; // Path to SVG or image file
  scale: number; // Scale factor for the background
  offset?: Vector2d; // Optional: Manual offset from center {x, y} in pixels
}

Example:

const backgroundConfig: BackgroundConfig = {
  src: 'assets/floor-plan.svg',
  scale: 2.0,
};

// With custom offset
const backgroundConfigWithOffset: BackgroundConfig = {
  src: 'assets/floor-plan.svg',
  scale: 1.5,
  offset: { x: 100, y: -50 }, // Move 100px right, 50px up from center
};

Note: The background image is automatically centered on the stage. The scale factor allows you to adjust the size of the background relative to the stage dimensions. Use the optional offset property to fine-tune the position by shifting the background from the center point.


ItemUpdatePayload

Properties that can be updated on an existing item.

interface ItemUpdatePayload {
  id?: string;
  background?: {
    backgroundColor?: string; // RGBA or hex color
    strokeColor?: string; // RGBA or hex color
    strokeWidth?: number; // Border width
  };
  label?: {
    text?: string; // Label text
    fontSize?: number; // Font size in pixels
    fontFamily?: string; // Font family
    fillColor?: string; // Text color
    verticalAlignment?: number; // 0-100 (percent from top)
    horizontalAlignment?: number; // 0-100 (percent from left)
  };
}

Example:

const updates: ItemUpdatePayload = {
  background: {
    backgroundColor: 'rgba(255, 0, 0, 0.3)',
    strokeWidth: 2,
  },
  label: {
    text: 'Updated',
    fontSize: 24,
  },
};

SpreadByOpts

Options for circular spread arrangement.

interface SpreadByOpts {
  withRotation: 'outside' | 'inside' | null;
  radius: number;
}
  • withRotation:
    • 'outside': Rotate items to face outward from center
    • 'inside': Rotate items to face inward to center
    • null: No rotation
  • radius: Circle radius in pixels

Example:

const spreadOpts: SpreadByOpts = {
  withRotation: 'outside',
  radius: 400,
};

PlanoramaItem

Data structure passed to item callbacks.

interface PlanoramaItem {
  id: string; // Unique item ID
  type: string; // Item type (from ItemConfig.name)
  itemProps: ItemUpdatePayload; // Current item properties
  boundingBox: IRect; // Bounding box dimensions
  pos: Vector2d; // Position
  scale: number; // Scale factor
  itemCenter: Vector2d; // Center point coordinates
  transform: {
    x: number;
    y: number;
    rotation: number;
  };
}

PlanoramaConfig

Configuration object for initializing Planorama.

interface PlanoramaConfig {
  stageContainer: HTMLDivElement;
  itemsConfig: ItemConfig[]; // Required: Array of item configurations
  backgroundConfig?: BackgroundConfig; // Optional: Background image configuration
  debug?: boolean; // Optional: Enable debug logging (default: false)
  onViewportChange?: (data: { scale: Vector2d; position: Vector2d }) => void;
  onViewModeChange?: (mode: StageMode) => void;
  onItemMouseOver?: (item: PlanoramaItem) => void;
  onItemMouseOut?: (item: PlanoramaItem) => void;
  onItemMouseClick?: (item: PlanoramaItem) => void;
  onItemsSelected?: (items: any[]) => void;
  onCreatorStart?: (data: any) => void;
  onCreatorMove?: (data: any) => void;
  onCreatorEnd?: (data: any) => void;
  onTransformChange?: (data: any) => void;
  onTransformEnd?: (data: any) => void;
  onTransformStart?: (data: any) => void;
}

Planorama

Return type of setPlanorama() containing all API methods.

interface Planorama {
  stage: Stage;
  setStageScale: (scale: Vector2d) => void;
  setStagePosition: (position: Vector2d) => void;
  centerStageOnObjectById: (id: string) => void;
  centerOnItems: (duration?: number) => void;
  setStageMode: (mode: StageMode) => void;
  setXAlignment: (gap?: number) => void;
  setYAlignment: (gap?: number) => void;
  setAlignmentInCols: (cols?: number, gap?: number) => void;
  spreadItemsByCircle: (spreadOpts?: SpreadByOpts) => void;
  setSpreadByOpts: (opts: SpreadByOpts) => void;
  setCreatorCurrentItem: (config: ItemConfig) => void;
  setRotation: (rotationAngle?: number) => void;
  setRotationAngle: (angle: number) => void;
  setGap: (gap: number) => void;
  setColumns: (cols: number) => void;
  discardSelection: () => void;
  deleteSelectedItems: () => void;
  cloneSelectedItems: () => void;
  updateItemById: (itemId: string, updates: ItemUpdatePayload) => void;
  selectItemsById: (ids: string[] | string) => void;
  exportAllItems: (callback: (items: PlanoramaItem[]) => void) => void;
  importItems: (items: PlanoramaItem[]) => void;
}

Examples

Complete Setup

import { setPlanorama } from '@maciejwegrzynek/planorama';

const container = document.querySelector('#planorama-stage')!;

const itemsConfig = [
  {
    name: 'parking-spot',
    width: 90,
    height: 180,
    src: 'assets/parking.svg',
    scale: { x: 1.875, y: 1.875 },
  },
  {
    name: 'computer-item',
    width: 80,
    height: 80,
    src: 'assets/computer.svg',
    scale: { x: 0.1, y: 0.1 },
  },
];

const { setGap, setColumns, setRotationAngle } = setPlanorama({
  stageContainer: container,
  itemsConfig,
  backgroundConfig: {
    src: 'assets/floor-plan.svg',
    scale: 1.5,
  },
  debug: false,

  onViewportChange: (data) => {
    console.log('Viewport changed:', data.scale, data.position);
  },

  onViewModeChange: (mode) => {
    console.log('Mode:', mode);
    updateModeUI(mode);
  },

  onItemMouseClick: (item) => {
    console.log('Clicked:', item.id);
    showItemDialog(item);
  },

  onItemsSelected: (items) => {
    console.log(`${items.length} items selected`);
    toggleSelectionTools(items.length > 0);
  },

  onTransformChange: (data) => {
    showRotationIndicator(data.rotation, data.rectPoints.topRight);
  },
});

// Set up initial configuration
setGap(10);
setColumns(3);
setRotationAngle(90);

Creating Items

const { setStageMode, setCreatorCurrentItem } = planorama;

// Define item template
const parkingSpot = {
  name: 'parking-spot',
  width: 90,
  height: 180,
  src: 'assets/parking.svg',
  scale: { x: 1.875, y: 1.875 },
  label: {
    fontSize: 20,
    fontFamily: 'Helvetica',
    fillColor: '#666',
  },
  background: {
    backgroundColor: 'rgba(196, 183, 203, 0.5)',
    strokeColor: 'rgba(196, 183, 203, 0.8)',
    strokeWidth: 1.5,
  },
};

// Switch to create mode
setStageMode('create');
setCreatorCurrentItem(parkingSpot);

// Now click on the stage to create items

Aligning Items

const {
  setStageMode,
  setXAlignment,
  setYAlignment,
  setAlignmentInCols,
  spreadItemsByCircle,
} = planorama;

// Switch to select mode
setStageMode('select');

// Select items by dragging or clicking

// Align horizontally with 15px gap
setXAlignment(15);

// Align vertically with 20px gap
setYAlignment(20);

// Align in 4 columns with 10px gap
setAlignmentInCols(4, 10);

// Spread in a circle
spreadItemsByCircle({
  radius: 500,
  withRotation: 'outside',
});

Rotating Items

const { setRotationAngle, setRotation } = planorama;

// Set default rotation
setRotationAngle(45);

// Rotate selected items by default angle
setRotation();

// Or rotate by specific angle
setRotation(90);

Updating Items

const { updateItemById } = planorama;

// Update item by ID
updateItemById('item-abc123', {
  background: {
    backgroundColor: '#ffff00',
    strokeColor: '#ff0000',
    strokeWidth: 3,
  },
  label: {
    text: 'Spot A-1',
    fontSize: 22,
    fillColor: '#000',
  },
});

Exporting Items

const { exportAllItems } = planorama;

// Export all items to console
exportAllItems((items) => {
  console.log('Total items:', items.length);
  items.forEach((item) => {
    console.log(`${item.type} (${item.id}):`, item.transform);
  });
});

// Save items to local storage
exportAllItems((items) => {
  const json = JSON.stringify(items, null, 2);
  localStorage.setItem('my-planorama', json);
});

// Download items as JSON file
exportAllItems((items) => {
  const exportData = items.map((item) => ({
    id: item.id,
    type: item.type,
    position: { x: item.transform.x, y: item.transform.y },
    rotation: item.transform.rotation,
    label: item.itemProps.label?.text,
  }));

  const blob = new Blob([JSON.stringify(exportData, null, 2)], {
    type: 'application/json',
  });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'planorama-export.json';
  a.click();
  URL.revokeObjectURL(url);
});

// Generate report by item type
exportAllItems((items) => {
  const report = items.reduce((acc, item) => {
    acc[item.type] = (acc[item.type] || 0) + 1;
    return acc;
  }, {} as Record<string, number>);

  console.log('Items by type:', report);
});

Fitting All Items on Screen

const { centerOnItems, exportAllItems } = planorama;

// After loading or importing items, fit them all on screen
exportAllItems((items) => {
  console.log(`Loaded ${items.length} items`);
  // Automatically zoom and pan to fit all items
  centerOnItems();
});

// Add a "Fit All" button to your UI
fitAllButton.addEventListener('click', () => {
  centerOnItems();
});

// Reset view after bulk operations
const { setAlignmentInCols, centerOnItems } = planorama;

// Align items in a grid
setAlignmentInCols(5, 20);

// Then fit them on screen
setTimeout(() => {
  centerOnItems();
}, 100);

Keyboard Shortcuts

const { discardSelection, deleteSelectedItems, cloneSelectedItems } = planorama;

container.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') {
    discardSelection();
  }

  if (e.key === 'Delete' || e.key === 'Backspace') {
    deleteSelectedItems();
  }

  if (e.ctrlKey && e.key === 'd') {
    e.preventDefault();
    cloneSelectedItems();
  }
});

License

MIT