// Subnet economics tile + sector coverage heatmap. // Both components fetch on mount, render gracefully when central is // unreachable (hide the tile vs. show stub). const CENTRAL = (window.__ENV && window.__ENV.CENTRAL_API_URL) || ''; function fmtBig(n) { if (n === null || n === undefined || Number.isNaN(n)) return '—'; const v = Number(n); if (v >= 1e9) return (v / 1e9).toFixed(2) + 'B'; if (v >= 1e6) return (v / 1e6).toFixed(2) + 'M'; if (v >= 1e3) return (v / 1e3).toFixed(1) + 'K'; return v.toLocaleString(); } function fmtInt(n) { if (n === null || n === undefined || Number.isNaN(n)) return '—'; return Number(n).toLocaleString(); } // ─── Subnet economics + graph counts, combined into one 6-card row ─── window.SubnetEconomics = function SubnetEconomics() { const [subnet, setSubnet] = React.useState(null); const [graph, setGraph] = React.useState(null); const [loaded, setLoaded] = React.useState(false); React.useEffect(() => { if (!CENTRAL) { setLoaded(true); return; } let alive = true; Promise.allSettled([ fetch(CENTRAL.replace(/\/$/, '') + '/api/v1/stats/subnet').then((r) => r.ok ? r.json() : null), fetch(CENTRAL.replace(/\/$/, '') + '/health').then((r) => r.ok ? r.json() : null), ]).then(([s, h]) => { if (!alive) return; setSubnet(s.status === 'fulfilled' ? s.value : null); setGraph(h.status === 'fulfilled' ? h.value : null); setLoaded(true); }); return () => { alive = false; }; }, []); const subnetOk = subnet && subnet.available !== false; const validators = subnetOk ? subnet.validators : null; const miners = subnetOk ? subnet.miners : null; const alphaStake = subnetOk ? subnet.total_alpha_stake : null; const entities = graph && typeof graph.entities === 'number' ? graph.entities : null; const relationships = graph && typeof graph.relationships === 'number' ? graph.relationships : null; const facts = graph && typeof graph.facts === 'number' ? graph.facts : null; return (

Subnet 43 · Live state

On-chain validators and miners, plus the canonical graph counts they're contributing to. Refreshed on every page load.

Validators
{loaded ? fmtInt(validators) : '…'}
on netuid 43
Miners
{loaded ? fmtInt(miners) : '…'}
registered UIDs
Alpha stake
{loaded ? fmtBig(alphaStake) : '…'}
total bonded
Entities
{loaded ? fmtBig(entities) : '…'}
in the graph
Relationships
{loaded ? fmtBig(relationships) : '…'}
typed edges
Facts
{loaded ? fmtBig(facts) : '…'}
sourced claims
); }; // ─── Sector coverage — squarified treemap ─── // Tile area is proportional to entity count. Tiles are absolutely positioned; // the squarify algorithm packs them into the container rectangle with no // gaps. Tile orientation is chosen each step to minimise worst aspect ratio, // so the tiles tend toward squares rather than long thin strips. window.SectorHeatmap = function SectorHeatmap() { const [data, setData] = React.useState(null); const [loaded, setLoaded] = React.useState(false); const containerRef = React.useRef(null); const [size, setSize] = React.useState({ w: 0, h: 480 }); // Fetch React.useEffect(() => { if (!CENTRAL) { setLoaded(true); return; } let alive = true; fetch(CENTRAL.replace(/\/$/, '') + '/api/v1/stats/coverage') .then((r) => (r.ok ? r.json() : null)) .then((d) => { if (!alive) return; setData(d); setLoaded(true); }) .catch(() => { if (alive) { setData(null); setLoaded(true); } }); return () => { alive = false; }; }, []); // Track container width so the treemap reflows on window resize. React.useEffect(() => { const el = containerRef.current; if (!el) return; const measure = () => { const rect = el.getBoundingClientRect(); setSize({ w: rect.width, h: rect.height || 480 }); }; measure(); const ro = new ResizeObserver(measure); ro.observe(el); return () => ro.disconnect(); }, [loaded]); const bySector = (data && data.entities_by_sector) || {}; const entries = Object.entries(bySector) .map(([k, v]) => ({ name: prettySector(k), value: Number(v) || 0 })) .filter((e) => e.value > 0) .sort((a, b) => b.value - a.value); const max = entries.length ? entries[0].value : 0; const tiles = (size.w > 0 && entries.length > 0) ? squarifyTreemap(entries, 0, 0, size.w, size.h) : []; return (

