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

@leebhin/pixigraph

v0.3.2

Published

Pixi.js 기반 그래프 렌더링 라이브러리 — node/edge/highlight/preview/history/viewport (cytoscape-like API)

Readme

pixigraph

A Pixi.js v8 graph rendering engine with a Cytoscape.js-like API (graph.nodes(), graph.add(), graph.style(), graph.on()), built for WebGL performance on large graphs — tens of thousands of nodes and edges.

Unlike Cytoscape, pixigraph is a pure scene-graph library: it renders into a Pixi Container you mount yourself and knows nothing about the DOM. You forward pointer events into it; it stays renderer-agnostic, embeddable, and fast.

  • Declarative selector stylingnode, edge, .class, :selected, #id with AND-combination and CSS-like specificity cascade
  • Rich node shapes — rectangles, circles, arbitrary polygons, and image/SVG-textured nodes (with automatic shape masking and a per-graph texture cache)
  • Smart edges — surface clipping to node geometry, parallel-edge curvature, target arrowheads, round caps, and animated dashed flow
  • Event delegation — Cytoscape-style graph.on(type, selector, handler) driven by feed(), with automatic mouseover/mouseout from a single mousemove
  • Selection & transform handles — resize (corner/edge handles), rotate (dedicated handle or Figma-style corner ring), move, multi-select union boxes, aspect-lock, and center-resize — all per-element overridable
  • Built-in viewport — pan/zoom camera with wheel & drag handlers, animated fit / center / panToElement, and screen-stable handle/hit sizing
  • Highlight groups — overlay multiple named style groups on the same elements, prefix-batch removal, auto-dim of non-highlighted elements, and focus-color locking
  • Undo / redo — automatic history tracking for add/remove/move/resize/rotate/data edits, with batching for compound operations
  • Clipboard — copy / cut / paste / duplicate of selections, plus property-only copy/paste
  • Graph editing primitives — connect nodes, inline-insert a node into an edge, split an edge with a new node, merge an edge–node–edge chain, with live preview overlays
  • Hover tooltips — opt-in, property-ordered entry extraction for your own tooltip component
  • Zero runtime dependencies — only pixi.js as a peer dependency. Ships ESM + CJS + type declarations.

Installation

npm install @leebhin/pixigraph pixi.js

pixi.js (^8.0.0) is a peer dependency — install it alongside.

Quick Start

pixigraph renders into a Pixi Container (graph.view). You mount that container in your own Pixi Application, then forward pointer events into the graph using feed().

import { Application, Point } from 'pixi.js';
import { PixiGraph } from '@leebhin/pixigraph';

// 1. A Pixi application is the host renderer.
const app = new Application();
await app.init({ background: '#ffffff', resizeTo: window, antialias: true });
document.body.appendChild(app.canvas);

// 2. Create the graph. It is a pure scene graph — no DOM container here.
const graph = new PixiGraph({
  selectionHandles: true,                 // single-node resize handles on select
  viewport: { wheel: true, drag: true },  // built-in pan/zoom camera
});
app.stage.addChild(graph.view);

// The viewport needs the host canvas to attach wheel/drag listeners.
graph.viewport?.attach(app.canvas);

// 3. Add nodes and edges. Coordinates are "graph-local" (your own world space).
graph.add({
  nodes: [
    { id: 'a', bbox: { x: 0,   y: 0, w: 80, h: 40 } },
    { id: 'b', bbox: { x: 260, y: 0, w: 80, h: 40 } },
  ],
  edges: [
    { id: 'e1', source: 'a', target: 'b' },
  ],
});

// 4. Style declaratively with selectors.
graph.style([
  { selector: 'node',          style: { fill: 0x2563eb, alpha: 0.85 } },
  { selector: 'node:selected', style: { fill: 0xf59e0b } },
  { selector: 'edge',          style: { stroke: 0x94a3b8, width: 2, arrowShape: 'triangle' } },
]);

