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

react-cosmos-diagram

v0.11.2

Published

React Diagram

Readme

react-cosmos-diagram

npm version license TypeScript

A highly customizable React library for building node-based diagrams and graph UIs. → Live demo


Table of Contents


Installation

npm install react-cosmos-diagram

Import the stylesheet once at your app entry point:

import 'react-cosmos-diagram/styles/style.css';

Peer requirements: React >= 17, React DOM >= 17


Features

  • Zoom, pan, drag, multi-select, and keyboard shortcuts supported natively
  • Fully customizable nodes, edges, and ports with TypeScript generics
  • Nested nodes via parentNode
  • Drag-selection box (default key: Shift)
  • Auto-panning during node drag and edge connection
  • onlyRenderVisibleElements for large-diagram performance
  • Background component for grid overlays
  • Grid snapping via gridStep

Quickstart

Minimal example

import ReactDiagram, { useNodesState, useEdgesState, addEdge } from 'react-cosmos-diagram';
import 'react-cosmos-diagram/styles/style.css';
import { useCallback } from 'react';
import type { Connection } from 'react-cosmos-diagram';

const initialNodes = [
  { id: '1', position: { x: 100, y: 100 }, data: { label: 'Node 1' } },
  { id: '2', position: { x: 300, y: 100 }, data: { label: 'Node 2' } },
];

const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }];

export default function App() {
  const [nodes, , onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

  const onConnect = useCallback(
    (params: Connection) => setEdges((eds) => addEdge(params, eds)),
    [],
  );

  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <ReactDiagram
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
      />
    </div>
  );
}

ReactDiagram needs a container with explicit width and height.

Full example with edge reconnection

import { useCallback, useRef } from 'react';

import ReactDiagram, {
  useNodesState,
  useEdgesState,
  addEdge,
  updateEdge,
  Connection,
  Edge,
  MarkerType,
  PortType,
} from 'react-cosmos-diagram';

import 'react-cosmos-diagram/styles/style.css';

const initialNodes = [
  {
    id: '1',
    width: 200,
    height: 100,
    data: { label: 'Node1' },
    position: { x: 100, y: 100 },
  },
  {
    id: '2',
    data: { label: 'Node2' },
    position: { x: 300, y: 50 },
  },
  {
    id: '3',
    data: { label: 'Node3' },
    position: { x: 10, y: 10 },
    parentNode: '1',
  },
  {
    id: '4',
    data: { label: 'Node4' },
    position: { x: 650, y: 100 },
  },
];

const initialEdges = [
  {
    id: 'e-1-2',
    type: 'bezier',
    source: '1',
    target: '2',
    markerStart: { type: MarkerType.Arrow },
  },
  {
    id: 'e-2-3',
    source: '2',
    target: '3',
    markerEnd: { type: MarkerType.Arrow },
    type: 'step',
    label: 'label',
  },
  {
    id: 'e-3-4',
    type: 'step',
    source: '3',
    target: '4',
    markerEnd: { type: MarkerType.Arrow },
  },
];

function Diagram() {
  const edgeConnected = useRef(true);

  const [nodes, , onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

  const onConnect = useCallback(
    (params: Connection) => setEdges((edges) => addEdge({ ...params }, edges)),
    [],
  );

  const onEdgeUpdateStart = useCallback(() => {
    edgeConnected.current = false;
  }, []);

  const onEdgeUpdateEnd = useCallback(
    (_e: MouseEvent, currentEdge: Edge, _portType: PortType) => {
      if (!edgeConnected.current) {
        setEdges((edges) => edges.filter((edge) => edge.id !== currentEdge.id));
      }
      edgeConnected.current = true;
    },
    [],
  );

  const onEdgeUpdate = useCallback(
    (originEdge: Edge, newConnection: Connection) => {
      edgeConnected.current = true;
      setEdges((edges) => updateEdge(originEdge, newConnection, edges));
    },
    [],
  );

  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <ReactDiagram
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        onEdgeUpdateStart={onEdgeUpdateStart}
        onEdgeUpdateEnd={onEdgeUpdateEnd}
        onEdgeUpdate={onEdgeUpdate}
      />
    </div>
  );
}

export default Diagram;

Custom Nodes

Use NodeProps<YourNodeType> to get typed props. Place Port components inside to define connection points.

import { memo } from 'react';
import { Port, NodeProps, Node } from 'react-cosmos-diagram';
import { Position } from 'react-cosmos-diagram';

type MyNodeData = { label: string; color?: string };
type MyNode = Node<MyNodeData, 'myNode'>;

function MyNode({ data }: NodeProps<MyNode>) {
  return (
    <div style={{ padding: 10, background: data.color ?? '#fff', border: '1px solid #ccc' }}>
      <Port type="target" position={Position.Top} />
      <div>{data.label}</div>
      <Port type="source" position={Position.Bottom} />
    </div>
  );
}

export default memo(MyNode);

Register custom nodes in nodeTypes (define outside the component to avoid recreation):

import { nodeTypes } from './nodeTypes';

