{"spec_id":"histogram-2d","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nhistogram-2d: 2D Histogram Heatmap\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-05-08\n\"\"\"\n\nimport os\n\nimport cairosvg\nimport numpy as np\n\n\n# Theme tokens - derived from pygal.style.Style patterns\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\"\nOKABE_ITO_1 = \"#009E73\"\n\n# Theme-adaptive style configuration (as would be used with pygal.style.Style)\n_style_config = {\n    \"background\": PAGE_BG,\n    \"plot_background\": PAGE_BG,\n    \"foreground\": INK,\n    \"foreground_strong\": INK,\n    \"foreground_subtle\": INK_MUTED,\n    \"colors\": (OKABE_ITO_1,),\n}\n\n# Data: Financial returns from different asset classes\nnp.random.seed(42)\n\n# Stock market returns (daily %)\nstock_returns = np.random.normal(0.08, 1.2, 2000)\n\n# Bond market returns (daily %)\nbond_returns = np.random.normal(0.02, 0.4, 2000)\n\n# Add some correlation structure (stocks and bonds move together in bull markets)\ncorrelation_factor = np.random.normal(0, 0.15, 2000)\nstock_returns = stock_returns + correlation_factor * 0.5\nbond_returns = bond_returns + correlation_factor * 0.2\n\n# Clip to realistic ranges\nx = np.clip(stock_returns, -5, 5)\ny = np.clip(bond_returns, -2, 2)\n\n# Compute 2D histogram\nn_bins = 20\ncounts, x_edges, y_edges = np.histogram2d(x, y, bins=n_bins)\ncounts = counts.T\n\n# Compute 1D marginal histograms\nx_hist, _ = np.histogram(x, bins=n_bins, range=(x_edges[0], x_edges[-1]))\ny_hist, _ = np.histogram(y, bins=n_bins, range=(y_edges[0], y_edges[-1]))\n\n\ndef get_viridis_color(t):\n    \"\"\"Interpolate perceptually uniform viridis colormap.\"\"\"\n    viridis_lut = [\n        \"#440154\",\n        \"#482878\",\n        \"#3e4a89\",\n        \"#31688e\",\n        \"#26828e\",\n        \"#1f9e89\",\n        \"#35b779\",\n        \"#6ece58\",\n        \"#b5de2b\",\n        \"#fde725\",\n    ]\n    pos = t * (len(viridis_lut) - 1)\n    idx = int(pos)\n    frac = pos - idx\n\n    if idx >= len(viridis_lut) - 1:\n        return viridis_lut[-1]\n    if frac == 0:\n        return viridis_lut[idx]\n\n    c1, c2 = viridis_lut[idx], viridis_lut[idx + 1]\n    r = int(int(c1[1:3], 16) * (1 - frac) + int(c2[1:3], 16) * frac)\n    g = int(int(c1[3:5], 16) * (1 - frac) + int(c2[3:5], 16) * frac)\n    b = int(int(c1[5:7], 16) * (1 - frac) + int(c2[5:7], 16) * frac)\n    return f\"#{r:02x}{g:02x}{b:02x}\"\n\n\n# SVG construction\nsvg_parts = []\nsvg_parts.append('<?xml version=\"1.0\" encoding=\"utf-8\"?>')\nsvg_parts.append('<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"4800\" height=\"2700\" viewBox=\"0 0 4800 2700\">')\nsvg_parts.append(f'<rect width=\"4800\" height=\"2700\" fill=\"{PAGE_BG}\"/>')\n\n# Title with refined styling\nsvg_parts.append(\n    f'<text x=\"2400\" y=\"90\" text-anchor=\"middle\" fill=\"{INK}\" '\n    f\"style=\\\"font-size:56px;font-weight:600;font-family:'system-ui', sans-serif;letter-spacing:0.5px\\\">\"\n    f\"histogram-2d · pygal · anyplot.ai</text>\"\n)\nsvg_parts.append(f'<line x1=\"500\" y1=\"120\" x2=\"4300\" y2=\"120\" stroke=\"{INK_MUTED}\" stroke-width=\"1.5\" opacity=\"0.4\"/>')\n\n# Layout\nmargin_l, margin_t = 320, 150\nmargin_r, margin_b = 280, 320\nmarginal_h = 240\ngap = 30\n\n# Main heatmap area\nhm_x = margin_l\nhm_y = margin_t + marginal_h + gap\nhm_w = 4800 - margin_l - margin_r - 180\nhm_h = 2700 - margin_t - margin_b - marginal_h - gap\n\ncell_w = hm_w / n_bins\ncell_h = hm_h / n_bins\n\nmin_val = counts.min()\nmax_val = counts.max()\n\n# Draw heatmap cells\nfor i in range(n_bins):\n    for j in range(n_bins):\n        val = counts[n_bins - 1 - i, j]\n        if max_val == min_val:\n            t = 1.0\n        else:\n            t = max(0, min(1, (val - min_val) / (max_val - min_val)))\n\n        color = get_viridis_color(t)\n\n        rx = hm_x + j * cell_w\n        ry = hm_y + i * cell_h\n        svg_parts.append(\n            f'<rect x=\"{rx:.1f}\" y=\"{ry:.1f}\" width=\"{cell_w + 0.5:.1f}\" height=\"{cell_h + 0.5:.1f}\" fill=\"{color}\"/>'\n        )\n\n# Heatmap border with refined styling\nsvg_parts.append(\n    f'<rect x=\"{hm_x}\" y=\"{hm_y}\" width=\"{hm_w}\" height=\"{hm_h}\" fill=\"none\" stroke=\"{INK_MUTED}\" stroke-width=\"2.5\" stroke-linejoin=\"miter\"/>'\n)\n\n# X-axis marginal histogram (top)\nmarg_x_y = margin_t\nmarg_x_h = marginal_h\nx_max = x_hist.max()\nfor j in range(n_bins):\n    bar_h = (x_hist[j] / x_max) * marg_x_h * 0.85 if x_max > 0 else 0\n    rx = hm_x + j * cell_w\n    ry = marg_x_y + marg_x_h - bar_h\n    svg_parts.append(\n        f'<rect x=\"{rx:.1f}\" y=\"{ry:.1f}\" width=\"{cell_w - 1:.1f}\" height=\"{bar_h:.1f}\" fill=\"{OKABE_ITO_1}\" opacity=\"0.75\" stroke=\"{OKABE_ITO_1}\" stroke-width=\"0.5\" stroke-opacity=\"0.3\"/>'\n    )\nsvg_parts.append(\n    f'<rect x=\"{hm_x}\" y=\"{marg_x_y}\" width=\"{hm_w}\" height=\"{marg_x_h}\" fill=\"none\" stroke=\"{INK_MUTED}\" stroke-width=\"2.5\" stroke-linejoin=\"miter\"/>'\n)\n\n# Y-axis marginal histogram (right)\nmarg_y_x = hm_x + hm_w + gap\nmarg_y_w = 140\ny_max = y_hist.max()\nfor i in range(n_bins):\n    bar_w = (y_hist[n_bins - 1 - i] / y_max) * marg_y_w * 0.85 if y_max > 0 else 0\n    rx = marg_y_x\n    ry = hm_y + i * cell_h\n    svg_parts.append(\n        f'<rect x=\"{rx:.1f}\" y=\"{ry:.1f}\" width=\"{bar_w:.1f}\" height=\"{cell_h - 1:.1f}\" fill=\"{OKABE_ITO_1}\" opacity=\"0.75\" stroke=\"{OKABE_ITO_1}\" stroke-width=\"0.5\" stroke-opacity=\"0.3\"/>'\n    )\nsvg_parts.append(\n    f'<rect x=\"{marg_y_x}\" y=\"{hm_y}\" width=\"{marg_y_w}\" height=\"{hm_h}\" fill=\"none\" stroke=\"{INK_MUTED}\" stroke-width=\"2.5\" stroke-linejoin=\"miter\"/>'\n)\n\n# X-axis ticks and labels\nfor idx in np.linspace(0, n_bins, 6):\n    px = hm_x + idx * cell_w\n    py = hm_y + hm_h\n    val = x_edges[0] + (x_edges[-1] - x_edges[0]) * idx / n_bins\n    svg_parts.append(\n        f'<line x1=\"{px:.1f}\" y1=\"{py:.1f}\" x2=\"{px:.1f}\" y2=\"{py + 12:.1f}\" stroke=\"{INK_MUTED}\" stroke-width=\"2\"/>'\n    )\n    svg_parts.append(\n        f'<text x=\"{px:.1f}\" y=\"{py + 45:.1f}\" text-anchor=\"middle\" fill=\"{INK_MUTED}\" '\n        f'style=\"font-size:32px;font-family:sans-serif\">{val:.1f}%</text>'\n    )\n\n# X-axis label\nsvg_parts.append(\n    f'<text x=\"{hm_x + hm_w / 2:.1f}\" y=\"{hm_y + hm_h + 110:.1f}\" text-anchor=\"middle\" '\n    f'fill=\"{INK}\" style=\"font-size:40px;font-weight:bold;font-family:sans-serif\">'\n    f\"Stock Market Daily Returns (%)</text>\"\n)\n\n# Y-axis ticks and labels\nfor idx in np.linspace(0, n_bins, 6):\n    px = hm_x\n    py = hm_y + hm_h - idx * cell_h\n    val = y_edges[0] + (y_edges[-1] - y_edges[0]) * idx / n_bins\n    svg_parts.append(\n        f'<line x1=\"{px - 12:.1f}\" y1=\"{py:.1f}\" x2=\"{px:.1f}\" y2=\"{py:.1f}\" stroke=\"{INK_MUTED}\" stroke-width=\"2\"/>'\n    )\n    svg_parts.append(\n        f'<text x=\"{px - 20:.1f}\" y=\"{py + 10:.1f}\" text-anchor=\"end\" fill=\"{INK_MUTED}\" '\n        f'style=\"font-size:32px;font-family:sans-serif\">{val:.1f}%</text>'\n    )\n\n# Y-axis label (rotated)\nly = hm_y + hm_h / 2\nlx = hm_x - 200\nsvg_parts.append(\n    f'<text x=\"{lx:.1f}\" y=\"{ly:.1f}\" text-anchor=\"middle\" fill=\"{INK}\" '\n    f'style=\"font-size:40px;font-weight:bold;font-family:sans-serif\" '\n    f'transform=\"rotate(-90, {lx:.1f}, {ly:.1f})\">Bond Market Daily Returns (%)</text>'\n)\n\n# Colorbar\ncb_x = marg_y_x + marg_y_w + 80\ncb_y = hm_y + hm_h * 0.1\ncb_w = 50\ncb_h = hm_h * 0.8\nn_seg = 100\n\nfor i in range(n_seg):\n    seg_val = min_val + (max_val - min_val) * (n_seg - 1 - i) / (n_seg - 1)\n    t = (seg_val - min_val) / (max_val - min_val) if max_val > min_val else 1\n    color = get_viridis_color(t)\n    seg_h = cb_h / n_seg\n    svg_parts.append(\n        f'<rect x=\"{cb_x:.1f}\" y=\"{cb_y + i * seg_h:.1f}\" width=\"{cb_w}\" height=\"{seg_h + 1:.1f}\" fill=\"{color}\"/>'\n    )\n\nsvg_parts.append(\n    f'<rect x=\"{cb_x}\" y=\"{cb_y}\" width=\"{cb_w}\" height=\"{cb_h}\" fill=\"none\" stroke=\"{INK_MUTED}\" stroke-width=\"2.5\" stroke-linejoin=\"miter\"/>'\n)\n\n# Colorbar ticks and labels\nfor i in range(5):\n    frac = i / 4\n    tick_val = max_val - frac * (max_val - min_val)\n    ty = cb_y + frac * cb_h\n    svg_parts.append(\n        f'<line x1=\"{cb_x + cb_w:.1f}\" y1=\"{ty:.1f}\" '\n        f'x2=\"{cb_x + cb_w + 10:.1f}\" y2=\"{ty:.1f}\" stroke=\"{INK_MUTED}\" stroke-width=\"2\"/>'\n    )\n    svg_parts.append(\n        f'<text x=\"{cb_x + cb_w + 20:.1f}\" y=\"{ty + 10:.1f}\" fill=\"{INK_MUTED}\" '\n        f'style=\"font-size:28px;font-family:sans-serif\">{int(tick_val)}</text>'\n    )\n\n# Colorbar label\nsvg_parts.append(\n    f'<text x=\"{cb_x + cb_w / 2:.1f}\" y=\"{cb_y - 30:.1f}\" text-anchor=\"middle\" '\n    f'fill=\"{INK}\" style=\"font-size:36px;font-weight:bold;font-family:sans-serif\">Count</text>'\n)\n\nsvg_parts.append(\"</svg>\")\n\n# Save SVG and PNG\nsvg_content = \"\\n\".join(svg_parts)\nwith open(f\"plot-{THEME}.svg\", \"w\", encoding=\"utf-8\") as f:\n    f.write(svg_content)\n\ncairosvg.svg2png(bytestring=svg_content.encode(\"utf-8\"), write_to=f\"plot-{THEME}.png\")\n\n# Save HTML\nhtml_content = f\"\"\"<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <title>histogram-2d - pygal</title>\n    <style>\n        body {{ margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; background: {PAGE_BG}; }}\n        .chart {{ max-width: 100%; height: auto; }}\n    </style>\n</head>\n<body>\n    <figure class=\"chart\">\n        {svg_content}\n    </figure>\n</body>\n</html>\n\"\"\"\n\nwith open(f\"plot-{THEME}.html\", \"w\", encoding=\"utf-8\") as f:\n    f.write(html_content)\n"}