// 5. Forward DOM pointer events into the graph (event delegation).
const world = () => graph.viewport?.world ?? graph.view; // graph-local space
function toLocal(e: PointerEvent) {
  const r = app.canvas.getBoundingClientRect();
  return world().toLocal(new Point(e.clientX - r.left, e.clientY - r.top));
}

app.canvas.addEventListener('pointertap', (e) => {
  const { x, y } = toLocal(e);
  graph.feed('tap', x, y, e);
});
app.canvas.addEventListener('pointermove', (e) => {
  const { x, y } = toLocal(e);
  graph.feed('mousemove', x, y, e); // auto-derives mouseover / mouseout
});

// 6. Subscribe with Cytoscape-style selectors.
graph.on('tap', 'node', ({ target }) => console.log('node tapped:', target?.id()));
graph.on('mouseover', 'node', ({ target }) => target?.addClass('hovered'));
graph.on('mouseout',  'node', ({ target }) => target?.removeClass('hovered'));

Core Concepts

A few ideas explain the whole API:

  • graph.view is a Pixi Container. You add it to your stage (or to your own world container). The graph never touches the DOM, so it embeds anywhere Pixi runs.
  • Coordinates are graph-local. Every position, bbox, and hit-test uses your world space (for example, image-pixel coordinates of a diagram). Converting screen pixels ⇄ graph-local is the caller's job — typically container.toLocal(...). When the built-in viewport is active, that space is graph.viewport.world.
  • Events are delegated, not listened. The graph doesn't register DOM listeners. You call graph.feed('tap' | 'cxttap' | 'mousemove', x, y, nativeEvent); the graph hit-tests and dispatches to handlers whose selector matches. A single mousemove feed automatically produces mouseover / mouseout.
  • State changes are data; rendering follows. ele.addClass(), ele.select(), ele.data(k, v) mutate the element and trigger a targeted re-render through the active style rules. You declare what things look like with graph.style(); the graph decides when to repaint.

Adding Elements

graph.add(input) takes { nodes?, edges? }. Nodes are always processed before edges so edges can resolve their endpoints. Duplicate ids are ignored; an edge whose source or target node is absent is skipped.

graph.add({
  nodes: [
    // Rectangle (default shape).
    { id: 'n1', bbox: { x: 0, y: 0, w: 100, h: 60 }, data: { label: 'Pump' } },

    // Circle.
    { id: 'n2', bbox: { x: 200, y: 0, w: 60, h: 60 }, shape: 'circle' },

    // Polygon — vertices are normalized [0,1] within the bbox, flat [x0,y0,x1,y1,...].
    { id: 'n3', bbox: { x: 320, y: 0, w: 80, h: 80 },
      polygonPoints: [0.5, 0, 1, 1, 0, 1] },

    // Image / SVG node — stretched to fit the bbox, masked to the shape.
    { id: 'n4', bbox: { x: 440, y: 0, w: 64, h: 64 },
      image: '/icons/valve.svg' },
  ],
  edges: [
    { id: 'e1', source: 'n1', target: 'n2', data: { kind: 'flow' } },
  ],
});

Node input fields

| Field | Type | Description | |-------|------|-------------| | id | string | Unique id. Referenced by edge.source / edge.target. | | bbox | { x, y, w, h } | Graph-local axis-aligned box the node occupies. | | data | Record<string, unknown> | Optional metadata, read back via ele.data('key'). | | shape | 'rect' \| 'circle' \| 'polygon' | Optional. Inferred from polygonPoints when omitted (else rect). | | polygonPoints | number[] | Normalized [0,1] flat vertices relative to the bbox. | | image | string | Image / SVG / data-URL / blob-URL used as the node texture. Same URL is fetched & decoded once per graph and cached; a fallback fill shows until it loads. |

Edge input fields

| Field | Type | Description | |-------|------|-------------| | id | string | Unique id. | | source | string | Source node id. | | target | string | Target node id. | | data | Record<string, unknown> | Optional metadata. |

