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

@pictogrammers/node-connector

v0.9.2

Published

A zero-dependency TypeScript class that renders routed SVG connections between nodes. Paths automatically navigate around node rectangles, separate overlapping segments, and draw bridge gaps at crossings.

Readme

NodeConnector

A zero-dependency TypeScript class that renders routed SVG connections between nodes. Paths automatically navigate around node rectangles, separate overlapping segments, and draw bridge gaps at crossings.

Installation

npm install @pictogrammers/node-connector

Quick start

<svg id="canvas" xmlns="http://www.w3.org/2000/svg"></svg>

<script type="module">
import { NodeConnector } from '@pictogrammers/node-connector';

const svg = document.getElementById('canvas');
const connector = new NodeConnector(svg);

// Register node bounding boxes
connector.setNode('a', 60,  100, 152, 80);
connector.setNode('b', 320, 100, 152, 80);

// Declare pins by key and relative Y offset
connector.setOutputPin('a', 'out', 40);
connector.setInputPin('b',  'in',  40);

// Connect output pin 'out' of 'a' to input pin 'in' of 'b'
connector.connect('a', 'out', 'b', 'in');
</script>

API

new NodeConnector(svg: SVGSVGElement)

Attaches to an existing <svg> element. Appends two <g> groups — one for connection paths, one for pin circles — as the last children of the SVG. Place your node markup before constructing NodeConnector (or insert nodes as svg.insertBefore(nodeEl, svg.firstChild)) so the connector's groups render on top.

Appearance properties

| Property | Default | Description | |---|---|---| | bridgeColor | 'white' | Background color painted over the under-path at each crossing to create a bridge gap. Set to match your canvas background. | | pathColor | '#666' | Stroke color of connection paths. | | pathColorHover | '#bbb' | Stroke color when hovering a connection path. | | pathInvalidColor | '#F00' | Stroke color of invalid (backward) connection paths. | | pinColor | '#999' | Fill color of pin circles. | | pinColorHover | '#444' | Fill color of pin circles on hover. | | pinBorder | '#333' | Stroke color of pin circles. | | pinBorderHover | '#333' | Stroke color of pin circles on hover. |

Set any of these before calling connect() — they are applied at element-creation time (path and pin colors) or read from this at event time (hover colors, so they can be changed after the fact).

connector.bridgeColor      = '#0d1117';
connector.pathColor        = '#4a9eff';
connector.pathColorHover   = '#80bfff';
connector.pathInvalidColor = '#ff4444';
connector.pinColor         = '#4a9eff';
connector.pinColorHover    = '#80bfff';
connector.pinBorder        = '#1a5fa8';
connector.pinBorderHover   = '#4a9eff';

setNode(nodeId, x, y, width, height)

Registers or updates a node's bounding box. Call this whenever a node is created or moved. All connected paths are immediately re-routed.

| Param | Type | Description | |---|---|---| | nodeId | string | Unique identifier for the node | | x, y | number | Top-left corner in SVG coordinates | | width, height | number | Size of the node rectangle |

connector.setNode('uuid1', 32, 32, 128, 64);

Moving a node:

node.x += dx;
node.y += dy;
nodeEl.style.left = `${node.x}px`;
nodeEl.style.top  = `${node.y}px`;
connector.setNode(node.id, node.x, node.y, node.w, node.h);

removeNode(nodeId)

Removes the node, all its pins, and all connections that reference it.

connector.removeNode('uuid1');

setInputPin(nodeId, key, relY)

Declares (or updates) an input pin on the left edge of a node. relY is the vertical offset from the node's top-left corner. If the same nodeId + key pair already exists the position is updated and all connected paths are re-routed.

connector.setInputPin('uuid1', 'in',  42);

setOutputPin(nodeId, key, relY)

Declares (or updates) an output pin on the right edge of a node.

connector.setOutputPin('uuid2', 'out', 42);

removeInputPin(nodeId, key)

Removes the input pin and any connections that use it.

connector.removeInputPin('uuid1', 'in');

removeOutputPin(nodeId, key)

Removes the output pin and any connections that use it.

connector.removeOutputPin('uuid2', 'out');

connect(sourceNodeId, sourceKey, targetNodeId, targetKey)

Draws a routed path from the output pin sourceKey of sourceNodeId to the input pin targetKey of targetNodeId. Both nodes and their respective pins must have been registered first.

connector.connect('uuid2', 'out', 'uuid1', 'in');
connector.connect('uuid1', 'out', 'uuid3', 'in');

The output pin circle is placed on the right edge of the source node; the input pin circle on the left edge of the target node. Pin circles respond to hover, click, and drag.

disconnect(sourceNodeId, sourceKey, targetNodeId, targetKey)

Removes a specific connection path. Pin circles remain until removeInputPin, removeOutputPin, or removeNode is called.

connector.disconnect('uuid2', 'out', 'uuid1', 'in');

on('click', callback)

Fired when a pin circle is clicked or when an output pin is dragged more than 5 px.

connector.on('click', (pin) => {
    // pin.nodeId  — which node owns the pin
    // pin.key     — the pin's key
    // pin.type    — 'input' | 'output'
});

Typical pattern — track the active output pin, then connect on the next input click:

let active = null;

connector.on('click', (pin) => {
    if (active) {
        if (pin.type === 'input') {
            connector.connect(active.nodeId, active.key, pin.nodeId, pin.key);
            active = null;
        } else {
            active = pin;
            connector.setPreviewPin(pin.nodeId, pin.key);
        }
    } else if (pin.type === 'output') {
        active = pin;
        connector.setPreviewPin(pin.nodeId, pin.key);
    }
});

on('connection-click', callback)

Fired when the user clicks anywhere along a connection path (via an invisible 12 px wide hit zone).

connector.on('connection-click', (sourceNodeId, sourceKey, targetNodeId, targetKey) => {
    connector.disconnect(sourceNodeId, sourceKey, targetNodeId, targetKey);
});

setPreviewPin(nodeId, key)

Shows a dashed preview line that follows the cursor from the output pin key of nodeId. Cleared automatically on any SVG click or Escape. Call again with a different node/key to switch the active pin mid-drag.

connector.setPreviewPin('uuid2', 'out');

Path routing

Paths are cubic bezier curves routed around node rectangles:

  • Forward connections (target to the right of source) use an iterative waypoint algorithm that detects collisions with intermediate node rectangles and inserts routing waypoints above or below the blocker. The side is chosen based on the source pin's Y position relative to the obstacle's vertical centre — pins higher than the obstacle's midpoint route above it, pins lower route below.
  • Backward connections (target to the left of source) are drawn as a straight line and styled with pathInvalidColor to indicate an invalid connection.
  • Overlapping horizontal segments across multiple paths at the same Y are spread into parallel lanes (±4 px, ±8 px) so they remain visually distinct. Lane assignment is sorted primarily by source pin Y — for above-routing, higher pins take the outer lane; for below-routing, lower pins take the outer lane — so entry-segment curves maintain their ordering and avoid crossing. Path length is the tiebreaker when two paths share the same source pin Y, with longer paths placed further out.
  • Crossings between paths get a bridge gap: a short perpendicular segment in bridgeColor painted over the under-path so it appears to pass beneath the over-path.

Development

npm install
npm run start
# npm run build