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

@mr_mint/elkjs-libavoid

v0.5.0

Published

Obstacle-avoiding edge routing for ELK JSON graphs using libavoid

Downloads

490

Readme

elkjs-libavoid

CI npm License: MIT

Obstacle-avoiding edge routing for ELK JSON graphs using libavoid.

Use ELK.js (or any other tool) to position your nodes, then pass the graph to elkjs-libavoid to compute edge routes that avoid overlapping with nodes.

Installation

npm install @mr_mint/elkjs-libavoid

elkjs is an optional peer dependency — install it if you need ELK for node layout:

npm install elkjs

Quick Start

import ELK from "elkjs";
import { routeEdgesInPlace } from "@mr_mint/elkjs-libavoid";

const elk = new ELK();

// 1. Define your graph
const graph = {
  id: "root",
  children: [
    { id: "n1", width: 100, height: 50 },
    { id: "n2", width: 100, height: 50 },
    { id: "n3", width: 100, height: 50 },
  ],
  edges: [
    { id: "e1", source: "n1", target: "n2" },
    { id: "e2", source: "n1", target: "n3" },
  ],
};

// 2. Layout nodes with ELK
const positioned = await elk.layout(graph);

// 3. Route edges with libavoid (mutates graph in place)
const routed = await routeEdgesInPlace(positioned);
// Edges now have sourcePoint, targetPoint, and bendPoints

Or use routeEdges to get route results without mutating the graph:

import { routeEdges } from "@mr_mint/elkjs-libavoid";

const routes = await routeEdges(positioned);
// routes is a Map<string, RouteResult> with absolute coordinates
for (const [edgeId, route] of routes) {
  console.log(edgeId, route.sourcePoint, route.targetPoint, route.bendPoints);
}

API

init(wasmPath?: string): Promise<void>

Pre-initialize the libavoid WASM module. This is optional — routeEdges, routeEdgesInPlace, and createRoutingSession will call it automatically on first use in Node.js. Call it explicitly if you want to control when the WASM module loads.

Browser environments: You must call init() with a URL to the libavoid.wasm file before using the routing APIs. Copy libavoid.wasm from node_modules/libavoid-js/dist/ to your public directory.

import { init } from "@mr_mint/elkjs-libavoid";

// Node.js — auto-detected, no path needed:
await init();

// Browser — must provide the WASM URL:
await init("/path/to/libavoid.wasm");

routeEdges(graph, options?): Promise<Map<string, RouteResult>>

Compute obstacle-avoiding routes for all edges in an ELK JSON graph. Nodes must already have x, y, width, and height set. The input graph is not modified.

Returns a Map of edge ID to RouteResult. Coordinates are absolute (not relative to parent nodes).

Supports both ELK simple edge format (source/target) and extended format (sources/targets/sections), as well as ports and hierarchical (compound) graphs.

import { routeEdges } from "@mr_mint/elkjs-libavoid";

const routes = await routeEdges(graph, {
  routingType: "orthogonal",
  shapeBufferDistance: 8,
});

for (const [edgeId, route] of routes) {
  // route.sourcePoint, route.targetPoint, route.bendPoints — absolute coords
  // route.sourceSide, route.targetSide — "north" | "south" | "east" | "west"
}

routeEdgesInPlace(graph, options?): Promise<ElkGraph>

Compute obstacle-avoiding routes and write them directly into the graph's edge objects. The graph is modified in place and also returned.

Coordinates are written relative to the edge's owner node's content area (inside padding), matching the ELK JSON convention.

import { routeEdgesInPlace } from "@mr_mint/elkjs-libavoid";

const routed = await routeEdgesInPlace(graph, {
  routingType: "orthogonal",
  shapeBufferDistance: 8,
});
// routed === graph, edges now have sourcePoint/targetPoint/bendPoints

createRoutingSession(graph, options?): Promise<RoutingSession>

Create a long-lived routing session for incremental updates. Use this instead of routeEdges() when you need to update node positions frequently (e.g., during drag operations) without re-creating the entire router on every frame.

import { createRoutingSession } from "@mr_mint/elkjs-libavoid";

const session = await createRoutingSession(graph, {
  routingType: "orthogonal",
});

// On node drag:
session.moveNode("n1", { x: newX, y: newY });
const routes = session.processTransaction();
// routes is a Map<string, RouteResult> with absolute coordinates

// Add/remove edges dynamically:
session.addEdge({ id: "e3", source: "n1", target: "n3" });
session.removeEdge("e1");
const updatedRoutes = session.processTransaction();