Edges automatically clip to node surfaces (polygon outline intersection for polygon nodes, bbox boundary otherwise) and bow apart when multiple edges share the same node pair.

Styling

Styles are declarative selector rules, replacing the entire ruleset on each graph.style() call (like cy.style()).

graph.style([
  { selector: 'node',                 style: { fill: 0x1e293b, alpha: 0.9 } },
  { selector: 'node.critical',        style: { fill: 0xdc2626 } },
  { selector: 'node:selected',        style: { fill: 0xf59e0b } },
  { selector: 'edge',                 style: { stroke: 0x64748b, width: 2 } },
  { selector: 'edge:selected',        style: { stroke: 0xf59e0b, width: 4, arrowShape: 'triangle' } },
  { selector: '#special-node',        style: { fill: 0x9333ea } },
]);

Selectors

A selector is one or more tokens AND-combined with no whitespace:

| Token | Matches | |-------|---------| | node / edge | Element group | | .className | Elements carrying the class | | :selected | Selected elements | | #id | The element with that id |

Examples: node, edge, node.foo, node:selected, node.foo:selected, #my-id.

Specificity = id × 100 + (classes + pseudos) × 10 + (group ? 1 : 0). The effective style of an element is computed by cascading, lowest specificity first, then declaration order (later wins on ties), starting from group defaults.

Not supported (by design): descendant selectors, attribute selectors, function-valued styles (data(x)), and :hover (use events to toggle a hovered class instead).

Style properties

| Property | Applies to | Description | |----------|-----------|-------------| | fill | node | Fill color — hex int (0x2563eb) or '#rrggbb'. | | alpha | node / edge | Node fill alpha or edge stroke alpha. | | stroke | edge | Stroke color. | | width | edge | Stroke width (graph-local units). | | arrowShape | edge | 'triangle' or 'none' (default 'none'). | | arrowSize | edge | Arrowhead size (default width × 3). | | lineCap | edge | 'butt' (default) or 'round'. | | lineDash | edge | Dash length (graph-local units). 0 = solid. | | lineGap | edge | Gap length. Defaults to lineDash. | | lineDashOffset | edge | Dash phase offset. Animate it for flow effects (see Dashed flow). |

Base style override

Pass style to the constructor to change the fallback colors used when no rule matches:

const graph = new PixiGraph({
  style: {
    node: { fill: 0x2563eb, alpha: 0.8 },
    edge: { stroke: 0x999999, width: 2, alpha: 1 },
  },
});

Events

The graph hit-tests on feed() and dispatches to matching handlers. Register with an optional selector:

graph.on('tap', ({ target, x, y, native }) => { /* any hit + background (target may be null) */ });
graph.on('tap', 'node', ({ target }) => { /* node hits only */ });
graph.on('cxttap', '.deletable', ({ target, native }) => { native?.preventDefault(); });
graph.on('mouseover', 'node:selected', ({ target }) => { /* … */ });

graph.off('tap', handler); // remove one
graph.off('tap');          // remove all of a type
graph.off();               // remove everything

Event payload: { type, target, x, y, native }target is the hit element (or null for background taps), x/y are graph-local, native is the original DOM event.

| Event | Fired by | target | |-------|----------|----------| | tap | feed('tap', …) (left click) | element or null (background) | | cxttap | feed('cxttap', …) (right click / contextmenu) | element or null | | mouseover | auto, from feed('mousemove', …) | always non-null | | mouseout | auto, when hover leaves an element | always non-null | | select / unselect | selection state changes | the element | | add / remove | element added / removed | the element | | bbox / rotation / polygon | node geometry changes | the element | | data | ele.data(key, value) | the element |

You only ever feed tap, cxttap, and mousemove; mouseover/mouseout are derived automatically. Use graph.clearHover(native?) to force a mouseout when the pointer leaves the host canvas.

Selection & Transform Handles

