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

grid-router

v0.1.1

Published

Orthogonal edge routing on a grid: A* with per-direction cell occupancy, Steiner-style bus joins and crossing hops — plus an optional Svelte canvas where the consumer owns the chips.

Readme

grid-router

Orthogonal edge routing on a grid for node-link diagrams: A* with per-direction cell occupancy, Steiner-style bus joins, and crossing "hops". Framework-free core + an optional Svelte 5 canvas where the consumer owns the chips.

Live examples →

| Schematic with named ports | Org chart | | --- | --- | | Electronic circuit example: real schematic symbols with positioned named ports | Org chart example: reporting lines merge into buses per manager |

| Oil & gas + cost layers | Radial tree | | --- | --- | | Oil & gas pipeline example: product buses detouring around a keep-out zone | Radial tree example: root at the center, color-coded limbs radiating in all directions |

Concepts

  • Grid & occupancy — the canvas is rasterized into square cells (res, default 12px). Node boxes (inflated by BOX_INFLATE) hard-block cells. Each free cell can host one horizontal and one vertical run; same-direction reuse by another bus is practically prohibited, perpendicular crossings pay a small toll. Lanes separate themselves; crossings happen only where they're worth it.
  • Buses & joins — edges sharing (source, bus) form a bus. Later edges of a bus seed their search from every cell the bus already routed (cost ~0), so branches leave the trunk at the optimal point. exitCost prices opening a new trunk out of a source: raise it to force merges even over shorter direct paths. Color follows the bus: a bus shares geometry, so every edge in it should render the same color — if you color per target, split the buses along the same lines (bus: colorGroupOf(target)).
  • Endpoints are exclusive — two buses never share a start/goal cell, so stubs and arrowheads never stack.
  • Nothing is dropped — if a dense node row walls the grid off, a desperate pass routes through obstacles at a huge cost and the result counts it in violations. violations > 0 is a layout problem, not a router bug: the layout does not supply enough corridor space.
  • Corridor supplycorridorGaps(res) returns the empirically-validated minimum rowGap / chipGap / sidePad the layout must provide so that routing stays violation-free. A chip gap must fit one aligned free cell column (2·res + 2·inflate); side padding keeps the outer columns routable.
  • Hops — crossings are disambiguated without geometry: each bus paints a background-colored halo under its paths, cutting a small gap into every bus drawn before it (the cut line reads as tunneling under). Per-bus grouping keeps a bus's own T-junctions intact.
  • Cost layers — the router is deliberately agnostic of higher-level layout concepts (lanes, sections, keep-out zones). opts.costRegions lets the consumer express them anyway: rects with a per-cell bias — positive repels routes, negative attracts them (e.g. a "lanes layer" biasing its corridors negative). The step cost is floored, so negative biases are safe.
  • Port constraintsopts.nodeSides restricts which box sides a node's edges may attach to (schematic pins, side-anchored UML). For POSITIONED named ports, register tiny pad elements at the pin locations as their own nodes (mcu:pb0) and connect edges to those — the body registers separately as a pure obstacle (see the circuit example); small boxes are first-class.
  • Pre-built layerswithLayers(base, portsLayer(…), keepOutLayer(…), corridorLayer(…)) composes declarative routing facts into opts; falsy entries are skipped so layers toggle inline (withLayers(base, zoneOn && keepOutLayer([zone]))).

Core API (framework-free)

import { routeGrid, corridorGaps } from 'grid-router';

const result = routeGrid(
  boxes,   // Map<string, GridBox> — measured node rects (t/b/l/r/cx/cy)
  edges,   // { id, source, target, bus?, data? }[]
  width,   // routable canvas size in px
  height,
  { res: 12, costOwn: 0.02, costTurn: 1, exitCost: 0 } // defaults shown
);
// result.conns: { id, source, target, bus, data, d /* SVG path */ }[]
// result.violations: number (0 = clean; see corridorGaps)
// result.bottom, result.debug (occupancy cells for overlays)

Svelte canvas

The component draws only connectors. You lay out arbitrary chip markup in the default snippet and tag each connectable element with the provided register action; chip events stay on your own DOM. Size the component's container with CSS — it measures itself (no width/height props).

<script lang="ts">
  import { GridDiagram, type GridConn } from 'grid-router/svelte';

  let hovered = $state<string | undefined>();
  const edges = [{ id: 'e1', source: 'a', target: 'b', bus: 'ok', data: { tone: 'ok' } }];
</script>

<GridDiagram
  {edges}
  opts={{ res: 12 }}
  connStyle={(c) => ({
    color: (c.data as { tone?: string })?.tone === 'ok' ? '#3fb950' : '#8b94a1',
    dashed: false,
    arrowAt: 'end',
    class: hovered && hovered !== c.id ? 'dim' : ''
  })}
  onconnenter={(c) => (hovered = c.id)}
  onconnleave={() => (hovered = undefined)}
  onrouted={({ violations, ms }) => console.log(violations, ms)}
>
  {#snippet children(register)}
    <div class="my-layout">
      <div use:register={'a'}>chip A</div>
      <div use:register={'b'}>chip B</div>
    </div>
  {/snippet}
</GridDiagram>

<style>
  /* consumer-driven highlight: connStyle classes land on package paths */
  :global(path.dim) { opacity: 0.15; }
</style>

Layout contract: use the published CSS vars --gr-row-gap / --gr-chip-gap (from corridorGaps(res)) as your row/chip gaps, and set --grid-diagram-bg to your panel background so crossing hops cut with the right color.

Examples

Live at https://geptyro.github.io/grid-router/, or locally:

cd examples && npm install && npm run dev   # http://localhost:5200
  • Electronic circuit — real schematic symbols with POSITIONED NAMED PORTS (bat:+, mcu:pb0 — tiny registered pads at the lead tips; the body is just an obstacle); nets as buses (VCC/GND rails share trunks), no arrowheads (arrowAt: 'none'), hover a wire to trace its whole net.
  • Org chart — one bus per manager, reporting lines merge; hover a line or a person to light the whole team.
  • Oil & gas + cost layers — absolutely-positioned network, buses per product; toggle a maintenance keep-out zone (keepOutLayer) and watch the flows detour, or hit randomize layout — chips scatter and the router re-routes live (the revision prop triggers re-measure when chips move without resizing the canvas).
  • Radial tree — a mind map: root at the CENTER, color-coded limbs radiating in all directions via polar placement; each parent's children are one bus, so limbs fuse near the trunk (drive it with the merge knob). Randomize tree regenerates the whole topology.

Development

npm install
npm run build   # tsc → dist (core)
npm test        # vitest

The Svelte entry ships as source (src/svelte) and is compiled by the consumer's bundler (Vite + svelte plugin resolve the svelte export condition).