{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "heatmap",
  "type": "registry:component",
  "title": "Heatmap",
  "description": "Two dimensions, shaded by value. Sample data: card transactions by weekday and hour over 7 days, the busiest cells at 398 and 412.",
  "registryDependencies": [
    "@chartory/core",
    "@chartory/plot-heat"
  ],
  "files": [
    {
      "path": "registry/chartory/components/heatmap.tsx",
      "type": "registry:component",
      "target": "components/chartory/heatmap.tsx",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// Heatmap (Chartory chart 024): sizes xs | s | m | l, laid out again from the data at each\n// size; collection \"hearth\" (default) or \"signal\" (with @chartory/signal, Code and Complete). Data in the type's shape (default: the sample). Docs:\n// https://usechartory.com/docs/charts/heatmap\nimport { ChartView, type ChartViewProps } from \"@/lib/chartory/charts/chart-view\"\nimport { registerChart } from \"@/lib/chartory/charts/defs\"\nimport { data as sample } from \"@/lib/chartory/charts/types/024-heatmap/data\"\nimport { def } from \"@/lib/chartory/charts/types/024-heatmap/layout\"\n\nregisterChart(def, sample)\n\nexport type HeatmapData = typeof sample\nexport type HeatmapProps = Omit<ChartViewProps, \"def\" | \"data\" | \"states\"> & {\n  /** the data to draw (default: the sample) */\n  data?: HeatmapData\n  /** further data states: the chart morphs to them (update) or ticks through them (live, with interval) */\n  states?: HeatmapData[]\n}\n\nexport function Heatmap(props: HeatmapProps) {\n  return <ChartView def={def} {...props} />\n}\n"
    },
    {
      "path": "registry/chartory/charts/types/024-heatmap/data.ts",
      "type": "registry:lib",
      "target": "lib/chartory/charts/types/024-heatmap/data.ts",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// 024 Heatmap · sample data (Figma chart/024, fintech: card transactions by weekday and 2-hour slot, this week;\n// values 0–1 of the peak, Friday 18:00; the seeded profile from the Figma definition, expanded).\nimport type { HeatData } from \"../../plots/heat\";\n\nexport const data: HeatData = {\n  rows: [\"MON\", \"TUE\", \"WED\", \"THU\", \"FRI\", \"SAT\", \"SUN\"],\n  cols: [\"00\", \"02\", \"04\", \"06\", \"08\", \"10\", \"12\", \"14\", \"16\", \"18\", \"20\", \"22\"],\n  xl: { 0: \"00:00\", 3: \"06:00\", 6: \"12:00\", 9: \"18:00\", 11: \"22:00\" },\n  vals: [\n    [0.042950212320848474, 0.025571085900110698, 0.09426907720168544, 0.29813185054507657, 0.4916779047465315, 0.634181343692905, 0.6952996764306444, 0.718315630453972, 0.9126717504842541, 0.6246287075800023, 0.36930759834261034, 0.14250200409446936],\n    [0.04101932980994663, 0.02579766946377869, 0.09127559242825752, 0.3161267960332552, 0.5322873101486859, 0.6338270862038374, 0.7814803497538159, 0.6967432069068138, 0.8330030102092973, 0.6110429593282207, 0.37274138823133957, 0.14695200154661758],\n    [0.0466911211238667, 0.026023637296577744, 0.09130681194062662, 0.31145900139057026, 0.543148065111762, 0.6874129167287204, 0.7193508902457314, 0.796463132508175, 0.9050348960624239, 0.6162196682219484, 0.4032936035935272, 0.13844834865408406],\n    [0.0510512120404333, 0.02930445813741556, 0.09766638514454774, 0.33381293544845325, 0.5709361408354976, 0.7404487777600758, 0.8287016678329845, 0.7909794732799751, 0.9414471126053888, 0.6316222011985546, 0.38698031091925705, 0.13824210748231136],\n    [0.04730599835296441, 0.028748590963682433, 0.10389442203561516, 0.36942903404376887, 0.5413288690696139, 0.7828780662514633, 0.7164367875663735, 0.8171566672917253, 0.97, 1, 0.43169641223535704, 0.1386001648672857],\n    [0.035226549043239345, 0.01908586183425312, 0.06799949430730169, 0.2446528798690312, 0.3811747865268843, 0.5587964466809278, 0.5918713241114613, 0.6295783626636389, 0.7029343804597549, 0.520160706284717, 0.28902315842258886, 0.09818385316942997],\n    [0.0220524330022989, 0.01394488178256195, 0.04476039839571361, 0.1540554286549126, 0.3010104053416338, 0.3473532210487655, 0.4112919110410344, 0.4155956708307358, 0.4833149053735262, 0.324663398946013, 0.21128347750906062, 0.0695274355432612],\n  ],\n  marks: { \"4,9\": \"412\", \"4,8\": \"398\" },\n  gap: 3,\n};\n"
    },
    {
      "path": "registry/chartory/charts/types/024-heatmap/layout.ts",
      "type": "registry:lib",
      "target": "lib/chartory/charts/types/024-heatmap/layout.ts",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// 024 Heatmap (Figma chart/024, \"Card transactions by hour\"): C.heat, weekdays × 2-hour slots, the Friday peak marked.\nimport type { ChartDef, TooltipSpec } from \"../../core/shell\";\nimport { heat } from \"../../plots/heat\";\nimport { data } from \"./data\";\nimport { frame, type Frame, type Text } from \"../../core/node\";\n\n/** Deterministic wave in [-1, 1] per index and update step (no randomness: the same step gives the same period). */\nconst wave = (i: number, step: number, f = 1.7) => Math.sin(step * 2.3 + i * f + 0.5);\nconst clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));\n/** A text is named by its characters, so an update state takes the sample's text names (same order): its value\n *  labels then stay the same marks and morph in place instead of reading as new ones. */\nfunction keepNames(pl: Frame, ref: Frame): Frame {\n  const texts = (f: Frame): Text[] => f.children.flatMap((c) => (c.removed ? [] : c.kind === \"text\" ? [c] : c.kind === \"frame\" ? texts(c) : []));\n  const a = texts(pl), b = texts(ref);\n  if (a.length === b.length) a.forEach((t, i) => { if (t.name === t.text.slice(0, 40)) t.name = b[i].name; });\n  return pl;\n}\n\n/** Hover tooltip: one cell (weekday × 2-hour slot): its share of the busiest slot, the cell's colour step, the count\n *  where the cell is marked, its rank otherwise. */\nconst tip = (d: unknown, g: number): TooltipSpec => {\n  const o = d as typeof data, nc = o.cols.length, r = Math.floor(g / nc), c = g % nc, v = o.vals[r][c];\n  const end = String((+o.cols[c] + 2) % 24 || 24).padStart(2, \"0\");\n  const rank = 1 + o.vals.flat().filter((x) => x > v).length, mark = o.marks?.[r + \",\" + c];\n  return {\n    kind: \"cell\",\n    caps: `${o.rows[r]} · ${o.cols[c]}:00–${end}:00`,\n    value: String(Math.round(v * 100)),\n    unit: \"% of peak\",\n    swatch: v < 0.1 ? \"data/idle\" : \"data/seq-\" + Math.min(6, Math.ceil(v * 6)),\n    share: mark ? mark + \" TRANSACTIONS\" : `RANK ${rank} OF ${o.vals.flat().length} SLOTS`,\n  };\n};\n\nexport const def: ChartDef = {\n  no: 24,\n  slug: \"heatmap\",\n  legend: [[\"data/seq-1\", \"Low\"], [\"data/seq-6\", \"High\"]],\n  plot: (K, p, w, d) => {\n    const o = (d as typeof data | undefined) ?? data;\n    const pl = heat(K, p, w, o);\n    return o === data ? pl : keepNames(pl, heat(K, frame(null, \"sample\"), w, data));\n  },\n  tip,\n  update: (d, step) => {\n    const o = d as typeof data;\n    // later weeks: every slot moves ±12 %; values stay shares of the busiest slot (Friday 18:00 stays the peak), and the\n    // marked counts follow the new peak count\n    const [pr, pc] = [4, 9];\n    const raw = o.vals.map((row, r) => row.map((v, c) => v * (1 + 0.12 * wave(r * 12 + c, step, 0.9))));\n    const peak = raw[pr][pc];\n    const vals = raw.map((row, r) => row.map((v, c) => (r === pr && c === pc ? 1 : clamp(v / peak, 0, 0.99))));\n    const count = Math.round(412 * (1 + 0.06 * step + 0.05 * wave(0, step)));\n    const marks = Object.fromEntries(Object.keys(o.marks ?? {}).map((k) => { const [r, c] = k.split(\",\").map(Number); return [k, String(Math.round(count * vals[r][c]))]; }));\n    return { ...o, vals, marks };\n  },\n  describe: {\n    title: \"Card transactions by hour\",\n    summary: \"Card transactions this week by weekday and 2-hour slot, as a share of the busiest slot: Friday 18:00 with 412 transactions, then Friday 16:00 with 398; the quietest slots are 00:00 to 04:00.\",\n    table: {\n      head: [\"Day\", ...data.cols.map((c) => c + \":00\")],\n      rows: data.rows.map((r, i) => [r[0] + r.slice(1).toLowerCase(), ...data.vals[i].map((v) => Math.round(v * 100) + \"%\")]),\n    },\n  },\n};\n"
    }
  ],
  "categories": [
    "charts",
    "space-and-place"
  ],
  "meta": {
    "access": "free",
    "no": 24,
    "family": "Space and place"
  }
}