Enable handles via the constructor (selectionHandles: true | options). Handles are drawn at a screen-stable size regardless of zoom and can be customized globally and per element.

const graph = new PixiGraph({
  selectionHandles: {
    enabled: true,        // show resize handles on single-node select
    corners: true,        // 4 corner handles
    edges: true,          // 4 edge-midpoint handles
    move: true,           // drag nodes to move
    union: false,         // per-node handles on multi-select (true = one union box)
    rotate: true,         // enable rotation
    rotateMode: 'zone',   // Figma-style ring outside corners ('handle' = a dedicated top handle)
    keepAspect: false,    // hold Shift to lock aspect ratio temporarily
    centerResize: false,  // hold Ctrl to resize about center temporarily
    handle: { size: 10, shape: 'square', fill: 0xffffff, stroke: 0x000000, strokeWidth: 1 },
    box:    { enabled: true, stroke: 0x2563eb, width: 1, dash: 4, gap: 4 },
  },
});

// Selection API
graph.select('n1');                       // replace selection
graph.select(['n2', 'n3'], { additive: true });
graph.unselect('n2');
graph.unselectAll();
graph.selected();                         // currently selected elements

// Per-element overrides
graph.element('n1')?.resizable(false).rotatable(false).movable(true);

When the viewport is active, handle and hit sizing track zoom automatically. Without it, call graph.setViewScale(scale) and graph.setHitTolerance(px) when your camera changes so handles stay screen-stable.

Handle options

| Option | Type | Default | Description | |--------|------|---------|-------------| | enabled | boolean | false | Show resize handles on single-node select. | | selectable | boolean | true | Global selection enable. | | corners | boolean | true | 4 corner handles. | | edges | boolean | false | 4 edge-midpoint handles. | | resizeCursor | boolean | true | Show resize cursor on handle hover. | | centerResize | boolean | false | Resize about center (also via Ctrl). | | keepAspect | boolean | false | Lock aspect ratio (also via Shift). | | union | boolean | false | One union box for multi-select (else per-node). | | move | boolean | true | Drag nodes to move (needs ele.movable()). | | selectOnGrab | boolean | true | Dragging an unselected node auto-selects + moves it. | | rotate | boolean | false | Enable rotation. | | rotateMode | 'zone' \| 'handle' | 'handle' | 'zone' = Figma corner ring; 'handle' = dedicated top handle. | | rotateZone | number | 16 | zone mode: ring thickness (px). | | rotateGap | number | 24 | handle mode: handle distance above the top edge (px). | | handle | object | — | Handle style: size, zoomFollow, shape, fill, stroke, strokeWidth. | | box | object | — | Selection outline: enabled, stroke, width, alpha, dash, gap. |

Viewport (Pan / Zoom Camera)

Activate with viewport: true | config. When active, graph.view becomes the camera's outer container and graph content lives inside an inner world container (graph.viewport.world). The viewport keeps handle sizing and hit tolerance screen-stable as you zoom.

const graph = new PixiGraph({
  viewport: {
    wheel: true,                 // wheel-zoom (requires viewport.attach(canvas))
    drag: true,                  // drag-pan
    dragButton: 'middle',        // 'left' | 'middle' | 'right'
    minZoom: 0.05,
    maxZoom: 12,
    hitTolerancePx: 6,           // screen-px hit padding, kept constant across zoom
    onChange: () => syncMinimap(),
  },
});
graph.viewport?.attach(app.canvas);

// Camera control (also exposed directly on graph for convenience)
graph.fit();                                    // fit all elements
graph.fit([graph.element('n1')!], { padding: 0.2, duration: 300 });
graph.center();                                 // center all (keep zoom)
graph.panToElement('n1', { ratio: 0.18 });      // pan + select, auto-zoom to fill ~18%
graph.panToElement({ path: ['n1', 'e1', 'n2'] }, { duration: 400 });
graph.panToBbox({ x: 0, y: 0, w: 1000, h: 800 });
graph.zoom; graph.pan;                          // current zoom / pan (null if viewport off)

