awesome-decomposition-tree
v0.1.1
Published
Enterprise-grade Decomposition Tree component for React.
Maintainers
Keywords
Readme
awesome-decomposition-tree
Enterprise-grade Decomposition Tree for React. An interactive, Power BI–style decomposition experience with draggable nodes, zoom/pan, a synchronized Root Cause Analysis (RCA) bar chart, contribution analysis, and variance mode — all styles are injected at runtime, so no CSS import is needed.
Features
- Horizontal connector-based tree — nodes laid out left-to-right with smooth cubic Bézier connectors
- Expand / collapse — click the ⊕/⊖ button on any node; supports expanding children or all descendants
- Drag nodes — reposition any node (and its subtree) freely; positions persisted to
localStorage - Zoom & pan — mouse wheel to zoom (60%–200%), click-and-drag to pan the canvas
- RCA bar chart — synchronized with tree selection; click any RCA bar to focus and scroll the tree to that node
- Contribution % — each node shows its percentage of its parent's value
- Variance mode — renders
actual − budgetwith positive/negative colouring - Auto-refresh — polling interval for live REST API data
- Toolbar controls — tree height/width/bar-height sliders, label position, zoom, expand-all / collapse-all, RCA panel toggle
- Self-contained styles — CSS Module replaced with a runtime-injected
<style>tag; zero CSS imports required - ESM + CJS dual build — works in Vite, Next.js, CRA, and any modern bundler
- Zero runtime dependencies beyond React
Installation
npm install awesome-decomposition-tree
# or
yarn add awesome-decomposition-tree
# or
pnpm add awesome-decomposition-treePeer dependencies — React ≥ 17 and ReactDOM ≥ 17 must already be installed.
Quick Start
No CSS import needed — styles are injected automatically.
import AwesomeDecompositionTree from 'awesome-decomposition-tree';
var data = {
label: 'Revenue',
value: 10000000,
children: [
{
label: 'North',
value: 4000000,
children: [
{ label: 'Product A', value: 2000000 },
{ label: 'Product B', value: 2000000 },
],
},
{ label: 'South', value: 3000000 },
{ label: 'West', value: 3000000 },
],
};
export default function App() {
return (
<div style={{ padding: 24 }}>
<AwesomeDecompositionTree
title="Revenue Drivers"
data={data}
autoExpandLevel={2}
highlightLargest
onDrill={function(node) { console.log('Drill', node); }}
/>
</div>
);
}Both import styles work:
// Default import (recommended)
import AwesomeDecompositionTree from 'awesome-decomposition-tree';
// Named import
import { AwesomeDecompositionTree } from 'awesome-decomposition-tree';Props
| Prop | Type | Default | Description |
|---|---|---|---|
| data | object | built-in demo | Static tree data object. Takes precedence over jsonEndpoint and apiEndpoint. |
| apiEndpoint | string | "" | REST endpoint that returns tree data. Used only when data is not provided. |
| jsonEndpoint | string | "/decomposition-tree.json" | JSON file path. Used when neither data nor apiEndpoint is provided. |
| autoExpandLevel | number | 1 | Number of depth levels to auto-expand on load. 0 = root only, 2 = root + 2 levels deep. |
| autoRefresh | number | 0 | Polling interval in ms for apiEndpoint. 0 disables polling. |
| title | string | "Decomposition Tree" | Card heading shown in the toolbar. |
| theme | "light" | "light" | Visual theme. Currently "light" is supported. |
| currency | string | "USD" | ISO currency code used for value formatting (e.g. "EUR", "GBP"). |
| currencyLocale | string | "en-US" | BCP 47 locale for Intl.NumberFormat (e.g. "de-DE", "ja-JP"). |
| highlightLargest | boolean | true | Adds a gold glow border to the largest-value sibling node at each level. |
| mode | "actual" \| "budget" \| "forecast" \| "variance" | "actual" | Scenario mode. "variance" renders node.value − node.budget with +/− colouring. |
| onDrill | (node) => void | — | Callback fired when a node bar is clicked. Receives the raw node object. |
| layoutStorageKey | string | "awesome-decomposition-tree-layout" | localStorage key for persisting dragged node positions. Pass "" to disable. |
Data Format
Each node in the tree is a plain object:
{
label: string, // required — display name
value: number, // required — actual value
budget: number, // optional — used in "budget" and "variance" modes
forecast: number, // optional — used in "forecast" mode
children: [ ...nodes ] // optional — child nodes
}Leaf nodes (no children) are rendered as terminal bars with no expand button.
Examples
Example 1 — Revenue breakdown, deep auto-expand
import AwesomeDecompositionTree from 'awesome-decomposition-tree';
var data = {
label: 'Total Revenue FY2025',
value: 20000000,
children: [
{
label: 'Asia',
value: 9000000,
children: [
{
label: 'India',
value: 5000000,
children: [
{
label: 'Product A',
value: 3000000,
children: [
{
label: 'Online',
value: 1800000,
children: [
{ label: 'Amit Sharma', value: 900000 },
{ label: 'Neha Verma', value: 900000 },
],
},
{
label: 'Retail',
value: 1200000,
children: [
{ label: 'Raj Patel', value: 600000 },
{ label: 'Priya Nair', value: 600000 },
],
},
],
},
],
},
],
},
{
label: 'Europe',
value: 11000000,
children: [
{
label: 'Germany',
value: 6000000,
children: [
{
label: 'Product B',
value: 6000000,
children: [
{
label: 'Distributor',
value: 6000000,
children: [
{ label: 'Max Müller', value: 3000000 },
{ label: 'Lena Fischer', value: 3000000 },
],
},
],
},
],
},
],
},
],
};
export default function RevenueTree() {
return (
<div style={{ padding: 24, background: '#f0f2f7', minHeight: '100vh' }}>
<AwesomeDecompositionTree
title="Total Revenue FY2025"
data={data}
autoExpandLevel={3}
highlightLargest
onDrill={function(node) { console.log('Drill node:', node); }}
/>
</div>
);
}Example 2 — Manufacturing cost, variance mode
Uses budget on each node so variance mode can compute actual − budget per node.
import AwesomeDecompositionTree from 'awesome-decomposition-tree';
var data = {
label: 'Total Manufacturing Cost Q4',
value: 15000000,
budget: 13000000,
children: [
{
label: 'Plant A',
value: 8000000,
budget: 7000000,
children: [
{
label: 'Assembly',
value: 4000000,
budget: 3500000,
children: [
{
label: 'Raw Materials',
value: 2500000,
budget: 2000000,
children: [
{
label: 'Steel',
value: 1500000,
budget: 1200000,
children: [
{ label: 'Vendor X', value: 800000, budget: 600000 },
{ label: 'Vendor Y', value: 700000, budget: 600000 },
],
},
],
},
],
},
],
},
{
label: 'Plant B',
value: 7000000,
budget: 6000000,
children: [
{
label: 'Packaging',
value: 3000000,
budget: 2500000,
children: [
{
label: 'Labor',
value: 2000000,
budget: 1800000,
children: [
{
label: 'Overtime',
value: 1200000,
budget: 900000,
children: [
{ label: 'Shift 1', value: 600000, budget: 450000 },
{ label: 'Shift 2', value: 600000, budget: 450000 },
],
},
],
},
],
},
],
},
],
};
export default function ManufacturingTree() {
return (
<div style={{ padding: 24, background: '#f0f2f7', minHeight: '100vh' }}>
<AwesomeDecompositionTree
title="Manufacturing Cost Drivers"
data={data}
autoExpandLevel={3}
mode="variance"
highlightLargest
layoutStorageKey="manufacturing-layout"
onDrill={function(node) { console.log('Drill node:', node); }}
/>
</div>
);
}Example 3 — Live REST API with auto-refresh and onDrill routing
import { useState } from 'react';
import AwesomeDecompositionTree from 'awesome-decomposition-tree';
export default function LiveTree() {
var [lastDrill, setLastDrill] = useState(null);
return (
<div style={{ padding: 24, background: '#f0f2f7', minHeight: '100vh' }}>
{lastDrill && (
<div style={{
marginBottom: 16, padding: '10px 16px', borderRadius: 10,
background: '#fff', border: '1px solid #e2e8f0',
fontFamily: 'monospace', fontSize: 12,
}}>
Last drill: <strong>{lastDrill.label}</strong> — value: {lastDrill.value?.toLocaleString()}
</div>
)}
<AwesomeDecompositionTree
title="Live Revenue Drivers"
apiEndpoint="/api/revenue-tree"
autoExpandLevel={1}
autoRefresh={60000}
currency="EUR"
currencyLocale="de-DE"
highlightLargest
onDrill={function(node) {
setLastDrill(node);
// navigate to a detail page:
// window.location.href = '/analysis/' + encodeURIComponent(node.label);
}}
/>
</div>
);
}The /api/revenue-tree endpoint should return either the root node object directly, or wrapped in a data key:
{
"data": {
"label": "Revenue",
"value": 10000000,
"children": [ ... ]
}
}Interactions
| Interaction | Action |
|---|---|
| Click ⊕/⊖ | Expand or collapse the node's children (or all descendants, depending on toolbar setting) |
| Click node bar | Focus node in RCA panel + expand/collapse; fires onDrill callback |
| Click RCA bar | Focus that node in the tree, scroll canvas to it |
| Drag node | Reposition node and its entire subtree; position saved to localStorage |
| Mouse wheel on canvas | Zoom in / out (60%–200%) |
| Click-drag canvas | Pan the tree viewport |
| Toolbar sliders | Adjust tree height, tree width, bar height |
| Label position radio | Move labels: above bar (top), before bar (start), after bar (end), below bar (under) |
| Expand all / Collapse all | Bulk expand or collapse the entire tree |
| Show RCA checkbox | Toggle the Root Cause Analysis panel |
License
MIT
