awesome-kpi-comparator
v0.1.0
Published
Enterprise-grade KPI Comparator component for React — actual vs budget/forecast/previous with trend analysis, anomaly detection, ranking, sparklines, and a settings drawer.
Maintainers
Keywords
Readme
awesome-kpi-comparator
Enterprise-grade KPI Comparator component for React. Compares actual vs budget / forecast / previous / benchmark across multiple KPIs with variance badges, sparkline trend indicators, anomaly detection, row ranking, a comparison bar chart in 4 layouts, scenario toggling, and a live settings drawer — all styles injected at runtime so no CSS import is needed.
Features
- 4 comparison modes —
budget,forecast,previous,benchmark(auto-detects which are available per item); switchable at runtime from the settings drawer - 4 bar layouts —
horizontal(default tracks),vertical(column bars),stacked(100% stacked),premium(gradient card) - 3 trend modes —
lastTwo,movingAverage,linearRegression— computed fromtrendDatatime series - Sparkline — inline SVG polyline driven by
trendData - Variance badge —
+4.5% / +$22Kcoloured by RAG tone - Comparison bar — proportional bars coloured by status with a hover tooltip showing value, variance, and trend
- Scenario toggle — switch between
actual,budget, andforecastscenarios in the header - Anomaly detection — 3 methods (
variance,growth,benchmark) with configurable thresholds; anomalous cards show an "Anomaly" badge - Ranking — auto-ranks by
variancePercent; top/bottom performer shown in the footer - Settings drawer — gear icon opens a panel to switch comparison mode, layout, theme, trend mode/window, and apply filters
- Filter chips — show/hide cards by status (top, good, warning, bad, anomaly)
- 2 built-in themes —
corporate(blue accent) andemerald(green accent) - REST / JSON file / inline data —
dataprop,jsonFilePath, orapiEndpointwith retry - Self-contained — all CSS injected at runtime; no stylesheet import required
- ESM + CJS dual build — Vite, Next.js, CRA, any modern bundler
- Zero runtime dependencies beyond React
Installation
npm install awesome-kpi-comparator
# or
yarn add awesome-kpi-comparator
# or
pnpm add awesome-kpi-comparatorPeer dependencies — React ≥ 17 and ReactDOM ≥ 17 must already be installed.
Quick Start
No CSS import needed — styles are injected automatically.
import AwesomeKPIComparator from 'awesome-kpi-comparator';
var data = [
{ name: 'Revenue', actual: 5200000, budget: 5000000, previous: 4800000, trendData: [4.2,4.5,4.8,5.0,5.2] },
{ name: 'Gross Margin', actual: 0.42, budget: 0.40, previous: 0.38, trendData: [0.36,0.37,0.38,0.40,0.42], format: 'percent' },
{ name: 'New Customers',actual: 1240, budget: 1100, previous: 980, trendData: [820,900,980,1100,1240] },
];
export default function App() {
return (
<div style={{ padding: 32 }}>
<AwesomeKPIComparator data={data} />
</div>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| data | KPIItem[] | — | Inline data array. Takes precedence over jsonFilePath and apiEndpoint. |
| apiEndpoint | string | — | REST URL to fetch data from. |
| jsonFilePath | string | — | Path to a local JSON file (e.g. '/data/kpis.json'). |
| method | string | 'GET' | HTTP method for apiEndpoint. |
| headers | object | — | HTTP headers for apiEndpoint. |
| dataPath | string | — | Key to unwrap from a nested response (e.g. 'results'). |
| comparisonMode | 'budget' \| 'forecast' \| 'previous' \| 'benchmark' \| 'custom' | 'budget' | Which field to compare actual against. Changeable at runtime via the settings drawer. |
| layout | 'horizontal' \| 'vertical' \| 'stacked' \| 'premium' | 'premium' | Bar and card layout. Changeable at runtime. |
| scenario | 'actual' \| 'budget' \| 'forecast' | 'actual' | Initial scenario. If budget or forecast are present, a toggle appears in the header. |
| showTrend | boolean | true | Show trend arrow, growth %, and sparkline. |
| showRanking | boolean | true | Show rank badge on each card and top/bottom performer footer. |
| enableAnomalyDetection | boolean | false | Detect and badge anomalous KPIs. |
| anomalyMethod | 'variance' \| 'growth' \| 'benchmark' | 'variance' | Which metric to base anomaly detection on. |
| thresholds | { good: number, warning: number } | { good: 5, warning: 0 } | Variance % thresholds for RAG status. |
| theme | 'corporate' \| 'emerald' | 'corporate' | Initial colour theme. Changeable at runtime. |
| format | 'currency' \| 'number' \| 'percent' | 'currency' | Default value format (overridden per-item by item.format). |
| currency | string | 'USD' | ISO currency code for format="currency". |
| unitFormat | 'suffix' \| 'parenthetical' | 'suffix' | How units are appended to formatted values. |
| unitLabels | Record<string, string> | — | Map of unit codes to display labels (e.g. { 'hrs': 'hours' }). |
| trendMode | 'lastTwo' \| 'movingAverage' \| 'linearRegression' | 'lastTwo' | Trend calculation method. |
| trendWindow | number | 3 | Window size for movingAverage trend mode. |
| title | string | 'Executive KPI Comparator' | Card heading. |
| subtitle | string | 'Actual vs target across critical performance metrics' | Subheading. |
| onDrill | (kpi) => void | — | Called when a KPI card is clicked. Receives the fully enriched KPI object. |
KPIItem object
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | ✅ | Display name. Used as React key. |
| actual | number | ✅ | Actual value. Falls back to value or last trendData point. |
| budget | number | ➖ | Budget / target value. |
| forecast | number | ➖ | Forecast value. |
| previous | number | ➖ | Prior period value (for trend and previous comparison mode). |
| benchmark | number | ➖ | External benchmark value. |
| trendData | number[] | ➖ | Historical series (oldest → newest) for sparkline and trend calculation. |
| format | 'currency' \| 'number' \| 'percent' | ➖ | Per-item format override. |
| currency | string | ➖ | Per-item currency override. |
| unit | string | ➖ | Unit suffix (e.g. 'hrs', 'units'). |
Examples
Example 1 — Executive P&L dashboard (corporate theme, budget comparison, with anomaly detection)
import AwesomeKPIComparator from 'awesome-kpi-comparator';
var financialKPIs = [
{
name: 'Total Revenue',
actual: 5200000, budget: 5000000, forecast: 5150000, previous: 4800000,
trendData: [4200000, 4400000, 4600000, 4800000, 5000000, 5200000],
},
{
name: 'Gross Profit',
actual: 2184000, budget: 2000000, forecast: 2060000, previous: 1920000,
trendData: [1700000, 1760000, 1830000, 1920000, 2050000, 2184000],
},
{
name: 'EBITDA',
actual: 780000, budget: 850000, forecast: 820000, previous: 720000,
trendData: [600000, 640000, 680000, 720000, 770000, 780000],
},
{
name: 'Net Margin',
actual: 0.149, budget: 0.17, forecast: 0.16, previous: 0.15,
format: 'percent',
trendData: [0.143, 0.145, 0.147, 0.15, 0.148, 0.149],
},
{
name: 'Operating Costs',
actual: 3200000, budget: 3100000, forecast: 3140000, previous: 3050000,
trendData: [2900000, 2950000, 3000000, 3050000, 3120000, 3200000],
},
{
name: 'Cash Flow',
actual: 420000, budget: 500000, forecast: 480000, previous: 390000,
trendData: [310000, 340000, 360000, 390000, 400000, 420000],
},
];
export default function FinancialDashboard() {
return (
<div style={{ padding: 32, background: '#f4f6fb', minHeight: '100vh' }}>
<AwesomeKPIComparator
data={financialKPIs}
title="P&L Executive Summary — Q1 2026"
subtitle="Actual vs Budget with forecast overlay"
comparisonMode="budget"
layout="premium"
theme="corporate"
format="currency"
currency="USD"
showTrend={true}
showRanking={true}
enableAnomalyDetection={true}
anomalyMethod="variance"
thresholds={{ good: 5, warning: 0 }}
trendMode="lastTwo"
onDrill={function(kpi) { console.log('Drill into:', kpi.name); }}
/>
</div>
);
}Example 2 — Sales ops (emerald theme, previous comparison, moving-average trend)
Compares current period against the previous period, using a 3-point moving average to smooth the sparkline trend.
import AwesomeKPIComparator from 'awesome-kpi-comparator';
var salesKPIs = [
{
name: 'New Deals Closed',
actual: 148, budget: 130, previous: 112, forecast: 135,
trendData: [90, 98, 105, 112, 128, 148],
},
{
name: 'Pipeline Value',
actual: 3800000, budget: 3500000, previous: 3200000,
trendData: [2600000, 2800000, 3000000, 3200000, 3500000, 3800000],
},
{
name: 'Win Rate',
actual: 0.38, budget: 0.35, previous: 0.32,
format: 'percent',
trendData: [0.28, 0.29, 0.31, 0.32, 0.35, 0.38],
},
{
name: 'Avg Deal Size',
actual: 25675, budget: 26000, previous: 28571,
trendData: [24000, 25000, 26000, 28571, 27500, 25675],
},
{
name: 'Sales Cycle (days)',
actual: 34, budget: 30, previous: 38,
unit: 'days',
trendData: [42, 40, 38, 38, 36, 34],
},
{
name: 'Revenue per Rep',
actual: 185000, budget: 175000, previous: 160000,
trendData: [140000, 148000, 155000, 160000, 170000, 185000],
},
];
export default function SalesDashboard() {
return (
<div style={{ padding: 32, background: '#ecfdf3', minHeight: '100vh' }}>
<AwesomeKPIComparator
data={salesKPIs}
title="Sales Performance — Q1 2026"
subtitle="Current vs prior quarter across all sales metrics"
comparisonMode="previous"
layout="horizontal"
theme="emerald"
format="currency"
currency="USD"
showTrend={true}
showRanking={true}
enableAnomalyDetection={true}
anomalyMethod="growth"
thresholds={{ good: 10, warning: 0 }}
trendMode="movingAverage"
trendWindow={3}
onDrill={function(kpi) { alert('Selected: ' + kpi.name + ' — Score: ' + kpi.variancePercent.toFixed(1) + '%'); }}
/>
</div>
);
}Example 3 — HR operations (vertical layout, benchmark comparison, onDrill detail panel)
Compares each metric against an industry benchmark with vertical bar layout and a custom detail drawer.
import { useState } from 'react';
import AwesomeKPIComparator from 'awesome-kpi-comparator';
var hrKPIs = [
{
name: 'Employee Retention',
actual: 0.88, budget: 0.90, benchmark: 0.85, previous: 0.86,
format: 'percent',
trendData: [0.82, 0.83, 0.85, 0.86, 0.87, 0.88],
},
{
name: 'Time to Hire (days)',
actual: 28, budget: 25, benchmark: 32, previous: 31,
unit: 'days',
trendData: [38, 36, 34, 31, 30, 28],
},
{
name: 'Training Hours / Employee',
actual: 42, budget: 40, benchmark: 38, previous: 36,
unit: 'hrs',
trendData: [30, 32, 34, 36, 38, 42],
},
{
name: 'Engagement Score',
actual: 74, budget: 78, benchmark: 72, previous: 70,
trendData: [65, 67, 68, 70, 72, 74],
},
{
name: 'Absenteeism Rate',
actual: 0.032, budget: 0.025, benchmark: 0.04, previous: 0.038,
format: 'percent',
trendData: [0.045, 0.042, 0.040, 0.038, 0.035, 0.032],
},
{
name: 'Headcount',
actual: 842, budget: 850, benchmark: 900, previous: 810,
format: 'number',
trendData: [780, 790, 800, 810, 825, 842],
},
];
var drawerStyle = {
position: 'fixed', top: 0, right: 0, width: 340, height: '100vh',
background: '#fff', borderLeft: '1px solid #e4e7ec',
padding: 28, zIndex: 200, fontFamily: 'sans-serif', overflowY: 'auto',
};
export default function HRDashboard() {
var [selected, setSelected] = useState(null);
return (
<div style={{ padding: 32, background: '#f6f7fb', minHeight: '100vh' }}>
<AwesomeKPIComparator
data={hrKPIs}
title="HR Operations KPIs — Q1 2026"
subtitle="Actual vs industry benchmark"
comparisonMode="benchmark"
layout="vertical"
theme="corporate"
format="number"
showTrend={true}
showRanking={true}
enableAnomalyDetection={true}
anomalyMethod="benchmark"
thresholds={{ good: 5, warning: -5 }}
trendMode="linearRegression"
onDrill={function(kpi) { setSelected(kpi); }}
/>
{selected && (
<div style={drawerStyle}>
<button
onClick={function() { setSelected(null); }}
style={{ float: 'right', background: 'none', border: '1px solid #e4e7ec', borderRadius: 4, padding: '4px 10px', cursor: 'pointer' }}
>✕</button>
<h2 style={{ fontSize: 20, fontWeight: 700, marginBottom: 4 }}>{selected.name}</h2>
<p style={{ fontSize: 13, color: '#667085', marginBottom: 16 }}>
Status: <strong style={{ color: selected.status?.tone === 'good' ? '#1e9d57' : selected.status?.tone === 'bad' ? '#d24545' : '#e59f13' }}>
{selected.status?.label}
</strong>
</p>
<hr style={{ marginBottom: 16 }} />
<p style={{ fontSize: 13 }}>Actual: <strong>{String(selected.value)}</strong></p>
<p style={{ fontSize: 13 }}>Benchmark: <strong>{String(selected.comparisonValue)}</strong></p>
<p style={{ fontSize: 13 }}>
Variance: <strong>{Number.isFinite(selected.variancePercent) ? (selected.variancePercent >= 0 ? '+' : '') + selected.variancePercent.toFixed(1) + '%' : '--'}</strong>
</p>
{selected.trend && (
<p style={{ fontSize: 13 }}>
Trend: <strong>{selected.trend.direction}</strong>
{Number.isFinite(selected.trend.growthPercent) ? ' (' + (selected.trend.growthPercent >= 0 ? '+' : '') + selected.trend.growthPercent.toFixed(1) + '%)' : ''}
</p>
)}
{selected.anomaly?.isAnomaly && (
<p style={{ fontSize: 13, color: '#b45309', marginTop: 8 }}>
⚠ Anomaly: {selected.anomaly.reason}
</p>
)}
{Number.isFinite(selected.rank) && (
<p style={{ fontSize: 13, marginTop: 8 }}>Rank: #{selected.rank}</p>
)}
</div>
)}
</div>
);
}Themes
| Theme | Accent | Background |
|---|---|---|
| corporate | #2f6fed (blue) | #f4f6fb |
| emerald | #10b981 (green) | #ecfdf3 |
Switch at runtime from the settings drawer or pass theme as a prop.
Trend Modes
| Mode | Description |
|---|---|
| lastTwo | Compares the last two trendData points |
| movingAverage | Smoothes using a rolling average of trendWindow points |
| linearRegression | Fits a best-fit line and uses the slope direction |
License
MIT