Pan options (PanOptions): duration, easing, padding, zoom, ratio, maxZoom, select, onUpdate, onComplete.

Highlight Groups

Highlights overlay inline styles on a named set of elements. They cascade above selector rules, in registration order, and several groups can target the same element at once — ideal for trace results, search matches, connectivity views, and collaborative selection.

graph.highlight({ id: 'trace-1', elements: tracePath, style: { fill: '#ef4444', alpha: 0.6 } });
graph.highlight({ id: 'search',  elements: matches,   style: { stroke: '#0ea5e9', width: 14 } });

graph.unhighlight('search');
graph.unhighlightByPrefix('trace-');   // remove every group starting with "trace-"
graph.clearHighlights();
graph.highlightIds();                  // active group ids, in order

When any highlight group is active, non-highlighted elements are automatically dimmed (.dim). Pass noDim: true to a group to suppress that (a "show this, leave the rest alone" overlay).

Focus color locks attention on one color: only elements in a highlight group of that color stay bright; everything else gets a softer .focus-dim.

graph.setFocusColor('#ef4444');  // only red trace paths stay prominent
graph.setFocusColor(null);       // release focus

Both .dim and .focus-dim are defined by the library's built-in system style rules — they work without you adding any CSS or style rule, and you can override them by declaring the same selector in graph.style().

Undo / Redo

graph.history automatically records add / remove / move / resize / rotate / data edits.

graph.history.undo();
graph.history.redo();
graph.history.canUndo();
graph.history.canRedo();

const off = graph.history.onChange(() => updateToolbar()); // subscribe; call off() to unsubscribe

For drag interactions, suspend automatic recording during the drag and record once on release:

graph.history.suspend();
// … live drag via setNodesBboxes(...) per frame …
graph.history.resume();
graph.history.recordBboxChanges(changes); // one undo step for the whole drag

Wrap compound operations in a batch so a single undo reverts them together:

graph.history.beginBatch();
graph.connect('a', 'b');
graph.remove('old-edge');
graph.history.endBatch();

History keeps up to 100 steps (oldest dropped). The library's own compound methods (insertNodeIntoEdge, mergeChain, splitEdgeAt, paste, …) batch internally.

Clipboard

graph.copySelection();           // copy selected elements
graph.cutSelection();            // copy + remove
graph.paste({ x: 100, y: 100 }); // paste at a graph-local point → new ids
graph.duplicate();               // copy + paste in place

graph.copyProperties();          // copy data/properties only (no geometry)
graph.pasteProperties();         // apply copied properties to the selection

graph.hasClipboard();
graph.hasPropertyClipboard();

A drag-to-copy flow is also available: beginCopyDrag()updateCopyDrag(dx, dy)commitCopyDrag(dx, dy) / cancelCopyDrag(), with isCopyDragActive().

Graph Editing Primitives

High-level structural edits, each undoable and batched:

// Connect two nodes — auto-generates the edge id.
const id = graph.connect('a', 'b', { kind: 'flow' });

// Inline-insert an existing node into an edge (e.g. Alt-drag a node onto a wire):
graph.insertNodeIntoEdge('node-x', 'edge-1');   // → { e1, e2 } new edges

// Split an edge with a brand-new node at a projected point:
graph.splitEdgeAt('edge-1', { x: 120, y: 80, w: 40, h: 40 }); // → { nodeId, e1, e2 }

// Detect and merge an edge–node–edge chain back into a single edge:
const chain = graph.detectChain(graph.element('mid')!);
if (chain) graph.mergeChain(chain);             // → new edge id

// Geometry helpers for snapping / routing:
graph.findEdgeNear({ x, y }, 8);                // nearest edge within threshold
graph.projectOnEdge(edge, { x, y });            // clamped projection onto an edge

Preview overlays

Before committing an edit, render a live, non-interactive preview. Previews are signature-guarded, so they're cheap to call every frame:

