{"spec_id":"shap-summary","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nshap-summary: SHAP Summary Plot\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 81/100 | Created: 2026-05-14\n\"\"\"\n\nimport os\nimport re\nimport sys\n\n\nsys.path = [p for p in sys.path if not p.endswith(\"implementations/python\")]\n\nimport matplotlib as mpl\nimport numpy as np\nimport pygal\nfrom matplotlib.colors import Normalize\nfrom pygal.style import Style\n\n\nscript_dir = os.path.dirname(os.path.abspath(__file__))\nos.chdir(script_dir)\n\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\"\nRULE = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\n\nnp.random.seed(42)\nn_samples = 200\nn_features = 10\n\nfeature_names = [\n    \"Age\",\n    \"Income\",\n    \"Credit Score\",\n    \"Debt Ratio\",\n    \"Employment Years\",\n    \"Savings\",\n    \"Loan Amount\",\n    \"Interest Rate\",\n    \"Payment History\",\n    \"Inquiries\",\n]\n\nshap_values = np.zeros((n_samples, n_features))\nfeature_values = np.zeros((n_samples, n_features))\n\nfor i in range(n_features):\n    shap_values[:, i] = np.random.normal(0, 0.3 + i * 0.05, n_samples)\n    feature_values[:, i] = np.random.uniform(0, 1, n_samples)\n\nimportance = np.abs(shap_values).mean(axis=0)\nsorted_idx = np.argsort(-importance)\n\nshap_values = shap_values[:, sorted_idx]\nfeature_values = feature_values[:, sorted_idx]\nfeature_names = [feature_names[i] for i in sorted_idx]\n\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=(\"#009E73\",),\n    title_font_size=28,\n    label_font_size=22,\n    major_label_font_size=18,\n    legend_font_size=16,\n    stroke_width=2,\n)\n\nchart = pygal.XY(\n    style=custom_style,\n    title=\"shap-summary · pygal · anyplot.ai\",\n    x_title=\"SHAP Value\",\n    y_title=\"Feature\",\n    width=4800,\n    height=2700,\n    show_legend=False,\n    y_labels=feature_names,\n    show_x_guides=True,\n)\n\ncmap = mpl.colormaps.get_cmap(\"BrBG\")\nnorm = Normalize(vmin=0, vmax=1)\n\nfor j in range(n_features):\n    n_bands = 10\n    for band in range(n_bands):\n        band_color_value = (band + 0.5) / n_bands\n        color_rgba = cmap(norm(band_color_value))\n        hex_color = \"#{:02x}{:02x}{:02x}\".format(\n            int(color_rgba[0] * 255), int(color_rgba[1] * 255), int(color_rgba[2] * 255)\n        )\n\n        points = []\n        for i in range(n_samples):\n            feature_val = feature_values[i, j]\n            if band / n_bands <= feature_val < (band + 1) / n_bands:\n                jitter = np.random.normal(0, 0.15)\n                points.append((shap_values[i, j], j + jitter))\n\n        if points:\n            chart.add(f\"{feature_names[j]}_band_{band}\", points, show_legend=False, color=hex_color)\n\n# Add reference line at x=0 (neutral SHAP value)\nx_min, x_max = shap_values.min(), shap_values.max()\nmargin = (x_max - x_min) * 0.05\nchart.add(\n    \"Zero Reference\",\n    [(0, -0.7), (0, n_features - 0.3)],\n    show_legend=False,\n    stroke_style={\"width\": 2, \"dasharray\": \"5,5\"},\n    color=INK_SOFT,\n)\n\n# Render to SVG string and save HTML\nsvg_content = chart.render()\n\n# Post-process SVG for dark theme to fix text colors\nif THEME == \"dark\":\n    svg_str = svg_content.decode(\"utf-8\") if isinstance(svg_content, bytes) else svg_content\n\n    # Replace/add fill attributes in all text elements with light color\n    # First, replace any existing fill=\"#...\" in text elements\n    svg_str = re.sub(r'(<text\\s+[^>]*?)fill=\"[^\"]*\"', rf'\\1fill=\"{INK}\"', svg_str)\n\n    # For text elements without fill attribute, add one\n    def add_fill_to_text(match):\n        text_tag = match.group(0)\n        if \"fill=\" not in text_tag:\n            return text_tag[:-1] + f' fill=\"{INK}\">'\n        return text_tag\n\n    svg_str = re.sub(r\"<text[^>]*>\", add_fill_to_text, svg_str)\n    svg_content = svg_str.encode(\"utf-8\")\n\n# Save HTML\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(svg_content)\n\n# Render to PNG (using pygal's native renderer)\nchart.render_to_png(f\"plot-{THEME}.png\")\n"}