{"spec_id":"contour-density","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\ncontour-density: Density Contour Plot\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 60/100 | Updated: 2026-05-16\n\"\"\"\n\nimport os\nimport sys\nfrom pathlib import Path\n\n\n# Remove script directory from path to avoid name collision with pygal package\n_script_dir = str(Path(__file__).parent)\nsys.path = [p for p in sys.path if p != _script_dir]\n\nimport cairosvg\nimport numpy as np\nimport pygal\nfrom matplotlib.cm import Greens\nfrom matplotlib.colors import to_hex\nfrom pygal.style import Style\nfrom scipy import stats\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\"\nBRAND = \"#009E73\"\n\n# Data: Bivariate distribution with clusters (house price vs square footage)\nnp.random.seed(42)\n\n# Create realistic clustered data - real estate market with different property types\nn_samples = 400\n\n# Cluster 1: Starter homes (smaller, less expensive)\nn1 = 140\nx1 = np.random.normal(1200, 150, n1)  # Square footage\ny1 = np.random.normal(250000, 40000, n1)  # Price ($)\n\n# Cluster 2: Mid-range homes (medium size, moderate price)\nn2 = 160\nx2 = np.random.normal(2000, 200, n2)\ny2 = np.random.normal(450000, 60000, n2)\n\n# Cluster 3: Premium homes (larger, more expensive)\nn3 = 100\nx3 = np.random.normal(3200, 250, n3)\ny3 = np.random.normal(750000, 100000, n3)\n\n# Combine all data\nx_data = np.concatenate([x1, x2, x3])\ny_data = np.concatenate([y1, y2, y3])\n\n# Compute 2D KDE (Kernel Density Estimation)\n# Normalize data for better KDE computation\nx_norm = (x_data - x_data.min()) / (x_data.max() - x_data.min())\ny_norm = (y_data - y_data.min()) / (y_data.max() - y_data.min())\n\nn_grid = 100\nx_grid_norm = np.linspace(0, 1, n_grid)\ny_grid_norm = np.linspace(0, 1, n_grid)\nX_norm, Y_norm = np.meshgrid(x_grid_norm, y_grid_norm)\npositions_norm = np.vstack([X_norm.ravel(), Y_norm.ravel()])\n\n# Fit KDE on normalized data\nvalues_norm = np.vstack([x_norm, y_norm])\nkernel = stats.gaussian_kde(values_norm)\nZ = np.reshape(kernel(positions_norm).T, X_norm.shape)\n\n# Map back to original coordinates for visualization\nx_min, x_max = x_data.min() - 200, x_data.max() + 200\ny_min, y_max = y_data.min() - 100000, y_data.max() + 100000\nX = x_grid_norm * (x_max - x_min) + x_min\nY = y_grid_norm * (y_max - y_min) + y_min\n\nz_min, z_max = Z.min(), Z.max()\n\n\ndef interpolate_color(value, vmin, vmax):\n    \"\"\"Get color for value using Greens colormap.\"\"\"\n    if vmax == vmin:\n        norm = 0.5\n    else:\n        norm = max(0, min(1, (value - vmin) / (vmax - vmin)))\n    return to_hex(Greens(norm))\n\n\n# Style for 4800x2700 canvas\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_SOFT,\n    colors=(BRAND,),\n    title_font_size=28,\n    legend_font_size=16,\n    label_font_size=22,\n    value_font_size=18,\n    font_family=\"sans-serif\",\n)\n\n# Create base XY chart\nchart = pygal.XY(\n    width=4800,\n    height=2700,\n    style=custom_style,\n    title=\"contour-density · pygal · anyplot.ai\",\n    show_legend=False,\n    margin=120,\n    margin_top=200,\n    margin_bottom=200,\n    margin_left=300,\n    margin_right=350,\n    show_x_labels=False,\n    show_y_labels=False,\n    show_x_guides=False,\n    show_y_guides=False,\n    x_title=\"\",\n    y_title=\"\",\n)\n\n# Plot dimensions (matching chart margins)\nplot_x = 300\nplot_y = 200\nplot_width = 4800 - 300 - 350\nplot_height = 2700 - 200 - 200\n\n# Cell size\ncell_w = plot_width / (n_grid - 1)\ncell_h = plot_height / (n_grid - 1)\n\n# Build SVG content\nsvg_parts = []\n\n# Background for the plot area\nsvg_parts.append(\n    f'<rect x=\"{plot_x}\" y=\"{plot_y}\" width=\"{plot_width}\" height=\"{plot_height}\" fill=\"{PAGE_BG}\" stroke=\"none\"/>'\n)\n\n# Draw filled density cells\nfor i in range(n_grid - 1):\n    for j in range(n_grid - 1):\n        # Average of 4 corners for cell color\n        cell_val = (Z[i, j] + Z[i, j + 1] + Z[i + 1, j] + Z[i + 1, j + 1]) / 4\n        color = interpolate_color(cell_val, z_min, z_max)\n        cx = plot_x + j * cell_w\n        cy = plot_y + plot_height - (i + 1) * cell_h\n        svg_parts.append(\n            f'<rect x=\"{cx:.1f}\" y=\"{cy:.1f}\" width=\"{cell_w + 0.5:.1f}\" '\n            f'height=\"{cell_h + 0.5:.1f}\" fill=\"{color}\" stroke=\"none\"/>'\n        )\n\n# Draw contour lines using marching squares\nn_contour_levels = 10\ncontour_levels = np.linspace(z_min + (z_max - z_min) * 0.1, z_max * 0.95, n_contour_levels)\n\nfor level in contour_levels:\n    for i in range(n_grid - 1):\n        for j in range(n_grid - 1):\n            z00, z01 = Z[i, j], Z[i, j + 1]\n            z10, z11 = Z[i + 1, j], Z[i + 1, j + 1]\n\n            # Marching squares case\n            case = 0\n            if z00 >= level:\n                case |= 1\n            if z01 >= level:\n                case |= 2\n            if z11 >= level:\n                case |= 4\n            if z10 >= level:\n                case |= 8\n\n            if case == 0 or case == 15:\n                continue\n\n            # Cell position\n            x0 = plot_x + j * cell_w\n            y0 = plot_y + plot_height - (i + 1) * cell_h\n\n            # Linear interpolation helper\n            def lerp(v1, v2, lv):\n                if abs(v2 - v1) < 1e-10:\n                    return 0.5\n                return (lv - v1) / (v2 - v1)\n\n            # Edge midpoints\n            left = (x0, y0 + cell_h * lerp(z00, z10, level))\n            right = (x0 + cell_w, y0 + cell_h * lerp(z01, z11, level))\n            top = (x0 + cell_w * lerp(z10, z11, level), y0 + cell_h)\n            bottom = (x0 + cell_w * lerp(z00, z01, level), y0)\n\n            segments = []\n            if case in [1, 14]:\n                segments.append((left, bottom))\n            elif case in [2, 13]:\n                segments.append((bottom, right))\n            elif case in [3, 12]:\n                segments.append((left, right))\n            elif case in [4, 11]:\n                segments.append((right, top))\n            elif case == 5:\n                segments.append((left, top))\n                segments.append((bottom, right))\n            elif case in [6, 9]:\n                segments.append((bottom, top))\n            elif case in [7, 8]:\n                segments.append((left, top))\n            elif case == 10:\n                segments.append((left, bottom))\n                segments.append((right, top))\n\n            for (x1, y1), (x2, y2) in segments:\n                svg_parts.append(\n                    f'<line x1=\"{x1:.1f}\" y1=\"{y1:.1f}\" x2=\"{x2:.1f}\" y2=\"{y2:.1f}\" '\n                    f'stroke=\"{INK_SOFT}\" stroke-width=\"2\" stroke-opacity=\"0.5\"/>'\n                )\n\n# Add scatter points overlay (semi-transparent) for context\nfor px, py in zip(x_data[::4], y_data[::4], strict=True):\n    sx = plot_x + (px - x_min) / (x_max - x_min) * plot_width\n    sy = plot_y + plot_height - (py - y_min) / (y_max - y_min) * plot_height\n    svg_parts.append(\n        f'<circle cx=\"{sx:.1f}\" cy=\"{sy:.1f}\" r=\"5\" fill=\"{BRAND}\" stroke=\"{PAGE_BG}\" stroke-width=\"1\" opacity=\"0.5\"/>'\n    )\n\n# Axis frame\nsvg_parts.append(\n    f'<rect x=\"{plot_x}\" y=\"{plot_y}\" width=\"{plot_width}\" height=\"{plot_height}\" '\n    f'fill=\"none\" stroke=\"{INK_SOFT}\" stroke-width=\"2\"/>'\n)\n\n# Grid lines (optional, subtle)\nn_grid_lines = 6\nfor i in range(1, n_grid_lines):\n    frac = i / n_grid_lines\n    # Vertical grid lines\n    grid_x = plot_x + frac * plot_width\n    svg_parts.append(\n        f'<line x1=\"{grid_x:.1f}\" y1=\"{plot_y}\" x2=\"{grid_x:.1f}\" y2=\"{plot_y + plot_height}\" '\n        f'stroke=\"{INK_SOFT}\" stroke-width=\"1\" stroke-opacity=\"0.1\"/>'\n    )\n    # Horizontal grid lines\n    grid_y = plot_y + frac * plot_height\n    svg_parts.append(\n        f'<line x1=\"{plot_x}\" y1=\"{grid_y:.1f}\" x2=\"{plot_x + plot_width}\" y2=\"{grid_y:.1f}\" '\n        f'stroke=\"{INK_SOFT}\" stroke-width=\"1\" stroke-opacity=\"0.1\"/>'\n    )\n\n# X-axis labels and ticks\nn_x_ticks = 7\nfor i in range(n_x_ticks):\n    frac = i / (n_x_ticks - 1)\n    tick_x = plot_x + frac * plot_width\n    tick_y = plot_y + plot_height\n    val = x_min + frac * (x_max - x_min)\n    svg_parts.append(\n        f'<line x1=\"{tick_x:.1f}\" y1=\"{tick_y}\" x2=\"{tick_x:.1f}\" y2=\"{tick_y + 15}\" '\n        f'stroke=\"{INK_SOFT}\" stroke-width=\"2\"/>'\n    )\n    svg_parts.append(\n        f'<text x=\"{tick_x:.1f}\" y=\"{tick_y + 55}\" text-anchor=\"middle\" fill=\"{INK_SOFT}\" '\n        f'style=\"font-size:18px;font-family:sans-serif\">{val:.0f}</text>'\n    )\n\n# X-axis title\nsvg_parts.append(\n    f'<text x=\"{plot_x + plot_width / 2}\" y=\"{plot_y + plot_height + 130}\" text-anchor=\"middle\" '\n    f'fill=\"{INK}\" style=\"font-size:22px;font-weight:bold;font-family:sans-serif\">Square Footage</text>'\n)\n\n# Y-axis labels and ticks\nn_y_ticks = 7\nfor i in range(n_y_ticks):\n    frac = i / (n_y_ticks - 1)\n    tick_y = plot_y + plot_height - frac * plot_height\n    tick_x = plot_x\n    val = y_min + frac * (y_max - y_min)\n    svg_parts.append(\n        f'<line x1=\"{tick_x - 15}\" y1=\"{tick_y:.1f}\" x2=\"{tick_x}\" y2=\"{tick_y:.1f}\" '\n        f'stroke=\"{INK_SOFT}\" stroke-width=\"2\"/>'\n    )\n    svg_parts.append(\n        f'<text x=\"{tick_x - 25}\" y=\"{tick_y + 12:.1f}\" text-anchor=\"end\" fill=\"{INK_SOFT}\" '\n        f'style=\"font-size:18px;font-family:sans-serif\">${val / 1000:.0f}k</text>'\n    )\n\n# Y-axis title (rotated)\ny_title_x = plot_x - 180\ny_title_y = plot_y + plot_height / 2\nsvg_parts.append(\n    f'<text x=\"{y_title_x}\" y=\"{y_title_y}\" text-anchor=\"middle\" fill=\"{INK}\" '\n    f'style=\"font-size:22px;font-weight:bold;font-family:sans-serif\" '\n    f'transform=\"rotate(-90, {y_title_x}, {y_title_y})\">Price ($)</text>'\n)\n\n# Colorbar\ncb_width = 50\ncb_height = plot_height * 0.85\ncb_x = plot_x + plot_width + 60\ncb_y = plot_y + (plot_height - cb_height) / 2\n\n# Colorbar gradient\nn_cb_segments = 80\nseg_h = cb_height / n_cb_segments\nfor i in range(n_cb_segments):\n    seg_val = z_max - (z_max - z_min) * i / (n_cb_segments - 1)\n    seg_color = interpolate_color(seg_val, z_min, z_max)\n    seg_y = cb_y + i * seg_h\n    svg_parts.append(\n        f'<rect x=\"{cb_x}\" y=\"{seg_y:.1f}\" width=\"{cb_width}\" height=\"{seg_h + 1:.1f}\" fill=\"{seg_color}\"/>'\n    )\n\n# Colorbar border\nsvg_parts.append(\n    f'<rect x=\"{cb_x}\" y=\"{cb_y}\" width=\"{cb_width}\" height=\"{cb_height}\" fill=\"none\" '\n    f'stroke=\"{INK_SOFT}\" stroke-width=\"2\"/>'\n)\n\n# Colorbar labels\nn_cb_labels = 5\nfor i in range(n_cb_labels):\n    frac = i / (n_cb_labels - 1)\n    val = z_max - (z_max - z_min) * frac\n    label_y = cb_y + frac * cb_height + 12\n    svg_parts.append(\n        f'<text x=\"{cb_x + cb_width + 15}\" y=\"{label_y:.1f}\" fill=\"{INK_SOFT}\" '\n        f'style=\"font-size:16px;font-family:sans-serif\">{val:.1f}</text>'\n    )\n\n# Colorbar title\ncb_title_x = cb_x + cb_width / 2\ncb_title_y = cb_y - 30\nsvg_parts.append(\n    f'<text x=\"{cb_title_x}\" y=\"{cb_title_y}\" text-anchor=\"middle\" fill=\"{INK}\" '\n    f'style=\"font-size:22px;font-weight:bold;font-family:sans-serif\">Density</text>'\n)\n\n# Combine all SVG parts\ncustom_svg = \"\\n\".join(svg_parts)\n\n# Add dummy data point (required by pygal)\nchart.add(\"\", [(0, 0)])\n\n# Render base chart and inject custom SVG\nbase_svg = chart.render(is_unicode=True)\n\n# Insert custom contour SVG before the closing </svg> tag\noutput_svg = base_svg.replace(\"</svg>\", f\"{custom_svg}\\n</svg>\")\n\n# Save SVG\nwith open(f\"plot-{THEME}.svg\", \"w\", encoding=\"utf-8\") as f:\n    f.write(output_svg)\n\n# Convert to PNG using cairosvg\ncairosvg.svg2png(bytestring=output_svg.encode(\"utf-8\"), write_to=f\"plot-{THEME}.png\")\n\n# Save interactive HTML\nhtml_content = f\"\"\"<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\">\n    <title>contour-density - 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        {output_svg}\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"}