graph.previewInsert('node-x', targetEdge); // dim target + show the two branch edges
graph.previewMerge(chain);                 // mark the two edges removed + show the merged edge
graph.hasActivePreview();
graph.clearPreviews();

Hover Tooltips

Opt in with tooltip: true | options. The library doesn't render the tooltip — it gives you ordered, filtered key/value entries for your own component.

const graph = new PixiGraph({
  tooltip: {
    enabled: true,
    propertyOrder: ['category', 'symbolName', 'tagNumber'],
    hiddenKeyPattern: /(uuid|id)/i,
    hiddenKeys: ['polygonPoints'],
  },
});

graph.on('mouseover', 'node', ({ target }) => {
  const entries = graph.tooltipEntries(target!); // [[key, value], …] in priority order
  showTooltip(entries);
});

Dashed Flow Animation

Give edges a dash, then animate the global dash offset for a directional "flow" effect. Only dashed edges re-render, so it's cheap.

graph.style([
  { selector: 'edge.flow', style: { stroke: 0x22c55e, width: 3, lineDash: 12, lineGap: 8 } },
]);

let offset = 0;
app.ticker.add(() => {
  offset -= 1;                 // negative flows source → target
  graph.setDashOffset(offset);
});

Querying & Hit Testing

graph.element('n1');                 // by id, or null
graph.$('#n1');                      // selector lookup (#id form)
graph.elements();                    // all, insertion order
graph.nodes();
graph.edges();
graph.size();
graph.byClass('critical');           // elements with a class
graph.elementsIn({ x, y, w, h });    // rubber-band box select (excludes hidden/eye-off)

graph.elementAt(x, y);               // topmost element at a graph-local point
graph.nodeAt(x, y);
graph.edgeAt(x, y);

Hit testing respects real geometry (polygon outline, ellipse, rotated rect), prefers nodes over edges, and prefers the smallest overlapping node.

Configuration Reference

new PixiGraph({
  style?:            Partial<PixiGraphBaseStyle>,          // base node/edge colors
  selectionHandles?: boolean | PixiGraphHandleOptions,     // resize/rotate/move handles
  tooltip?:          boolean | PixiGraphTooltipOptions,    // hover tooltip entries
  viewport?:         boolean | PixiGraphViewportConfig,    // pan/zoom camera
});

Viewport config

| Option | Type | Default | Description | |--------|------|---------|-------------| | wheel | boolean | false | Wheel-zoom handler (needs attach(canvas)). | | wheelSensitivity | number | 0.0015 | Zoom factor per deltaY unit. | | drag | boolean | false | Drag-pan handler (needs attach(canvas)). | | dragButton | 'left' \| 'middle' \| 'right' | 'middle' | Pan mouse button. | | dragModifier | 'shift' \| 'ctrl' \| 'alt' \| null | null | Modifier required to start a pan. | | minZoom | number | 0.01 | Minimum zoom. | | maxZoom | number | 20 | Maximum zoom. | | hitTolerancePx | number | 6 | Screen-px hit padding, kept constant across zoom (0 = off). | | onChange | () => void | — | Called on any transform change (minimap sync, etc.). |

API Reference

new PixiGraph(config?) exposes:

Elements & queries

add · remove · clear · element · $ · elements · nodes · edges · size · byClass · elementsIn · elementAt · nodeAt · edgeAt · setHitTolerance

Styling

style · setDashOffset

Events

on · off · feed · clearHover

Selection & handles

select · unselect · unselectAll · selected · setSelectionHandles · redrawHandles · setViewScale · selectionBbox · resizableSelected · rotatableSelected · movableSelected · handlePositions · handleAt · rotateZoneAt

Node geometry

setNodeBbox · setNodesBboxes · setNodePolygon · setNodesRotations · static computeResizedBbox · static scaleBboxAbout · static handleCursor

Viewport

fit · center · panToElement · panToElements · panToBbox · zoom · pan · viewport (the PixiGraphViewport instance, or null)

