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

@bardioc/org-chart

v0.1.0

Published

Configurable, read-only React org chart. Every visual aspect is driven by a single config object.

Readme

@bardioc/org-chart

A read-only, deeply configurable React org chart. Every visual — canvas, node, connector, connector label, chrome — is driven by one config object, so the same component covers a bare name-and-hairline diagram and a fully dressed gradient-and-avatar chart.

In dev mode the repo is an experimental lab: a live control panel that edits every config field against three sample datasets.

npm install
npm run dev        # lab at http://localhost:5273
npm run build      # publishable library into dist/
npm run build:lab  # static build of the lab

The lab keeps your configuration and any custom presets in localStorage (zustand + persist), so a reload restores the design you were working on. The Preset tab holds the editor: save/rename/delete custom presets, export and import them as JSON, and paste a config in directly — either as a patch over the defaults (what you would put in the config prop) or as the fully resolved object. Dataset and filters deliberately start fresh on every load.

Scope

Visualisation only. There is no dragging, no inline editing, no forms. Nodes support hover, selection, collapse/expand, search dimming and pan/zoom.

Usage

import { OrgChart } from '@bardioc/org-chart';
import '@bardioc/org-chart/styles.css';

<OrgChart
  data={people}                       // flat list with parentId, or a nested root
  config={{ layout: { orientation: 'left-right' } }}
/>;

The library is data-model agnostic. Point it at any object shape via accessors, and reference arbitrary fields from the node config:

<OrgChart
  data={employees}
  accessors={{ id: 'employeeId', parentId: 'managerId', kind: 'type' }}
  config={{
    node: {
      fields: [
        { key: 'name', value: '{firstName} {lastName}' },
        { key: 'title', value: 'jobTitle' },
        { key: 'email', visible: false },
      ],
    },
  }}
/>

Field value is either a dot path (profile.jobTitle) or a template ({firstName} {lastName}).

Filtering by kind

filter keeps only the nodes it accepts and re-parents the survivors onto the nearest kept ancestor, so hiding every org unit collapses the chart down to a pure reporting line instead of tearing the hierarchy apart:

const onlyPeople = useCallback((d: Person) => d.kind === 'employee', []);

<OrgChart data={people} filter={onlyPeople} />;

Memoise the predicate — a new function identity re-runs the layout.

Refitting after a swap

Pass fitKey; when it changes the chart refits once the layout settles. This is more reliable than calling fitView() after changing data, because the request is registered during render and cannot race the measure pass:

<OrgChart data={data} fitKey={`${datasetId}|${presetId}`} />

Configuration

config is a deep-partial of OrgChartConfig, merged over the defaults.

| Group | Covers | | --- | --- | | canvas | fill (solid/gradient), pattern (dots, grid, cross, lines), frame, vignette, fit padding, cursors | | layout | mode (tree or radial), orientation (4), fixed vs measured sizing, sibling/subtree/level/root gaps, per-level or uniform rows, parent alignment, leaf alignment, sibling sorting, indented leaf stacking | | layout.radial | angular window (start/end), inner radius, ring gap, centred root, node and subtree arc gaps, fill-the-window, card rotation, ring growth cap | | node | fill, border, shadow, padding, accent bar or ring, avatar (initials/image/icon), ordered content rows, divider, descendant-count badge, collapse toggle, hover/selected/dimmed states | | edge | path shape, corner radius, curvature, shared trunk position, stroke, dash, flow animation, gradient, width-by-subtree-size, start/end markers, labels, hover/dimmed states | | controls | visibility, corner, direction, which buttons, zoom readout | | minimap | visibility, corner, size, colours, interactivity | | interaction | pan/zoom rules, fit behaviour, collapsibility, initial depth, selection, path highlighting, dimming (by ancestors or by neighbours), tooltip |

Colours are ColorSource values, resolved per node — fixed, by kind, by depth, or read from a datum field. Defaults reference the shadcn/bardioc CSS custom properties (var(--card), var(--chart-1), …), so the chart follows the host theme in light and dark with no extra wiring.

node.byKind / node.byDepth (and the edge equivalents) layer scoped overrides on top of the base config.

Presets

PRESETS exports eight ready-made configs, each carrying a category (tree or radial) and tags so a picker can group them: default, minimal, maximal, blueprint, roster, neon, orbit and graph. Use them directly or as a starting point:

import { OrgChart, PRESET_BY_ID } from '@bardioc/org-chart';

<OrgChart data={people} config={PRESET_BY_ID.get('minimal')!.config} />;

Custom nodes

<OrgChart
  data={people}
  config={{ node: { variant: 'custom' } }}
  renderNode={({ node, isSelected }) => <MyCard datum={node.datum} active={isSelected} />}
/>

Layout still measures the rendered element, so custom nodes of varying size do not overlap.

Imperative handle

const chart = useRef<OrgChartHandle>(null);
chart.current?.fitView();
chart.current?.centerOnNode('user-42', 1);
chart.current?.collapseAll();

fitView keeps re-fitting until the measure/relayout cycle settles, so it is safe to call immediately after changing config or data. toSVG() returns standalone SVG markup — real edge geometry plus a box and primary label per node; rich card styling is not reproduced.

How it works

  • Layout is a tidy tree via d3-hierarchy. Cross-axis units are pixels and separation returns the required centre-to-centre distance, which is what makes variable-width nodes overlap-free. Multiple roots hang off a virtual root that is dropped afterwards.
  • Radial mode maps depth to a ring. A node's angular footprint depends on its radius, so separation returns requiredArc / radius; when the inner rings still cannot fit their children, the ring spacing is grown iteratively (capped by maxRingGrowth) rather than compressing nodes into each other. A full 360° window is treated as cyclic, so the first and last node keep a gap. Note that ring spacing has to clear the card's width, because near the left and right of the circle the radial direction runs horizontally — narrow cards, or rotateNodes, are what keep a radial chart compact.
  • Sizing defaults to measured: nodes report their box from a layout effect and the tree is recomputed before paint. fixed ignores the measurement cache instead of clearing it, so toggling back does not re-measure from scratch. The cache is deliberately never invalidated on config change — a ResizeObserver already tracks the truth, and clearing used to race with the child's own report, stranding the layout on the fallback size.
  • Edges are SVG paths in a layer behind (or above) the nodes. Sibling elbows share a trunk coordinate so they bend on exactly the same line; fork shares one trunk per depth, giving the classic bus even when parent cards differ in height. Labels are HTML, positioned along the path polyline.
  • Pan/zoom is d3-zoom applied to a transformed wrapper. A click that ends a drag is swallowed, so panning never changes the selection.

Migration note

The sample dataset in src/lab/sample-data.ts is the Sunshine Company org from the previous org-builder React Flow chart, re-expressed in this package's generic flat shape. The domain types (Organization, OrganizationalUnit, Team, User) are deliberately not baked into the library.