// Cleanup:
session.destroy();

RoutingSession implements Symbol.dispose for TC39 Explicit Resource Management:

using session = await createRoutingSession(graph);

getWasmPath(): string

Node.js helper that returns the absolute path to the bundled libavoid.wasm file. Available from the ./node subpath export.

import { getWasmPath } from "@mr_mint/elkjs-libavoid/node";

const wasmPath = getWasmPath();

Types

RouteResult

Returned by routeEdges() and RoutingSession.processTransaction().

interface RouteResult {
  sourcePoint: ElkPoint;
  targetPoint: ElkPoint;
  bendPoints: ElkPoint[];
  sourceSide: ConnectionSide;
  targetSide: ConnectionSide;
}

ConnectionSide

type ConnectionSide = "north" | "south" | "east" | "west";

SelfLoopHandling

type SelfLoopHandling = "skip" | "fallback";

Options

All options are optional. Pass them as the second argument to routeEdges or routeEdgesInPlace.

Router Options

These options are shared by routeEdges, routeEdgesInPlace, and createRoutingSession.

| Option | Type | Default | Description | |--------|------|---------|-------------| | routingType | "orthogonal" \| "polyline" | "orthogonal" | Routing style — right-angle bends or diagonal segments | | segmentPenalty | number | 10 | Cost per segment beyond the first | | anglePenalty | number | 0 | Cost for tight bends | | crossingPenalty | number | 0 | Cost for edge crossings | | clusterCrossingPenalty | number | 0 | Cost for crossing cluster boundaries | | fixedSharedPathPenalty | number | 0 | Cost for sharing a path with an immovable edge | | reverseDirectionPenalty | number | 0 | Cost for routing backwards | | portDirectionPenalty | number | 100 | Cost for leaving a port in the wrong direction | | shapeBufferDistance | number | 4 | Padding around obstacles (in pixels) | | idealNudgingDistance | number | 4 | Spacing between parallel edge segments | | nudgeOrthogonalSegmentsConnectedToShapes | boolean | — | Nudge segments connected to shapes | | nudgeOrthogonalTouchingColinearSegments | boolean | — | Nudge touching colinear segments | | performUnifyingNudgingPreprocessingStep | boolean | — | Preprocessing step for unified nudging | | nudgeSharedPathsWithCommonEndPoint | boolean | — | Nudge shared paths that share an endpoint |

Routing Options

These additional options are available for routeEdges and routeEdgesInPlace only (not createRoutingSession).

| Option | Type | Default | Description | |--------|------|---------|-------------| | edgeIds | string[] | — | Only route edges with these IDs; others are left unchanged | | selfLoopHandling | "skip" \| "fallback" | "skip" | How to handle self-loop edges (source === target). "skip" omits them; "fallback" generates a synthetic route |

Graph Format

elkjs-libavoid works with the ELK JSON format. Nodes must be positioned before routing.

Simple Edges

{
  id: "root",
  children: [
    { id: "n1", x: 0, y: 0, width: 100, height: 50 },
    { id: "n2", x: 200, y: 100, width: 100, height: 50 },
  ],
  edges: [
    { id: "e1", source: "n1", target: "n2" },
  ],
}

With routeEdgesInPlace, each edge gets sourcePoint, targetPoint, and bendPoints.

Extended Edges

edges: [
  { id: "e1", sources: ["n1"], targets: ["n2"] },
]

With routeEdgesInPlace, extended edges get a sections array with startPoint, endPoint, and bendPoints.

Ports

children: [
  {
    id: "n1", x: 0, y: 0, width: 100, height: 50,
    ports: [{ id: "p1", x: 100, y: 25, width: 5, height: 5 }],
  },
],
edges: [
  { id: "e1", source: "n1", sourcePort: "p1", target: "n2" },
]

Hierarchical Graphs

Edges defined within compound nodes are routed correctly with coordinates relative to their parent.

{
  id: "root",
  children: [
    {
      id: "group", x: 0, y: 0, width: 400, height: 200,
      children: [
        { id: "a", x: 10, y: 10, width: 50, height: 50 },
        { id: "b", x: 200, y: 100, width: 50, height: 50 },
      ],
      edges: [{ id: "e1", source: "a", target: "b" }],
    },
  ],
}

Requirements

  • Node.js >= 20
  • A runtime that supports WebAssembly

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

# Install dependencies
npm install

# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Build
npm run build

# Lint and format
npm run check:fix

# Type check
npm run typecheck

License

MIT