Highlights

highlight · unhighlight · unhighlightByPrefix · clearHighlights · hasHighlight · highlightIds · setFocusColor · getFocusColor

History

history (a PixiGraphHistory: undo · redo · canUndo · canRedo · clear · onChange · beginBatch · endBatch · suspend · resume · record*)

Clipboard

copySelection · cutSelection · paste · duplicate · copyProperties · pasteProperties · hasClipboard · hasPropertyClipboard · beginCopyDrag · updateCopyDrag · commitCopyDrag · cancelCopyDrag · isCopyDragActive

Editing & preview

connect · insertNodeIntoEdge · splitEdgeAt · mergeChain · detectChain · findEdgeNear · projectOnEdge · previewInsert · previewMerge · hasActivePreview · currentPreviewInsertTarget · clearPreviews

Misc

hidden · tooltipEntries · destroy · isDestroyed · view

PixiGraphElement

Returned from graph.element(), graph.nodes(), etc. — never constructed directly.

  • Identity: id · group · isNode · isEdge · data(key?, value?) · bbox · position · rotation · shape · polygonPoints · image
  • Capabilities: selectable · resizable · rotatable · movable · handleMode
  • Classes & state: addClass · removeClass · toggleClass · hasClass · classes · select · unselect · selected
  • Highlights: highlights · hasHighlight
  • Graph traversal (edges): source · target · sourcePoint · targetPoint · connectedEdges

Most setters are chainable: ele.addClass('on').data('label', 'x').select();

Exports

import {
  PixiGraph, PixiGraphElement,
  PixiGraphViewport, PixiGraphHistory, PixiGraphPreview,
  PixiGraphEventBus, HighlightManager,
  StyleEngine, parseSelector, matchesSelector,
  ptSegDist, projectOnSeg,
  DEFAULT_PIXIGRAPH_STYLE,
} from '@leebhin/pixigraph';

// Types
import type {
  PixiGraphConfig, PixiGraphAddInput, PixiGraphNodeInput, PixiGraphEdgeInput,
  PixiGraphBaseStyle, PixiGraphStyleProps, PixiGraphStyleRule, ParsedSelector,
  PixiGraphEventType, PixiGraphFeedType, PixiGraphEventPayload, PixiGraphHandler,
  PixiGraphHighlightInput, PixiGraphViewportConfig, PanOptions, ViewportTween,
  ElementGroup, GraphBbox, GraphPoint,
} from '@leebhin/pixigraph';

Cleanup

graph.destroy();        // detach handlers, cancel tweens, free image textures
graph.isDestroyed();

How It Works

  1. The graph renders into a Pixi Container (graph.view) split into stacked sublayers — edges below nodes below handles — so z-order is correct without per-element sorting.
  2. With the viewport active, graph.view is the camera's outer container and content lives in an inner world container whose transform is the pan/zoom matrix.
  3. All input is graph-local. You convert screen → graph-local (container.toLocal) and feed() it; the graph never registers DOM listeners itself, which keeps it embeddable and SSR-/test-friendly.
  4. feed('mousemove', …) tracks the hovered element and emits mouseover / mouseout automatically when it changes.
  5. Effective style = group defaults → matching selector rules (specificity, then declaration order) → highlight-group overrides → system classes (.dim, .focus-dim, .hidden). Only the affected elements re-render on a state change.
  6. Edges clip to node surfaces and bow apart when parallel; arrowheads are placed precisely even where geometry overlaps.
  7. Image nodes load through Pixi Assets, cached per URL so N nodes sharing an image cost one fetch; a fallback fill shows until the texture resolves.
  8. Handles and the selection box are drawn at a screen-stable size by inversely scaling with zoom (setViewScale / viewport), so they don't grow or shrink as you zoom.

Building From Source

npm run build      # dist/ — ESM + CJS + .d.ts (via tsup)
npm run typecheck  # tsc --noEmit

License

MIT © LeeBhin