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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@thi.ng/layout

v4.0.15

Published

Configurable nested 2D grid layout generators

Readme

@thi.ng/layout

npm version npm downloads Mastodon Follow

[!NOTE] This is one of 211 standalone projects, maintained as part of the @thi.ng/umbrella monorepo and anti-framework.

🚀 Please help me to work full-time on these projects by sponsoring me on GitHub. Thank you! ❤️

About

Configurable nested 2D grid layout generators.

Currently, this package features two grid layout strategies (each based on requesting/allocating cells of a desired size), as well as more general supporting types to define other layout types / implementations using the same shared API.

A brief overview and comparison of the available strategies is provided further below.

Status

STABLE - used in production

Search or submit any issues for this package

Related packages

  • @thi.ng/imgui - Immediate mode GUI with flexible state handling & data only shape output

Installation

yarn add @thi.ng/layout

ESM import:

import * as l from "@thi.ng/layout";

Browser ESM import:

<script type="module" src="https://esm.run/@thi.ng/layout"></script>

JSDelivr documentation

For Node.js REPL:

const l = await import("@thi.ng/layout");

Package sizes (brotli'd, pre-treeshake): ESM: 1.22 KB

Dependencies

Note: @thi.ng/api is in most cases a type-only import (not used at runtime)

Usage examples

Four projects in this repo's /examples directory are using this package:

| Screenshot | Description | Live demo | Source | |:----------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------|:-----------------------------------------------------|:----------------------------------------------------------------------------------| | | Interactive inverse FFT toy synth | Demo | Source | | | Canvas based Immediate Mode GUI components | Demo | Source | | | Minimal IMGUI usage example | Demo | Source | | | Randomized space-filling, nested grid layout generator | Demo | Source |

API

Generated API docs

GridLayout

The GridLayout class supports infinite nesting and column/row-based space allocation, based on an initial configuration and supporting multiple column/row spans.

screenshot

The code producing this layout (incl. the visualization itself):

import * as g from "@thi.ng/geom";
import { gridLayout, type LayoutBox } from "@thi.ng/layout";
import { writeFileSync } from "node:fs";

// collection of generated layout boxes/cells
const cells: g.Group[] = [];

// helper function to collect & visualize layout boxes
const addRect = (id: number, box: LayoutBox, fill: string) => {
    console.log(box);
    const shape = g.rect([box.x, box.y], [box.w, box.h], { fill });
    cells.push(
        g.group({}, [
            shape,
            g.text(g.centroid(shape)!, "#" + id, {
                fill: "black",
                stroke: "none",
            }),
        ])
    );
};

// create a single column layout @ position [10,10], 1000px wide
// the last values are row height and cell spacing
const layout = gridLayout(10, 10, 1000, 1, 60, 4);

// get next layout box (1st row, by default the column/row span is [1,1])
addRect(1, layout.next(), "#fec");
// { x: 10, y: 10, w: 1000, h: 60, cw: 1000, ch: 60, gapX: 4, gapY: 4, span: [ 1, 1 ] }

// 2nd row
addRect(2, layout.next(), "#fec");
// { x: 10, y: 74, w: 1000, h: 60, cw: 1000, ch: 60, gapX: 4, gapY: 4, span: [ 1, 1 ] }

// create nested 2-column layout (3rd row)
const twoCols = layout.nest(2);

addRect(3, twoCols.next(), "#cfc");
// { x: 10, y: 138, w: 498, h: 60, cw: 498, ch: 60, gapX: 4, gapY: 4, span: [ 1, 1 ] }

addRect(4, twoCols.next(), "#cfc");
// { x: 512, y: 138, w: 498, h: 60, cw: 498, ch: 60, gapX: 4, gapY: 4, span: [ 1, 1 ] }

// now nest 3-columns in the 1st column of twoCols
// (i.e. now each column is 1/6th of the main layout's width)
const inner = twoCols.nest(3);

// allocate with col/rowspan, here 1 column x 4 rows
addRect(5, inner.next([1, 4]), "#9ff");
// { x: 10, y: 202, w: 163.33, h: 252, cw: 163.33, ch: 60, gapX: 4, gapY: 4, span: [ 1, 4 ] }
addRect(6, inner.next([1, 4]), "#9ff");
// { x: 177.33, y: 202, w: 163.33, h: 252, cw: 163.33, ch: 60, gapX: 4, gapY: 4, span: [ 1, 4 ] }
addRect(7, inner.next([1, 4]), "#9ff");
// { x: 344.66, y: 202, w: 163.33, h: 252, cw: 163.33, ch: 60, gapX: 4, gapY: 4, span: [ 1, 4 ] }

// back to twoCols (2nd column)
addRect(8, twoCols.next([1, 2]), "#cfc");
// { x: 512, y: 202, w: 498, h: 124, cw: 498, ch: 60, gapX: 4, gapY: 4, span: [ 1, 2 ] }

// export as SVG
writeFileSync(
    "export/readme-grid.svg",
    g.asSvg(
        g.svgDoc(
            {
                __margin: 10,
                font: "12px Menlo, monospace",
                align: "center",
                baseline: "middle",
            },
            ...cells
        )
    )
);

StackedLayout

An extension of GridLayout which tracks individual column-based heights and so can create more complex, irregular, packed, space-filling layout arrangements. This layout algorithm prioritizes the column(s) with the lowest height.

This class also provides a .availableSpan() method to find available space and help equalize columns and fill/allocate any bottom gaps.

IMPORTANT: As with GridLayout, nested layouts MUST be completed first before requesting new cells (aka LayoutBoxes) from a parent, otherwise unintended overlaps will occur.

screenshot

The code producing this layout (incl. the visualization itself):

import * as g from "@thi.ng/geom";
import { stackedLayout, type LayoutBox } from "@thi.ng/layout";
import { writeFileSync } from "node:fs";

// collection of generated layout boxes/cells
const cells: g.Group[] = [];

// helper function to collect & visualize layout boxes
const addRect = (id: number, box: LayoutBox, fill: string) => {
    console.log(box);
    const shape = g.rect([box.x, box.y], [box.w, box.h], { fill });
    cells.push(
        g.group({}, [
            shape,
            g.text(g.centroid(shape)!, "#" + id, {
                fill: "black",
                stroke: "none",
            }),
        ])
    );
};

// create a 4-column layout @ position [0,0], 1000px wide
// the last values are row height and cell spacing
const layout = stackedLayout(0, 0, 1000, 4, 60, 4);

// get next layout box (1st column)
addRect(1, layout.next([1, 2]), "#fec");
// { x: 0, y: 0, w: 247, h: 124, cw: 247, ch: 60, gapX: 4, gapY: 4, span: [ 1, 2 ] }

// 2nd column
addRect(2, layout.next(), "#fec");
// { x: 251, y: 0, w: 247, h: 60, cw: 247, ch: 60, gapX: 4, gapY: 4, span: [ 1, 1 ] }

// 3rd column
addRect(3, layout.next([1, 4]), "#fec");
// { x: 502, y: 0, w: 247, h: 252, cw: 247, ch: 60, gapX: 4, gapY: 4, span: [ 1, 4 ] }

// 4th column
addRect(4, layout.next([1, 1]), "#fec");
// { x: 753, y: 0, w: 247, h: 60, cw: 247, ch: 60, gapX: 4, gapY: 4, span: [ 1, 1 ] }

// 2x2 span
// (note that this will create a gap in the 2nd column)
addRect(5, layout.next([2, 2]), "#fec");
// { x: 0, y: 128, w: 498, h: 124, cw: 247, ch: 60, gapX: 4, gapY: 4, span: [ 2, 2 ] }

const inner = layout.nest(2);

addRect(6, inner.next([1, 5]), "#cfc");
// { x: 753, y: 64, w: 121.5, h: 316, cw: 121.5, ch: 60, gapX: 4, gapY: 4, span: [ 1, 5 ] }
addRect(7, inner.next([1, 5]), "#cfc");
// { x: 878.5, y: 64, w: 121.5, h: 316, cw: 121.5, ch: 60, gapX: 4, gapY: 4, span: [ 1, 5 ] }

// fill available space in the other columns
// (depending on situation, this might have to be done multiple times
// to fill all available space, please consult documentation)
addRect(8, layout.next(layout.availableSpan()), "#9ff");
// { x: 0, y: 256, w: 749, h: 124, cw: 247, ch: 60, gapX: 4, gapY: 4, span: [ 3, 2 ] }

// export as SVG
writeFileSync(
    "export/readme-stacked.svg",
    g.asSvg(
        g.svgDoc(
            {
                __margin: 10,
                font: "12px Menlo, monospace",
                align: "center",
                baseline: "middle",
            },
            ...cells
        )
    )
);

Authors

If this project contributes to an academic publication, please cite it as:

@misc{thing-layout,
  title = "@thi.ng/layout",
  author = "Karsten Schmidt",
  note = "https://thi.ng/layout",
  year = 2019
}

License

© 2019 - 2025 Karsten Schmidt // Apache License 2.0