{"spec_id":"heatmap-risk-matrix","library":"echarts","language":"javascript","code":"// anyplot.ai\n// heatmap-risk-matrix: Risk Assessment Matrix (Probability vs Impact)\n// Library: echarts 5.5.1 | JavaScript 22.22.3\n// Quality: 86/100 | Created: 2026-06-20\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data ---\n\nconst impactLabels     = [\"Negligible\", \"Minor\", \"Moderate\", \"Major\", \"Catastrophic\"];\nconst likelihoodLabels = [\"Rare\", \"Unlikely\", \"Possible\", \"Likely\", \"Almost Certain\"];\n\n// 5×5 background risk-score grid: [impact_idx, likelihood_idx, score]\nconst heatmapData = [];\nfor (let l = 0; l < 5; l++) {\n  for (let i = 0; i < 5; i++) {\n    heatmapData.push([i, l, (i + 1) * (l + 1)]);\n  }\n}\n\n// IT infrastructure project — 12 identified risks\nconst risks = [\n  { name: \"Data Breach\",       likelihood: 3, impact: 5 },\n  { name: \"Staff Attrition\",   likelihood: 4, impact: 3 },\n  { name: \"Budget Overrun\",    likelihood: 3, impact: 4 },\n  { name: \"Scope Creep\",       likelihood: 5, impact: 3 },\n  { name: \"Vendor Failure\",    likelihood: 2, impact: 4 },\n  { name: \"Ransomware\",        likelihood: 2, impact: 5 },\n  { name: \"Reg. Compliance\",   likelihood: 3, impact: 3 },\n  { name: \"Integration Fault\", likelihood: 4, impact: 4 },\n  { name: \"Natural Disaster\",  likelihood: 1, impact: 5 },\n  { name: \"API Deprecation\",   likelihood: 4, impact: 2 },\n  { name: \"Market Shift\",      likelihood: 3, impact: 2 },\n  { name: \"Phishing Attack\",   likelihood: 5, impact: 2 },\n];\n\n// Jitter offsets to separate risks sharing the same cell\nconst jitterPatterns = {\n  1: [[0, 0]],\n  2: [[-0.22, 0], [0.22, 0]],\n  3: [[-0.22, -0.18], [0.22, -0.18], [0, 0.22]],\n  4: [[-0.2, -0.2], [0.2, -0.2], [-0.2, 0.2], [0.2, 0.2]],\n};\n\nconst cellGroups = {};\nrisks.forEach(r => {\n  const key = `${r.impact - 1},${r.likelihood - 1}`;\n  if (!cellGroups[key]) cellGroups[key] = [];\n  cellGroups[key].push(r);\n});\n\nconst scatterData = risks.map(r => {\n  const key   = `${r.impact - 1},${r.likelihood - 1}`;\n  const group = cellGroups[key];\n  const idx   = group.indexOf(r);\n  const count = Math.min(group.length, 4);\n  const [jx, jy] = jitterPatterns[count][idx] || [0, 0];\n  return { value: [r.impact - 1 + jx, r.likelihood - 1 + jy], name: r.name };\n});\n\n// Zone colors — Imprint semantic palette: green=low → amber=medium → ochre=high → red=critical\nconst ZONE_COLORS = [\"#009E73\", \"#DDCC77\", \"#BD8233\", \"#AE3030\"];\n\n// --- Init ---\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option ---\nchart.setOption({\n  animation: false,\n  backgroundColor: \"transparent\",\n\n  title: {\n    text: \"heatmap-risk-matrix · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 22,\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: \"600\" },\n  },\n\n  visualMap: {\n    type: \"piecewise\",\n    seriesIndex: [0],\n    pieces: [\n      { min: 1,  max: 4,  label: \"Low 1–4\",        color: ZONE_COLORS[0] },\n      { min: 5,  max: 9,  label: \"Medium 5–9\",      color: ZONE_COLORS[1] },\n      { min: 10, max: 19, label: \"High 10–16\",      color: ZONE_COLORS[2] },\n      { min: 20, max: 25, label: \"Critical 20–25\",  color: ZONE_COLORS[3] },\n    ],\n    orient: \"horizontal\",\n    left: \"center\",\n    bottom: 30,\n    textStyle: { color: t.inkSoft, fontSize: 14 },\n    itemWidth: 22,\n    itemHeight: 22,\n    itemGap: 24,\n  },\n\n  grid: {\n    left: 180,\n    right: 50,\n    top: 88,\n    bottom: 178,\n    containLabel: false,\n  },\n\n  xAxis: {\n    type: \"category\",\n    data: impactLabels,\n    name: \"IMPACT\",\n    nameLocation: \"middle\",\n    nameGap: 50,\n    nameTextStyle: { color: t.ink, fontSize: 15, fontWeight: \"700\" },\n    axisLabel: { color: t.inkSoft, fontSize: 13 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { show: false },\n    axisTick: { show: false },\n  },\n\n  yAxis: {\n    type: \"category\",\n    data: likelihoodLabels,\n    name: \"LIKELIHOOD\",\n    nameLocation: \"middle\",\n    nameGap: 135,\n    nameTextStyle: { color: t.ink, fontSize: 15, fontWeight: \"700\" },\n    axisLabel: { color: t.inkSoft, fontSize: 13 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { show: false },\n    axisTick: { show: false },\n  },\n\n  series: [\n    {\n      name: \"Risk Score\",\n      type: \"heatmap\",\n      data: heatmapData,\n      itemStyle: {\n        borderWidth: 4,\n        borderColor: t.pageBg,\n      },\n      emphasis: { disabled: true },\n      label: {\n        show: true,\n        formatter: params => params.value[2],\n        fontSize: 16,\n        fontWeight: \"700\",\n        color: \"#F0EFE8\",\n        textShadowColor: \"rgba(26,26,23,0.7)\",\n        textShadowBlur: 5,\n        offset: [0, 50],\n      },\n    },\n    {\n      name: \"Risks\",\n      type: \"scatter\",\n      data: scatterData,\n      symbolSize: 15,\n      itemStyle: {\n        color: t.ink,\n        borderColor: t.pageBg,\n        borderWidth: 2,\n      },\n      label: {\n        show: true,\n        formatter: params => params.name,\n        position: \"top\",\n        distance: 8,\n        color: t.ink,\n        fontSize: 13,\n        fontWeight: \"600\",\n        backgroundColor: t.elevatedBg,\n        padding: [3, 6, 3, 6],\n        borderRadius: 3,\n      },\n      emphasis: { disabled: true },\n    },\n  ],\n});\n"}