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

@prnt/dagr-layout

v0.1.4

Published

Headless Sugiyama layout pipeline for Dagr, the dagre successor: incremental and animation first.

Readme

@prnt/dagr-layout

The headless Sugiyama layout pipeline behind Dagr, and the reason the project exists: a dagre successor designed for animation and incremental relayout from the start, not retrofitted with them.

pnpm add @prnt/dagr-layout @prnt/dagr-graph

@prnt/dagr-graph is a peerDependency, so you install it yourself. See "one copy of @prnt/dagr-graph" below, which is not a formality.

import { Graph } from '@prnt/dagr-graph';
import { layout } from '@prnt/dagr-layout';

const graph = new Graph();
graph.addNode('ingest');
graph.addNode('parse');
graph.addNode('render');
graph.addEdge('ingest', 'parse', 'a');
graph.addEdge('parse', 'render', 'b');

const result = layout({ graph, config: { nodeSep: 20, rankSep: 60 } });
result.nodes.get('parse'); // { id, x, y, width, height }, where x and y are the centre
result.edges.get('a'); // a routed polyline

Headless means headless: this package returns geometry and draws nothing. It names no renderer, and the y-down to y-up conversion belongs to whoever owns the drawing.

What it is for: the edit, not the first draw

A cold layout is the easy half. What a node-graph UI actually does is edit a graph that is already on screen, and the question is how much of the drawing moves when one node is added.

Bind the stages and the config once with createLayout, then feed it patches:

import { createLayout } from '@prnt/dagr-layout';

const engine = createLayout({ config: { nodeSep: 20, rankSep: 60 } });
const first = engine.run(graph);

const again = engine.relayout(patch); // patch from graph.subscribe
again.ran; // which stages actually ran
again.influence; // exactly which nodes were entitled to move
again.delta; // absolute positions for what changed, and nothing else

Over six scripted editing sessions measured against the shipped default pipeline, the incremental path moves 4.1x to 38.4x less of the drawing per patch than a cold relayout, with order churn of exactly zero on every session and every step, for 3.1% to 13.8% more crossings. The tables, the sessions and what the warm start costs are on the incremental layout page, committed as a golden corpus rather than quoted from memory.

A patch that no stage reads runs no stage at all: an inert relayout on a 10,000-node drawing costs 1.955ms against 3,317ms for a full one.

Read this first: one copy of @prnt/dagr-graph

Graph uses #private fields, which makes it nominally typed. Two copies in your tree fail to compile with separate declarations of a private property '#nodes', and @prnt/dagr-graph is all over this package's surface (LayoutInput.graph, LayoutConfig.nodeSize). It is a peer dependency for exactly that reason, and a caret range on a 0.x package does not cross a minor, so a version skew between the two is a resolution with two copies in it rather than a warning.

The delta contract

Three rules the whole package is written against, and a consumer animating a relayout relies on all three:

  • Absent means unchanged. A node not named in a delta did not move.
  • Absolute only. Positions are where the node now is, never an offset.
  • Removals apply before additions.

applyDelta is exported so the meaning ships as code rather than as prose. @prnt/dagr-render's createNodeMotion is the consumer that springs between two of them.

Choosing stages

layout() is sugar over the same runner the engine uses. The defaults are defaultStages; override one at a time:

import { layout, longestPathRankStage, networkSimplexRankStage } from '@prnt/dagr-layout';

layout({ graph }, { rank: longestPathRankStage }); // fewest layers
layout({ graph }, { rank: networkSimplexRankStage }); // least total edge length

serveLayout is the worker side, exported from the package root rather than a subpath, because the two halves are not separately loadable.

Documentation

The pipeline, every stage boundary, the stability contract and the measurements are on the layout pipeline page.

MIT © prnt.design