{"spec_id":"heatmap-correlation","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nheatmap-correlation: Correlation Matrix Heatmap\nLibrary: plotnine 0.15.8 | Python 3.13.15\nQuality: 90/100 | Updated: 2026-08-18\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    coord_fixed,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_text,\n    geom_tile,\n    ggplot,\n    labs,\n    scale_color_identity,\n    scale_fill_gradient2,\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\"\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# Imprint diverging colormap (imprint_div) — matte-red <-> page background <-> blue\nDIV_LOW = \"#AE3030\"\nDIV_HIGH = \"#4467A3\"\nDARK_TEXT = \"#1A1A17\"\nLIGHT_TEXT = \"#F0EFE8\"\n\n\ndef _hex_to_rgb(hex_color):\n    hex_color = hex_color.lstrip(\"#\")\n    return tuple(int(hex_color[i : i + 2], 16) / 255 for i in (0, 2, 4))\n\n\ndef _lerp_hex(hex_a, hex_b, t):\n    ra, ga, ba = _hex_to_rgb(hex_a)\n    rb, gb, bb = _hex_to_rgb(hex_b)\n    r = round((ra + (rb - ra) * t) * 255)\n    g = round((ga + (gb - ga) * t) * 255)\n    b = round((ba + (bb - ba) * t) * 255)\n    return f\"#{r:02x}{g:02x}{b:02x}\"\n\n\ndef _relative_luminance(hex_color):\n    def channel(c):\n        return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4\n\n    r, g, b = _hex_to_rgb(hex_color)\n    return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b)\n\n\ndef cell_fill_hex(value):\n    \"\"\"Mirror scale_fill_gradient2's low/mid/high interpolation so annotation\n    text color can be judged against the cell's actual rendered background\n    rather than against the page-level theme token.\"\"\"\n    if value <= 0:\n        return _lerp_hex(DIV_LOW, PAGE_BG, value + 1)\n    return _lerp_hex(PAGE_BG, DIV_HIGH, value)\n\n\ndef text_color_for(hex_color):\n    return DARK_TEXT if _relative_luminance(hex_color) > 0.5 else LIGHT_TEXT\n\n\n# Data - realistic financial/portfolio variables for correlation analysis\nnp.random.seed(42)\n\nvariables = [\"Stock_A\", \"Stock_B\", \"Stock_C\", \"Bonds\", \"Gold\", \"Real_Estate\", \"Oil\", \"Tech_Index\"]\n\n# Realistic correlation matrix: positive correlations among stocks, negative\n# correlations for bonds vs. stocks, and near-zero correlations for gold.\nbase_corr = np.array(\n    [\n        [1.00, 0.85, 0.72, -0.35, -0.15, 0.42, 0.28, 0.91],  # Stock_A\n        [0.85, 1.00, 0.68, -0.28, -0.22, 0.38, 0.31, 0.82],  # Stock_B\n        [0.72, 0.68, 1.00, -0.18, -0.08, 0.52, 0.45, 0.75],  # Stock_C\n        [-0.35, -0.28, -0.18, 1.00, 0.45, 0.12, -0.25, -0.32],  # Bonds\n        [-0.15, -0.22, -0.08, 0.45, 1.00, 0.08, 0.35, -0.18],  # Gold\n        [0.42, 0.38, 0.52, 0.12, 0.08, 1.00, 0.22, 0.48],  # Real_Estate\n        [0.28, 0.31, 0.45, -0.25, 0.35, 0.22, 1.00, 0.32],  # Oil\n        [0.91, 0.82, 0.75, -0.32, -0.18, 0.48, 0.32, 1.00],  # Tech_Index\n    ]\n)\n\n# Long format, lower triangle only (incl. diagonal) to avoid redundancy.\n# The diagonal is trivially 1.00 for every asset, so its fill is masked to a\n# neutral tone (still annotated with the real value) — this keeps the two\n# strong-color endpoints reserved for genuinely informative relationships.\nrows = []\nfor i, var1 in enumerate(variables):\n    for j, var2 in enumerate(variables):\n        if i >= j:\n            value = base_corr[i, j]\n            is_diagonal = i == j\n            fill_hex = INK_MUTED if is_diagonal else cell_fill_hex(value)\n            rows.append(\n                {\n                    \"Var1\": var1,\n                    \"Var2\": var2,\n                    \"Correlation\": value,\n                    \"Correlation_fill\": np.nan if is_diagonal else value,\n                    \"text_color\": text_color_for(fill_hex),\n                    \"is_strong\": (not is_diagonal) and abs(value) >= 0.7,\n                }\n            )\n\ndf = pd.DataFrame(rows)\ndf[\"Var1\"] = pd.Categorical(df[\"Var1\"], categories=variables, ordered=True)\ndf[\"Var2\"] = pd.Categorical(df[\"Var2\"], categories=variables, ordered=True)\n\n# Strong relationships (|r| >= 0.7) get a bolder outline — a lightweight,\n# distinctive cue that draws the eye to the correlations worth acting on.\nstrong_df = df[df[\"is_strong\"]]\n\nplot = (\n    ggplot(df, aes(x=\"Var2\", y=\"Var1\"))\n    + geom_tile(aes(fill=\"Correlation_fill\"), color=INK_SOFT, size=0.5)\n    + geom_tile(data=strong_df, mapping=aes(x=\"Var2\", y=\"Var1\"), fill=None, color=INK, size=1.6)\n    + geom_text(aes(label=\"Correlation\", color=\"text_color\"), format_string=\"{:.2f}\", size=6.5)\n    + scale_fill_gradient2(\n        low=DIV_LOW,\n        mid=PAGE_BG,\n        high=DIV_HIGH,\n        midpoint=0,\n        limits=(-1, 1),\n        na_value=INK_MUTED,\n        name=\"Correlation\\nCoefficient\",\n    )\n    + scale_color_identity()\n    + coord_fixed(ratio=1)\n    + labs(title=\"heatmap-correlation · python · plotnine · anyplot.ai\", x=\"Portfolio Asset\", y=\"Portfolio Asset\")\n    + theme_minimal()\n    + theme(\n        figure_size=(6, 6),  # 6x6 at 400 DPI = 2400x2400 px\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_grid_major=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_border=element_rect(color=INK_SOFT, fill=None),\n        plot_title=element_text(size=13, color=INK, ha=\"center\", weight=\"bold\"),\n        axis_title_x=element_text(size=11, color=INK),\n        axis_title_y=element_text(size=11, color=INK),\n        axis_text_x=element_text(size=9, color=INK_SOFT, rotation=45, ha=\"right\"),\n        axis_text_y=element_text(size=9, color=INK_SOFT),\n        axis_line=element_line(color=INK_SOFT),\n        legend_background=element_rect(fill=PAGE_BG, color=INK_SOFT),\n        legend_title=element_text(size=10, color=INK),\n        legend_text=element_text(size=9, color=INK_SOFT),\n    )\n)\n\n# Save at 400 DPI for 2400x2400 pixel output\nscript_dir = os.path.dirname(os.path.abspath(__file__))\nos.chdir(script_dir)\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=6, height=6, units=\"in\")\n"}