awesome-heatmap
v0.1.1
Published
Zero-dependency Heatmap Grid component for React — auto-extracts numeric matrices from any data shape, 8 color scales, 3 themes, IDB prefs persistence.
Maintainers
Readme
awesome-heatmap
A zero-dependency heatmap grid React component. Feed it any data shape — arrays of objects, REST API URLs, GeoJSON, CSV-style nested arrays — and it auto-extracts a numeric matrix, renders colour-coded cells, and gives you a full toolbar with colour scale picker, row sorting, cell size slider, theme switcher, and a floating tooltip. All styles are injected at runtime, so no CSS import is needed.
Features
- Universal data ingestion — arrays of objects, URL strings (JSON / REST API), GeoJSON FeatureCollections, CSV-style
[[headers], [row], ...], envelope-wrapped responses ({ data: [...] },{ results: [...] }, etc.) - Auto matrix extraction — detects numeric columns (≥ 75% parseable values), picks the first non-numeric column as the row label, flattens nested objects up to 3 levels deep
- 8 colour scales — Plasma, Inferno, Viridis, Magma, Thermal, Ice, Red→Blue, Spectral — all using perceptually uniform control-point interpolation
- 3 built-in themes —
dark,light,ember— all colours driven by CSS custom properties - Toolbar controls — colour scale dropdown with preview dots, row sort (original / A→Z / Z→A / sum↑ / sum↓), cell size slider (16–80px), show/hide cell values toggle, theme switcher
- Column header mini-bars — each column header shows a colour-coded bar for its average value relative to the global range
- Sticky row labels + column headers — both stay in view while scrolling a large matrix
- Floating tooltip — shows row label, column label, formatted value, and a colour-matched progress bar
- Summary footer — global Σ, x̄, max, min, and cell count
- IndexedDB preference persistence — theme, scale, sort, cell size, and show-values are saved per
storageKeyand restored on next load - Self-contained styles — CSS Module replaced with a runtime-injected
<style>tag; no stylesheet import required - ESM + CJS dual build — works in Vite, Next.js, CRA, and any modern bundler
- Zero runtime dependencies beyond React
Installation
npm install awesome-heatmap
# or
yarn add awesome-heatmap
# or
pnpm add awesome-heatmapPeer dependencies — React ≥ 18 and ReactDOM ≥ 18 must already be installed.
Quick Start
No CSS import needed — styles are injected automatically.
import { AwesomeHeatmap } from 'awesome-heatmap';
var data = [
{ city: 'New York', jan: 2, feb: 4, mar: 10, apr: 17, may: 23, jun: 28 },
{ city: 'London', jan: 5, feb: 6, mar: 9, apr: 13, may: 17, jun: 20 },
{ city: 'Tokyo', jan: 6, feb: 7, mar: 11, apr: 17, may: 22, jun: 26 },
{ city: 'Sydney', jan: 26, feb: 26, mar: 23, apr: 18, may: 14, jun: 11 },
{ city: 'Dubai', jan: 19, feb: 21, mar: 25, apr: 33, may: 38, jun: 40 },
];
export default function App() {
return (
<div style={{ width: '100%', height: '500px' }}>
<AwesomeHeatmap
data={data}
title="Monthly Temperatures (°C)"
/>
</div>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| data | Array \| string | [] | Data to visualise. Pass an array of objects, or a URL string pointing to a JSON endpoint. See Data formats below. |
| title | string | 'Heatmap' | Title shown in the toolbar. |
| theme | 'dark' \| 'light' \| 'ember' | 'dark' | Initial colour theme. User can switch at runtime via the toolbar. |
| colorScale | string | 'plasma' | Initial colour scale key. Must be one of the COLOR_SCALES keys. |
| storageKey | string | 'awesome-heatmap' | IndexedDB key used to persist and restore user preferences. Pass a unique value per instance. Set to '' to disable persistence. |
| height | string \| number | '100%' | CSS height of the component. Numbers are treated as px. |
| onThemeChange | (theme: string) => void | — | Called whenever the user switches theme, with the new theme name. |
Named Exports
COLOR_SCALES
An object mapping scale keys to display labels. Use this to build your own scale picker or to pass a valid value to the colorScale prop.
import { COLOR_SCALES } from 'awesome-heatmap';
console.log(COLOR_SCALES);
// {
// plasma: "Plasma",
// inferno: "Inferno",
// viridis: "Viridis",
// magma: "Magma",
// thermal: "Thermal",
// ice: "Ice",
// rdbu: "Red → Blue",
// spectral: "Spectral",
// }PAGE_BG
An object mapping theme names to their recommended outer page background colours. Useful for styling the page wrapper to match the heatmap.
import { PAGE_BG } from 'awesome-heatmap';
console.log(PAGE_BG);
// { dark: "#04060c", light: "#e0e5f0", ember: "#070402" }Data Formats
AwesomeHeatmap accepts data in many shapes and normalises them automatically.
Array of objects (recommended)
var data = [
{ product: 'Shoes', mon: 120, tue: 95, wed: 143 },
{ product: 'Bags', mon: 88, tue: 112, wed: 76 },
{ product: 'Hats', mon: 54, tue: 67, wed: 91 },
];The first non-numeric column (product) becomes the row label. All numeric columns become the heatmap columns.
URL string — JSON endpoint
<AwesomeHeatmap data="https://api.example.com/metrics" title="Server Metrics" />The component fetches the URL, then unwraps common envelope keys (data, results, items, rows, etc.) automatically.
Envelope-wrapped response
{ "results": [ { "region": "APAC", "q1": 4.2, "q2": 5.1, "q3": 3.8 } ] }CSV-style nested arrays (first row = headers)
var data = [
['city', 'q1', 'q2', 'q3', 'q4'],
['Berlin', 8.2, 14.1, 19.3, 9.5 ],
['Paris', 9.1, 15.0, 20.2, 10.1],
['Madrid', 11.5, 17.3, 24.8, 13.2],
];GeoJSON FeatureCollection
var geojson = {
type: 'FeatureCollection',
features: [
{ type: 'Feature', geometry: { type: 'Point', coordinates: [0,0] }, properties: { name: 'A', value: 42 } },
],
};Properties are extracted automatically; the geometry is ignored.
Examples
Example 1 — Dark theme with Viridis scale and persistence disabled
import { AwesomeHeatmap } from 'awesome-heatmap';
var salesData = [
{ rep: 'Alice', jan: 42000, feb: 38000, mar: 51000, apr: 47000, may: 55000, jun: 61000 },
{ rep: 'Bob', jan: 31000, feb: 44000, mar: 39000, apr: 52000, may: 48000, jun: 43000 },
{ rep: 'Carol', jan: 58000, feb: 62000, mar: 55000, apr: 67000, may: 71000, jun: 69000 },
{ rep: 'David', jan: 27000, feb: 31000, mar: 35000, apr: 29000, may: 38000, jun: 42000 },
{ rep: 'Eva', jan: 49000, feb: 53000, mar: 61000, apr: 58000, may: 64000, jun: 72000 },
{ rep: 'Frank', jan: 36000, feb: 41000, mar: 38000, apr: 44000, may: 46000, jun: 50000 },
];
export default function SalesDashboard() {
return (
<div style={{ width: '100%', height: '480px', borderRadius: 16, overflow: 'hidden' }}>
<AwesomeHeatmap
data={salesData}
title="Sales by Rep — H1"
theme="dark"
colorScale="viridis"
storageKey=""
/>
</div>
);
}Example 2 — Light theme, live data from a REST API, onThemeChange callback
import { useState } from 'react';
import { AwesomeHeatmap, PAGE_BG } from 'awesome-heatmap';
export default function LiveMetrics() {
var [theme, setTheme] = useState('light');
return (
<div style={{ background: PAGE_BG[theme], minHeight: '100vh', padding: 32, transition: 'background 0.4s' }}>
<div style={{ width: '100%', height: '600px', borderRadius: 20, overflow: 'hidden', boxShadow: '0 4px 24px rgba(0,0,0,0.12)' }}>
<AwesomeHeatmap
data="https://jsonplaceholder.typicode.com/users"
title="User Metrics (live)"
theme={theme}
colorScale="thermal"
storageKey="live-metrics-heatmap"
onThemeChange={function(t) { setTheme(t); }}
/>
</div>
</div>
);
}Example 3 — Ember theme, CSV-style array, controlled height, multiple instances
import { AwesomeHeatmap, COLOR_SCALES } from 'awesome-heatmap';
var weeklyTraffic = [
['page', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'],
['Home', 9420, 8830, 9210, 9640, 11200, 8100, 7300],
['Products', 4210, 4650, 4120, 4980, 5340, 3800, 3200],
['Blog', 2100, 2340, 2890, 2670, 2450, 3100, 2900],
['Pricing', 1840, 2010, 1950, 2200, 2680, 1400, 1100],
['Contact', 820, 910, 880, 970, 1050, 640, 580 ],
['Docs', 3400, 3800, 4200, 4100, 3900, 2200, 1900],
];
var teamPerformance = [
['team', 'sprints_completed', 'bugs_closed', 'prs_merged', 'reviews_done', 'deploys'],
['Frontend', 8, 24, 31, 42, 12],
['Backend', 6, 38, 22, 28, 9],
['Mobile', 7, 19, 17, 24, 6],
['DevOps', 5, 11, 8, 15, 28],
['Data', 4, 14, 12, 18, 4],
];
export default function MultiHeatmap() {
return (
<div style={{ background: '#070402', minHeight: '100vh', padding: 32, display: 'flex', flexDirection: 'column', gap: 24 }}>
<div style={{ height: 420, borderRadius: 16, overflow: 'hidden' }}>
<AwesomeHeatmap
data={weeklyTraffic}
title="Weekly Page Traffic"
theme="ember"
colorScale="plasma"
storageKey="traffic-heatmap"
/>
</div>
<div style={{ height: 360, borderRadius: 16, overflow: 'hidden' }}>
<AwesomeHeatmap
data={teamPerformance}
title="Team Performance — Current Sprint"
theme="ember"
colorScale="inferno"
storageKey="team-heatmap"
/>
</div>
{/* Display available colour scales */}
<div style={{ color: '#a0836a', fontFamily: 'monospace', fontSize: 12 }}>
Available scales: {Object.keys(COLOR_SCALES).join(', ')}
</div>
</div>
);
}No separate CSS file is produced. All styles are bundled as a JavaScript string and injected into <head> when the component first mounts.
Toolbar Controls
| Control | Description | |---|---| | Cell size slider | Resize all cells from 16px to 80px. Values are shown inside cells when size ≥ 28px and the eye toggle is on. | | Eye toggle | Show or hide numeric values inside each cell. | | Colour scale dropdown | Switch between the 8 available scales. Each option shows a preview strip. | | Sort dropdown | Sort rows by: original order, label A→Z / Z→A, or row sum ascending / descending. | | Theme dots | Switch between Dark (blue), Light (indigo), and Ember (orange) themes. |
CSS Custom Properties
All visual tokens are CSS custom properties set on the root element. Override them per-instance via a wrapper class:
.my-heatmap-wrapper {
--hm-bg: #0a0a0a;
--hm-surface: #141414;
--hm-surface2: #1c1c1c;
--hm-surface3: #242424;
--hm-border: rgba(255,255,255,0.06);
--hm-border2: rgba(255,255,255,0.12);
--hm-text1: #f0f0f0;
--hm-text2: #888;
--hm-text3: #444;
--hm-accent: #7c3aed;
--hm-accentbg: rgba(124,58,237,0.12);
--hm-shadow: 0 8px 40px rgba(0,0,0,0.7);
}Then wrap the component:
<div className="my-heatmap-wrapper" style={{ height: 500 }}>
<AwesomeHeatmap data={data} />
</div>Preference Persistence
User preferences (theme, colour scale, sort order, cell size, show-values toggle) are automatically saved to IndexedDB under the storageKey and restored on the next mount. To use multiple independent instances on the same page, pass a unique storageKey to each:
<AwesomeHeatmap data={salesData} storageKey="sales-heatmap" />
<AwesomeHeatmap data={trafficData} storageKey="traffic-heatmap" />To disable persistence entirely, pass an empty string:
<AwesomeHeatmap data={data} storageKey="" />License
MIT