Sector coverage

Entities indexed per sector. Tile area scales with the count — bigger tile, deeper coverage. Underserved sectors show up as small tiles and are good targets for new miners.

{!loaded && (
Loading…
)} {loaded && entries.length === 0 && (
No sector data yet.
)}
0 ? 'block' : 'none'}}> {tiles.map((t) => { const intensity = max ? Math.max(0.06, Math.min(0.35, t.value / max * 0.35)) : 0.06; return (
{fmtBig(t.value)}
{t.name}
); })}
); }; // Squarified treemap (Bruls, Huijing, van Wijk 2000). Lays out `items` // (objects with .value, already sorted descending) into a rectangle // [x, y, w, h] and returns a list of placed tiles { x, y, w, h, ...item }. function squarifyTreemap(items, x, y, w, h) { const total = items.reduce((s, i) => s + i.value, 0); if (total <= 0 || w <= 0 || h <= 0) return []; const scale = (w * h) / total; const scaled = items.map((it) => ({ ...it, area: it.value * scale })); const out = []; layoutSquarify(scaled.slice(), [], { x, y, w, h }, out); return out; } function layoutSquarify(remaining, row, rect, out) { if (remaining.length === 0) { if (row.length) placeRow(row, rect, out); return; } const side = Math.min(rect.w, rect.h); const item = remaining[0]; const newRow = row.concat([item]); const oldWorst = row.length ? worstAspect(row, side) : Infinity; const newWorst = worstAspect(newRow, side); if (row.length === 0 || newWorst <= oldWorst) { layoutSquarify(remaining.slice(1), newRow, rect, out); } else { const placed = placeRow(row, rect, out); layoutSquarify(remaining, [], placed, out); } } // Lay a finished row out along the short side of `rect`, then return the // remaining rectangle for the next row. function placeRow(row, rect, out) { const sum = row.reduce((s, i) => s + i.area, 0); if (sum <= 0) return rect; if (rect.w >= rect.h) { // Vertical strip on the left, tiles stacked top-to-bottom. const stripW = sum / rect.h; let dy = 0; for (const it of row) { const tileH = (it.area / sum) * rect.h; out.push({ ...it, x: rect.x, y: rect.y + dy, w: stripW, h: tileH }); dy += tileH; } return { x: rect.x + stripW, y: rect.y, w: rect.w - stripW, h: rect.h }; } else { // Horizontal strip on the top, tiles arranged left-to-right. const stripH = sum / rect.w; let dx = 0; for (const it of row) { const tileW = (it.area / sum) * rect.w; out.push({ ...it, x: rect.x + dx, y: rect.y, w: tileW, h: stripH }); dx += tileW; } return { x: rect.x, y: rect.y + stripH, w: rect.w, h: rect.h - stripH }; } } function worstAspect(row, side) { let sum = 0; let rmin = Infinity, rmax = -Infinity; for (const it of row) { sum += it.area; if (it.area < rmin) rmin = it.area; if (it.area > rmax) rmax = it.area; } if (sum <= 0) return Infinity; const s2 = side * side; const sum2 = sum * sum; return Math.max((s2 * rmax) / sum2, sum2 / (s2 * rmin)); } // Convert snake_case_sector keys ("cloud_computing") into display labels // ("Cloud Computing"). Untouched if already prettified. function prettySector(s) { if (!s) return '—'; return String(s) .replace(/_/g, ' ') .replace(/\b\w/g, (c) => c.toUpperCase()); }