Skip to content

Introduction

Radial layouts for ego networks

An ego graph puts one node — the customer, account, or person the view is about — at the centre. ego-graph lays its neighbourhood around it, with branch folding and sensible treatment for relationships that should not control the hierarchy.

ego-graph is deliberately small:

  • The core accepts plain { id, width, height } nodes and { source, target } edges.
  • It is synchronous, renderer-agnostic, and returns top-left positions in a Map.
  • The react-flow entry point contains only pure adapters; it does not import React.

This is the canonical executable sample at examples/polar-petal-playground.tsx; the page imports its source rather than maintaining a second copy. Edit the code and the preview reruns.

import { polarPetal } from 'ego-graph';
import './styles.css';

const nodes = [
  { id: 'ada', label: 'Ada', width: 120, height: 44 },
  { id: 'lin', label: 'Lin', width: 120, height: 44 },
  { id: 'maria', label: 'Maria', width: 120, height: 44 },
  { id: 'kofi', label: 'Kofi', width: 120, height: 44 },
  { id: 'noor', label: 'Noor', width: 120, height: 44 },
];

const edges = [
  { source: 'ada', target: 'lin' },
  { source: 'ada', target: 'maria' },
  { source: 'ada', target: 'kofi' },
  { source: 'maria', target: 'noor' },
];

const positions = polarPetal({ nodes, edges, root: 'ada' });
const byId = new Map(nodes.map((node) => [node.id, node]));
const center = (id: string) => {
  const position = positions.get(id)!;
  const node = byId.get(id)!;
  return { x: position.x + node.width / 2 + 280, y: position.y + node.height / 2 + 190 };
};

export default function App() {
  return (
    <svg viewBox="0 0 560 1000" role="img" aria-label="An ego graph laid out with polarPetal">
      <rect width="560" height="1000" fill="#f8fafc" />
      {edges.map((edge) => {
        const from = center(edge.source);
        const to = center(edge.target);
        return <line key={`${edge.source}-${edge.target}`} x1={from.x} y1={from.y} x2={to.x} y2={to.y} stroke="#94a3b8" strokeWidth="2" />;
      })}
      {nodes.map((node) => {
        const position = positions.get(node.id)!;
        return (
          <g key={node.id} transform={`translate(${position.x + 280}, ${position.y + 190})`}>
            <rect width={node.width} height={node.height} rx="10" fill={node.id === 'ada' ? '#3157b7' : 'white'} stroke="#3157b7" />
            <text x={node.width / 2} y="28" textAnchor="middle" fill={node.id === 'ada' ? 'white' : '#172554'} fontSize="14">{node.label}</text>
          </g>
        );
      })}
    </svg>
  );
}

Layout Best for Extra dependency
polarPetal Wide, shallow graphs and the smallest bundle None
radialDagre Deep or uneven branches @dagrejs/dagre
sectoredDagre Very wide, shallow graphs @dagrejs/dagre

Start with polarPetal when a pure radial layout is enough. Use the Dagre variants when ranked subtrees need stricter internal structure.

LAYOUT_NAMES lists these as strings; LayoutName is the union type. Importing either does not load the implementations — see Layouts & options.