Skip to content

matanfield/bentogreed

Repository files navigation

Bento Grid

A lightweight, framework-agnostic library for generating bento grid layouts. Transform an array of tile areas into optimized rectangular layouts using proven treemap algorithms.

Features

  • 🎯 Simple API - Just provide tile areas and canvas dimensions
  • 🔄 Two Algorithms - Choose between squarified (optimized) or binary (deterministic) layouts
  • 📦 Framework Agnostic - Pure TypeScript, works with any framework or vanilla JS
  • 🎨 Flexible - Configurable padding, gutters, and layout strategies
  • 📐 Precise - Returns exact pixel coordinates for each tile

Installation

npm install bentogrid

Quick Start

import { computeBentoLayout, BentoGridConfig } from 'bentogrid';

// Define your configuration
const config: BentoGridConfig = {
  canvas: {
    width: 1024,
    height: 640,
    padding: 16, // Optional, defaults to 0
  },
  tiles: [3, 1, 2, 1, 2.5, 1.2, 7.5, 2.34, 1], // Array of tile areas/weights
  options: {
    strategy: 'squarified', // 'squarified' or 'binary'
    gutter: 8, // Optional, spacing between tiles
    order: 'descending', // Optional, 'descending' (default) or 'input'
  },
};

// Compute the layout
const layout = computeBentoLayout(config);

// Use the layout
layout.tiles.forEach((tile) => {
  console.log(`Tile ${tile.id}:`, tile.rect);
  // { x: 16, y: 16, width: 320, height: 200 }
});

Usage Examples

Basic Usage

import { computeBentoLayout } from 'bentogrid';

const layout = computeBentoLayout({
  canvas: { width: 800, height: 600 },
  tiles: [1, 2, 3, 4],
});

// Access the computed rectangles
layout.tiles.forEach((tile) => {
  const { x, y, width, height } = tile.rect;
  // Render your tile at these coordinates
});

With React

import { computeBentoLayout, BentoGridConfig } from 'bentogrid';
import { useMemo } from 'react';

function BentoGrid({ config }: { config: BentoGridConfig }) {
  const layout = useMemo(() => computeBentoLayout(config), [config]);

  return (
    <div style={{ position: 'relative', width: config.canvas.width, height: config.canvas.height }}>
      {layout.tiles.map((tile) => (
        <div
          key={tile.id}
          style={{
            position: 'absolute',
            left: tile.rect.x,
            top: tile.rect.y,
            width: tile.rect.width,
            height: tile.rect.height,
            border: '1px solid #ccc',
          }}
        >
          Tile {tile.id}
        </div>
      ))}
    </div>
  );
}

With Canvas/SVG

const layout = computeBentoLayout({
  canvas: { width: 1200, height: 800, padding: 20 },
  tiles: [3, 1, 2, 1, 2.5],
  options: { strategy: 'squarified', gutter: 10 },
});

// SVG example
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('width', '1200');
svg.setAttribute('height', '800');

layout.tiles.forEach((tile) => {
  const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
  rect.setAttribute('x', tile.rect.x.toString());
  rect.setAttribute('y', tile.rect.y.toString());
  rect.setAttribute('width', tile.rect.width.toString());
  rect.setAttribute('height', tile.rect.height.toString());
  svg.appendChild(rect);
});

API Reference

computeBentoLayout(config: BentoGridConfig): BentoGridLayout

Computes a bento grid layout from the provided configuration.

Configuration (BentoGridConfig)

{
  canvas: {
    width: number;        // Canvas width in pixels
    height: number;       // Canvas height in pixels
    padding?: number;     // Optional padding around the grid (default: 0)
  };
  tiles: number[];        // Array of tile areas/weights (proportional)
  options?: {
    strategy?: 'squarified' | 'binary';  // Layout algorithm (default: 'squarified')
    gutter?: number;                     // Spacing between tiles (default: 0)
    order?: 'descending' | 'input';      // Sort tiles by area or keep original order (default: 'descending')
  };
}

Return Value (BentoGridLayout)

{
  rect: {
    x: number;      // X position of the grid area
    y: number;      // Y position of the grid area
    width: number;  // Width of the grid area
    height: number; // Height of the grid area
  };
  tiles: Array<{
    id: string;     // Unique tile identifier (e.g., "tile-0", "tile-1")
    area: number;   // Original area value
    rect: {
      x: number;    // X position in pixels
      y: number;    // Y position in pixels
      width: number;// Width in pixels
      height: number;// Height in pixels
    };
  }>;
}

Layout Strategies

Squarified (Default)

Optimizes for square-like rectangles, minimizing aspect ratios. Best for visual appeal and readability.

{ strategy: 'squarified' }

Binary

Uses recursive binary splitting. More deterministic and predictable, but may produce less square-like tiles.

{ strategy: 'binary' }

How Tile Areas Work

The tiles array contains proportional areas. The library automatically normalizes them to fill the available canvas space:

  • [1, 1, 1] → Three equal-sized tiles
  • [3, 1, 2] → First tile is 3x larger than the second, 1.5x larger than the third
  • [1, 4, 7.5, 2.34] → Any positive numbers work as proportions

Tiles are sorted by area (largest first) before layout to mimic treemap behavior. If you want to preserve your input order—for example, to create a custom storytelling layout—set options.order = 'input'.

Development

# Install dependencies
npm install

# Run demo
npm run dev

# Build library
npm run build

Architecture

  • Core engine (src/layout.ts): Pure TypeScript layout computation
  • Algorithms (src/algorithms/): Swappable layout strategies (squarified, binary)
  • Types (src/types.ts): TypeScript definitions
  • Demo (src/demo/): React visualization app

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors