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

@loradb/lora-graph-canvas

v0.11.4

Published

React graph canvas for LoraDB. Unified 2D/3D force-directed component with a built-in tool palette for adding, connecting, selecting, and removing nodes.

Readme

@loradb/lora-graph-canvas

React graph canvas for LoraDB. One component, <LoraGraphCanvas />, that wraps force-graph (2D HTML5 canvas) and 3d-force-graph (WebGL via Three.js). Switch between 2D and 3D at runtime without re-mounting your data, and use the built-in tool palette to build, edit, select, and delete nodes interactively.

import { LoraGraphCanvas } from "@loradb/lora-graph-canvas";
import "@loradb/lora-graph-canvas/styles.css";

<LoraGraphCanvas
  defaultData={{
    nodes: [{ id: "a" }, { id: "b" }],
    links: [{ source: "a", target: "b" }],
  }}
  nodeLabel="id"
  nodeAutoColorBy="group"
/>;

Install

yarn add @loradb/lora-graph-canvas three
# or
npm install @loradb/lora-graph-canvas three

three is a required peer dependency — the package itself does not bundle Three.js, so 2D-only consumers can dedupe with the rest of the app's Three usage, and 3D consumers can pin a specific version.

Built-in tools

The toolbar ships with twelve tools, controllable individually:

| Tool | Shortcut | What it does | | ------------ | -------- | -------------------------------------------------- | | Select | V | Click / shift-click nodes to select them. | | Pan | H | Pan-only cursor. | | Add node | N | Click on the canvas to drop a node. | | Add link | L | Click two nodes to connect them. | | Delete | / | Delete the current selection (cascades links). | | Fit | F | zoomToFit to the current graph bbox. | | Zoom in/out | + - | Step the zoom level. | | Pause/Resume | — | Stop / start the d3 simulation. | | Screenshot | — | Download a PNG of the canvas. | | Toggle 2D/3D | 3 | Swap engines, preserving data and selection. |

// Pick a subset…
<LoraGraphCanvas tools={["select", "add-node", "delete", "toggle-mode"]} />

// …or hide the whole bar and drive everything from the ref:
<LoraGraphCanvas tools={false} ref={ref} />

Ref handle

import { useRef } from "react";
import {
  LoraGraphCanvas,
  type LoraGraphCanvasHandle,
} from "@loradb/lora-graph-canvas";

const ref = useRef<LoraGraphCanvasHandle>(null);

ref.current?.addNode({ id: "x", label: "hello" });
ref.current?.addLink({ source: "x", target: "y" });
ref.current?.removeNode("x");        // also removes attached links
ref.current?.fit(400, 40);
ref.current?.setMode("3d");
const blob = await ref.current?.screenshot();

Available methods: data (getData, setData, addNode, addNodes, updateNode, removeNode, removeNodes, addLink, addLinks, removeLink, clear); selection (getSelection, setSelection, selectAll, clearSelection); view (getMode, setMode, fit, centerAt, zoom, zoomIn, zoomOut); engine (pause, resume, reheat, screenshot); escape hatches (engine2D, engine3D).

Controlled vs uncontrolled

// Uncontrolled — internal state owns the graph, host gets a notification:
<LoraGraphCanvas defaultData={initialData} onDataChange={save} />

// Controlled — host owns the graph:
<LoraGraphCanvas data={dataFromState} onDataChange={setDataState} />

The same dichotomy applies to mode (defaultMode vs mode).

Theming

The chrome (toolbar, context menu, tooltips) is driven by --lgc-* CSS variables. Override the ones you want either through the theme prop or by attaching CSS to the .lora-graph-canvas container. The engine's own canvas reads backgroundColor from a regular prop, independent of the theme.

import { LoraGraphCanvas, darkTheme } from "@loradb/lora-graph-canvas";

<LoraGraphCanvas
  backgroundColor="#0e1014"
  theme={{ ...darkTheme, accent: "#ff6699" }}
/>;

Two presets are exported: lightTheme and darkTheme.

Confirm-before-delete

Gate every node / link removal — keyboard, toolbar, context menu, selection panel, cut, and the imperative removeNode / removeLink handle methods — through an async guard:

<LoraGraphCanvas
  onBeforeNodeDelete={(nodes, { source }) =>
    source === "imperative"
      ? true                                // trust your own code
      : openMyConfirmDialog(nodes)          // returns Promise<boolean>
  }
  onBeforeLinkDelete={(links) => openMyConfirmDialog([], links)}
  onNodeDeleted={(nodes, { source }) => analytics.track("nodes.removed", {
    n: nodes.length,
    source,
  })}
/>

The guard receives every item in the batch (one selection-wide call, not per-item), the originating source ("keyboard" | "toolbar" | "contextMenu" | "selectionPanel" | "cut" | "imperative"), and may return either a boolean or a Promise<boolean>. A thrown error is treated as a cancel — your host won't silently destroy data.

When no guard is wired, deletion happens immediately as before; the imperative methods become Promise<boolean> but resolve on the same tick.

Performance knobs

For large graphs, cap cooldownTicks (default ∞) and increase warmupTicks to spend more time computing layout off-screen before the first paint:

<LoraGraphCanvas cooldownTicks={50} warmupTicks={20} />

For graphs above ~10k nodes, switch the layout engine in 3D mode:

<LoraGraphCanvas mode="3d" forceEngine="ngraph" />

Opt-in UX flags

Each is a single boolean prop:

| Prop | What it does | | --- | --- | | focusOnClick | Click a node → animated camera focus; click again to restore. | | highlightNeighborsOnHover | Hover a node → it + its neighbours light up in the accent color. Pair with autoIndexNeighbors so the component builds the neighbour index for you, or stash _neighbors / _links yourself. | | autoIndexNeighbors | After every data change, build _neighbors and _links arrays on each node — required by the highlight flag if you don't provide them. | | collideNodes | Inject d3-force-3d's forceCollide so circles don't overlap. Pass a number to override the radius. | | showGrid | Faint background grid that adapts to the zoom level. 2D only. Pass { spacing, color } to customise. | | showLegend | Bottom-left widget enumerating nodeAutoColorBy groups with a colour swatch; click a group to toggle its visibility (drives nodeVisibility automatically). | | enableRename | (Default true.) Double-click a node to rename it inline. Set false to suppress. | | enableClipboard | (Default true.) ⌘C / ⌘X / ⌘V keybindings + matching ref methods. |

<LoraGraphCanvas
  data={graph}
  nodeAutoColorBy="group"
  focusOnClick
  highlightNeighborsOnHover
  autoIndexNeighbors
  collideNodes
  showGrid
  showLegend
/>

Animated particles for events

The kapsule emits an animated particle along a link via emitParticle. Wire it through the ref to visualise events (data flow, message sent, etc.):

const ref = useRef<LoraGraphCanvasHandle>(null);
useEffect(() => {
  socket.on("message", (msg) => {
    const link = graph.links.find((l) => l.id === msg.linkId);
    if (link) ref.current?.emitParticle(link);
  });
}, []);

Custom forces

Inject d3-force primitives through d3Force(name, fn):

import { forceRadial } from "d3-force-3d";

ref.current?.d3Force("radial", forceRadial(200));
ref.current?.reheat();

License

BUSL-1.1. Third-party attributions for force-graph and 3d-force-graph (MIT, © Vasco Asturiano) live in THIRD_PARTY.md.