const nodeTypes = { myNode: MyNode };

<ReactDiagram nodes={nodes} nodeTypes={nodeTypes} ... />

Custom Edges

Use EdgeProps<YourEdgeType> along with BaseEdge and getBezierPath:

import { memo } from 'react';
import { BaseEdge, EdgeProps, Edge, getBezierPath } from 'react-cosmos-diagram';
import { Position } from 'react-cosmos-diagram';

type MyEdgeData = { animated?: boolean };
type MyEdge = Edge<MyEdgeData, 'myEdge'>;

function MyEdge({
  id,
  sourceX,
  sourceY,
  targetX,
  targetY,
  sourcePosition = Position.Bottom,
  targetPosition = Position.Top,
  style,
  markerEnd,
}: EdgeProps<MyEdge>) {
  const [path, labelX, labelY, offsetX, offsetY] = getBezierPath({
    sourceX,
    sourceY,
    sourcePosition,
    targetX,
    targetY,
    targetPosition,
  });

  return (
    <BaseEdge
      path={path}
      labelX={labelX}
      labelY={labelY}
      style={style}
      markerEnd={markerEnd}
    />
  );
}

export default memo(MyEdge);

Register in edgeTypes:

const edgeTypes = { myEdge: MyEdge };

<ReactDiagram edges={edges} edgeTypes={edgeTypes} ... />

Custom Connection Line

Provide a ConnectionLineComponent prop to render a custom line while the user is dragging a new connection:

import type { ConnectionLineComponentProps } from 'react-cosmos-diagram';

function CustomConnectionLine({ fromX, fromY, toX, toY }: ConnectionLineComponentProps) {
  return (
    <g>
      <path
        fill="none"
        stroke="#222"
        strokeWidth={2}
        d={`M${fromX},${fromY} C${fromX},${toY} ${toX},${fromY} ${toX},${toY}`}
      />
      <circle cx={toX} cy={toY} r={4} fill="#222" />
    </g>
  );
}

<ReactDiagram ConnectionLineComponent={CustomConnectionLine} ... />

Components

| Component | Description | |---|---| | ReactDiagram | Main diagram canvas. Default export. | | ReactDiagramProvider | Context provider for accessing the store outside the canvas. | | Port | Connection port placed inside a custom node. | | BaseEdge | SVG edge primitive used inside custom edges. | | BezierEdge | Built-in bezier curve edge. | | StepEdge | Built-in right-angle step edge. | | Background | Background grid component. |


Hooks

useNodesState<NodeType>(initialNodes)

const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);

Returns [NodeType[], Dispatch<SetStateAction<NodeType[]>>, OnNodesChange<NodeType>].

useEdgesState<EdgeType>(initialEdges)

const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

Returns [EdgeType[], Dispatch<SetStateAction<EdgeType[]>>, OnEdgesChange<EdgeType>].

useStore(selector)

Zustand selector hook for reading internal diagram state from within the canvas.

useStoreApi()

Returns { getState, setState, subscribe } — use when you need imperative access to the store, e.g. from event handlers outside the React tree.


Utilities

Edge paths

| Function | Returns | Description | |---|---|---| | getBezierPath(params) | [path, labelX, labelY, offsetX, offsetY] | Bezier SVG path + label position | | getBezierEdgeCenter(params) | [centerX, centerY, offsetX, offsetY] | Center point of a bezier edge | | getStepPath(params) | [path, labelX, labelY, offsetX, offsetY] | Right-angle step path | | getStraightPath(params) | [path, labelX, labelY, offsetX, offsetY] | Straight-line path |

Edge state

| Function | Description | |---|---| | addEdge(connection, edges) | Appends a new edge from a Connection object | | updateEdge(oldEdge, newConnection, edges) | Replaces an edge's source/target with a new connection |

Type guards

| Function | Description | |---|---| | isCoreNode(element) | Returns true if the value is a CoreNode | | isCoreEdge(element) | Returns true if the value is a CoreEdge |

Geometry

| Function | Description | |---|---| | clamp(value, min, max) | Clamps a number between min and max | | rectToBox(rect) | Converts Rect to Box | | boxToRect(box) | Converts Box to Rect |


Props Reference

Data

| Prop | Type | Description | |---|---|---| | nodes | NodeType[] | Array of node objects | | edges | EdgeType[] | Array of edge objects | | nodeTypes | NodeTypes<NodeType> | Map of type name → custom node component | | edgeTypes | EdgeTypes<EdgeType> | Map of type name → custom edge component |

Change Handlers

| Prop | Type | Description | |---|---|---| | onNodesChange | OnNodesChange<NodeType> | Called on node position, selection, dimension, or removal changes | | onEdgesChange | OnEdgesChange<EdgeType> | Called on edge selection or removal changes |

Connection

| Prop | Type | Default | Description | |---|---|---|---| | onConnect | (connection: Connection) => void | — | Called when a new connection is completed | | onConnectStart | OnConnectStart | — | Called when the user starts dragging a connection | | onConnectEnd | OnConnectEnd | — | Called when the connection drag ends | | connectionRadius | number | — | Snap radius around a port for completing a connection | | ConnectionLineComponent | ConnectionLineComponent | — | Custom component for the in-progress connection line |

