Crypto Market Heatmap Development
A heatmap of the cryptocurrency market is a visualization of price changes across the entire market simultaneously. Each asset is represented as a rectangle whose size is proportional to market cap, with color reflecting percentage change over a period. An indispensable tool for quickly understanding market structure.
Treemap Layout Algorithm
The foundation of a heatmap is the Treemap algorithm, which optimally packs rectangles of different sizes:
interface HeatmapCell {
symbol: string;
marketCap: number;
changePercent: number;
price: number;
volume24h: number;
x: number; // computed by algorithm
y: number;
width: number;
height: number;
}
class SquarifiedTreemap {
layout(
data: HeatmapCell[],
bounds: {x: number; y: number; width: number; height: number}
): HeatmapCell[] {
// Sort by decreasing size (capitalization)
const sorted = [...data].sort((a, b) => b.marketCap - a.marketCap);
const totalMarketCap = sorted.reduce((sum, d) => sum + d.marketCap, 0);
return this.squarify(sorted, bounds, totalMarketCap);
}
private squarify(items: HeatmapCell[], bounds: Bounds, total: number): HeatmapCell[] {
if (items.length === 0) return [];
const isHorizontal = bounds.width >= bounds.height;
const result: HeatmapCell[] = [];
let row: HeatmapCell[] = [];
let rowArea = 0;
let offset = 0;
for (const item of items) {
const itemArea = (item.marketCap / total) * (bounds.width * bounds.height);
const testRow = [...row, item];
const testArea = rowArea + itemArea;
if (this.wouldImproveAspectRatio(testRow, testArea, bounds, isHorizontal)) {
row.push(item);
rowArea = testArea;
} else {
// Finalize current row
result.push(...this.layoutRow(row, rowArea, bounds, isHorizontal, offset, total));
const rowThickness = rowArea / (isHorizontal ? bounds.width : bounds.height);
offset += rowThickness;
row = [item];
rowArea = itemArea;
}
}
if (row.length > 0) {
result.push(...this.layoutRow(row, rowArea, bounds, isHorizontal, offset, total));
}
return result;
}
}
Data Fetching
import httpx
import asyncio
class MarketDataProvider:
COINGECKO_URL = "https://api.coingecko.com/api/v3"
async def get_heatmap_data(
self,
vs_currency: str = 'usd',
top_n: int = 100
) -> list[dict]:
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.COINGECKO_URL}/coins/markets",
params={
"vs_currency": vs_currency,
"order": "market_cap_desc",
"per_page": top_n,
"page": 1,
"sparkline": False,
"price_change_percentage": "1h,24h,7d"
}
)
coins = response.json()
return [
{
"symbol": c["symbol"].upper(),
"name": c["name"],
"market_cap": c["market_cap"] or 0,
"change_1h": c.get("price_change_percentage_1h_in_currency", 0) or 0,
"change_24h": c.get("price_change_percentage_24h", 0) or 0,
"change_7d": c.get("price_change_percentage_7d_in_currency", 0) or 0,
"volume_24h": c.get("total_volume", 0) or 0,
"price": c["current_price"],
"image": c["image"]
}
for c in coins if c["market_cap"]
]
We cache data: CoinGecko free API is 10-30 requests/min. Update every 60 seconds:
async def get_cached_data(self) -> list[dict]:
cache_key = "heatmap_data"
cached = await self.redis.get(cache_key)
if cached:
return json.loads(cached)
data = await self.get_heatmap_data()
await self.redis.setex(cache_key, 60, json.dumps(data))
return data
React Visualization
import React, { useMemo } from 'react';
const getColor = (changePercent: number): string => {
const intensity = Math.min(Math.abs(changePercent) / 10, 1);
if (changePercent > 0) {
const green = Math.floor(180 * intensity + 60);
return `rgb(0, ${green}, 0)`;
} else {
const red = Math.floor(180 * intensity + 60);
return `rgb(${red}, 0, 0)`;
}
};
const HeatmapCell: React.FC<{cell: HeatmapCell; period: '1h'|'24h'|'7d'}> = ({cell, period}) => {
const change = period === '1h' ? cell.change1h : period === '24h' ? cell.change24h : cell.change7d;
const bgColor = getColor(change);
return (
<div
style={{
position: 'absolute',
left: cell.x,
top: cell.y,
width: cell.width - 2,
height: cell.height - 2,
backgroundColor: bgColor,
border: '1px solid rgba(0,0,0,0.3)',
overflow: 'hidden',
cursor: 'pointer'
}}
title={`${cell.symbol}: ${change > 0 ? '+' : ''}${change.toFixed(2)}%`}
>
{cell.width > 40 && (
<div className="p-1 text-white">
<div className="font-bold text-xs">{cell.symbol}</div>
{cell.height > 30 && (
<div className={`text-xs ${change > 0 ? 'text-green-200' : 'text-red-200'}`}>
{change > 0 ? '+' : ''}{change.toFixed(2)}%
</div>
)}
</div>
)}
</div>
);
};
const CryptoHeatmap: React.FC = () => {
const [data, setData] = useState<HeatmapCell[]>([]);
const [period, setPeriod] = useState<'1h'|'24h'|'7d'>('24h');
const containerRef = useRef<HTMLDivElement>(null);
const [dimensions, setDimensions] = useState({width: 1200, height: 700});
const layoutData = useMemo(() => {
const treemap = new SquarifiedTreemap();
return treemap.layout(data, {x: 0, y: 0, ...dimensions});
}, [data, dimensions]);
return (
<div>
<div className="flex gap-2 mb-4">
{(['1h', '24h', '7d'] as const).map(p => (
<button key={p} onClick={() => setPeriod(p)}
className={`px-3 py-1 rounded ${period === p ? 'bg-blue-600' : 'bg-gray-700'}`}>
{p}
</button>
))}
</div>
<div ref={containerRef} className="relative bg-gray-900"
style={{width: dimensions.width, height: dimensions.height}}>
{layoutData.map(cell => (
<HeatmapCell key={cell.symbol} cell={cell} period={period} />
))}
</div>
</div>
);
};
A heatmap is a killer feature for a crypto platform. Users return to it daily to monitor the market. A good implementation includes: filtering by sectors (DeFi, Layer1, GameFi), zooming into individual sectors, and clickthrough to the trading pair.







