{"spec_id":"heatmap-risk-matrix","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nheatmap-risk-matrix: Risk Assessment Matrix (Probability vs Impact)\nLibrary: plotnine 0.15.7 | Python 3.13.14\nQuality: 89/100 | Updated: 2026-06-20\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove script's own directory from sys.path so 'plotnine' resolves to the library, not this file\n_here = os.path.dirname(os.path.realpath(__file__))\nsys.path = [p for p in sys.path if p and os.path.realpath(p) != _here]\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_label,\n    geom_text,\n    geom_tile,\n    ggplot,\n    labs,\n    scale_fill_gradient2,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Data: 5×5 background grid\nlikelihood_levels = [1, 2, 3, 4, 5]\nimpact_levels = [1, 2, 3, 4, 5]\n\ngrid_rows = []\nfor li in likelihood_levels:\n    for imp in impact_levels:\n        score = li * imp\n        if score <= 4:\n            zone = \"Low\"\n        elif score <= 9:\n            zone = \"Medium\"\n        elif score <= 16:\n            zone = \"High\"\n        else:\n            zone = \"Critical\"\n        grid_rows.append({\"likelihood\": li, \"impact\": imp, \"risk_score\": score, \"zone\": zone})\n\ngrid_df = pd.DataFrame(grid_rows)\n\n# Critical-zone cells for emphasis overlay (risk_score > 16)\ncritical_df = grid_df[grid_df[\"risk_score\"] > 16].copy()\n\n# Score number position: top-left corner of each cell\ngrid_df[\"score_x\"] = grid_df[\"impact\"] - 0.38\ngrid_df[\"score_y\"] = grid_df[\"likelihood\"] + 0.35\n\n# Risk items\nnp.random.seed(42)\nrisks = pd.DataFrame(\n    {\n        \"risk_name\": [\n            \"Supply Delay\",\n            \"Budget Overrun\",\n            \"Key Staff Loss\",\n            \"Scope Creep\",\n            \"Vendor Failure\",\n            \"Reg Change\",\n            \"Data Breach\",\n            \"Tech Debt\",\n            \"Market Shift\",\n            \"Integration Bug\",\n            \"Power Outage\",\n            \"Compliance Gap\",\n        ],\n        \"likelihood\": [3, 4, 2, 5, 2, 3, 1, 4, 3, 4, 1, 3],\n        \"impact\": [3, 4, 5, 3, 4, 2, 5, 2, 4, 3, 4, 4],\n    }\n)\n\n# Smart label positioning: offset risks sharing the same cell\ncell_counts = risks.groupby([\"likelihood\", \"impact\"]).cumcount()\ncell_totals = risks.groupby([\"likelihood\", \"impact\"])[\"risk_name\"].transform(\"count\")\n\nlabel_offsets = []\nfor idx in range(len(risks)):\n    count = cell_counts.iloc[idx]\n    total = cell_totals.iloc[idx]\n    if total > 1:\n        offset = 0.18 if count == 0 else -0.18\n    else:\n        offset = -0.05\n    label_offsets.append(offset)\n\nrisks[\"label_y\"] = risks[\"likelihood\"] + label_offsets\nrisks[\"label_x\"] = risks[\"impact\"].astype(float)\n\n# Axis labels\nlikelihood_labels = {1: \"Rare\", 2: \"Unlikely\", 3: \"Possible\", 4: \"Likely\", 5: \"Almost\\nCertain\"}\nimpact_labels = {1: \"Negligible\", 2: \"Minor\", 3: \"Moderate\", 4: \"Major\", 5: \"Catastrophic\"}\n\ntitle = \"heatmap-risk-matrix · python · plotnine · anyplot.ai\"\n\n# Plot\nplot = (\n    ggplot()\n    # Background heatmap tiles with Imprint-derived green→ochre→red gradient\n    + geom_tile(data=grid_df, mapping=aes(x=\"impact\", y=\"likelihood\", fill=\"risk_score\"), color=INK_SOFT, size=0.8)\n    + scale_fill_gradient2(\n        low=\"#009E73\",\n        mid=\"#BD8233\",\n        high=\"#AE3030\",\n        midpoint=12,\n        limits=(1, 25),\n        name=\"Risk\\nScore\",\n        breaks=[1, 5, 10, 15, 20, 25],\n    )\n    # Critical-zone emphasis: thicker matte-red border on highest-risk cells\n    + geom_tile(data=critical_df, mapping=aes(x=\"impact\", y=\"likelihood\"), fill=\"none\", color=\"#AE3030\", size=1.8)\n    # Risk score numbers in top-left corners (semi-transparent to stay secondary)\n    + geom_text(\n        data=grid_df,\n        mapping=aes(x=\"score_x\", y=\"score_y\", label=\"risk_score\"),\n        color=INK,\n        alpha=0.45,\n        size=3.2,\n        fontweight=\"bold\",\n        ha=\"left\",\n        va=\"top\",\n    )\n    # Risk item labels — theme-adaptive fill and text\n    + geom_label(\n        data=risks,\n        mapping=aes(x=\"label_x\", y=\"label_y\", label=\"risk_name\"),\n        color=INK,\n        fill=ELEVATED_BG,\n        size=3.8,\n        alpha=0.92,\n        label_padding=0.22,\n        label_size=0.3,\n        label_r=0.08,\n    )\n    # Zone annotation above the matrix\n    + annotate(\n        \"text\",\n        x=3,\n        y=5.58,\n        label=\"Zones:  Low (1–4)  ·  Medium (5–9)  ·  High (10–16)  ·  Critical (20–25)\",\n        size=3.5,\n        color=INK_MUTED,\n        fontstyle=\"italic\",\n    )\n    + scale_x_continuous(breaks=impact_levels, labels=[impact_labels[i] for i in impact_levels], expand=(0, 0.55))\n    + scale_y_continuous(\n        breaks=likelihood_levels, labels=[likelihood_labels[i] for i in likelihood_levels], expand=(0, 0.65)\n    )\n    + labs(x=\"Impact →\", y=\"Likelihood →\", title=title)\n    + theme_minimal()\n    + theme(\n        figure_size=(6, 6),\n        plot_title=element_text(size=12, ha=\"center\", weight=\"bold\", margin={\"b\": 8}, color=INK),\n        axis_title_x=element_text(size=10, weight=\"bold\", margin={\"t\": 8}, color=INK),\n        axis_title_y=element_text(size=10, weight=\"bold\", margin={\"r\": 8}, color=INK),\n        axis_text_x=element_text(size=8, color=INK_SOFT),\n        axis_text_y=element_text(size=8, color=INK_SOFT),\n        legend_title=element_text(size=8, weight=\"bold\", color=INK),\n        legend_text=element_text(size=8, color=INK_SOFT),\n        legend_key_height=40,\n        panel_grid_major=element_blank(),\n        panel_grid_minor=element_blank(),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_border=element_rect(color=INK_SOFT, fill=None),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=6, height=6, units=\"in\")\n"}