Node Events

| Prop | Type | |---|---| | onNodeClick | (event: MouseEvent, node: NodeType) => void | | onNodeDoubleClick | (event: MouseEvent, node: NodeType) => void | | onNodeContextMenu | (event: MouseEvent, node: NodeType) => void | | onNodeMouseEnter | (event: MouseEvent, node: NodeType) => void | | onNodeMouseMove | (event: MouseEvent, node: NodeType) => void | | onNodeMouseLeave | (event: MouseEvent, node: NodeType) => void | | onNodeDragStart | NodeDragHandler<NodeType> | | onNodeDrag | NodeDragHandler<NodeType> | | onNodeDragEnd | NodeDragHandler<NodeType> |

Edge Events

| Prop | Type | |---|---| | onEdgeClick | (event: MouseEvent, edge: EdgeType) => void | | onEdgeDoubleClick | EdgeMouseHandler<EdgeType> | | onEdgeContextMenu | EdgeMouseHandler<EdgeType> | | onEdgeMouseEnter | EdgeMouseHandler<EdgeType> | | onEdgeMouseMove | EdgeMouseHandler<EdgeType> | | onEdgeMouseLeave | EdgeMouseHandler<EdgeType> | | onEdgeUpdate | (oldEdge: EdgeType, newConnection: Connection) => void | | onEdgeUpdateStart | (event: MouseEvent, edge: EdgeType, portType: PortType) => void | | onEdgeUpdateEnd | (event: MouseEvent, edge: EdgeType, portType: PortType) => void |

Viewport

| Prop | Type | Default | Description | |---|---|---|---| | defaultViewport | Viewport | { x:0, y:0, zoom:1 } | Initial viewport position and zoom | | minZoom | number | 0.5 | Minimum zoom level | | maxZoom | number | 2 | Maximum zoom level | | translateExtent | CoordinateExtent | — | Restricts how far the viewport can be panned | | nodeExtent | CoordinateExtent | — | Restricts how far nodes can be dragged | | panning | boolean | true | Enable/disable viewport panning |

Behavior

| Prop | Type | Default | Description | |---|---|---|---| | nodesDraggable | boolean | true | Allow nodes to be dragged | | elevateNodesOnSelect | boolean | — | Raise selected nodes above others | | autoPanOnNodeDrag | boolean | true | Auto-pan when dragging a node near the edge | | autoPanOnConnect | boolean | true | Auto-pan when dragging a connection near the edge | | onlyRenderVisibleElements | boolean | false | Skip rendering off-screen nodes and edges | | multiSelectionKeyCode | KeyCode | 'Meta' | Key to hold for multi-select | | dragSelectionKeyCode | KeyCode | 'Shift' | Key to hold for drag-selection box | | noDragClassName | string | 'nodrag' | Elements with this class won't trigger node drag | | noPanClassName | string | 'nopan' | Elements with this class won't trigger panning | | smoothStep | boolean | — | Use smooth corners on step edges | | centerStep | boolean | — | Center the step path between source and target | | gridStep | GridStep | — | Snap nodes to a grid while dragging |


TypeScript Generics

v0.10.0 improved generic inference so that custom Node and Edge types flow through hooks and the ReactDiagram component without manual casting.

import ReactDiagram, {
  useNodesState,
  useEdgesState,
  addEdge,
  Node,
  Edge,
} from 'react-cosmos-diagram';
import { useCallback } from 'react';
import type { Connection } from 'react-cosmos-diagram';

// 1. Define typed Node and Edge
type AppNode = Node<{ label: string; color: string }, 'colored'>;
type AppEdge = Edge<{ weight: number }, 'weighted'>;

const initialNodes: AppNode[] = [
  { id: '1', type: 'colored', position: { x: 0, y: 0 }, data: { label: 'A', color: 'red' } },
  { id: '2', type: 'colored', position: { x: 200, y: 0 }, data: { label: 'B', color: 'blue' } },
];

const initialEdges: AppEdge[] = [
  { id: 'e1-2', source: '1', target: '2', data: { weight: 5 } },
];

export default function TypedDiagram() {
  // 2. Hooks infer AppNode / AppEdge from initial values
  const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
  const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);

  const onConnect = useCallback(
    (params: Connection) =>
      setEdges((eds) => addEdge({ ...params, data: { weight: 1 } }, eds)),
    [],
  );

  // 3. ReactDiagram<AppNode, AppEdge> is inferred — nodeTypes and event handlers
  //    are fully typed without explicit generics.
  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <ReactDiagram
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        onNodeClick={(_e, node) => console.log(node.data.color)} // typed!
      />
    </div>
  );
}

Credits

Under the hood, react-cosmos-diagram depends on these great libraries:

  • d3-zoom — zoom, pan and drag interactions
  • d3-drag — node dragging
  • zustand — internal state management

License

React Cosmos Diagram is MIT licensed.