{"spec_id":"scatter-embedding","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nscatter-embedding: t-SNE and UMAP Embedding Visualization\nLibrary: pygal 3.1.3 | Python 3.13.14\nQuality: 87/100 | Updated: 2026-08-11\n\"\"\"\n\nimport os\nimport sys\n\n\n# Work around naming conflict: pygal.py filename shadows pygal package\nsys.path.pop(0)\n\nimport numpy as np\nimport pygal\nfrom pygal.formatters import Significant\nfrom pygal.style import Style\nfrom sklearn.datasets import make_blobs\nfrom sklearn.manifold import TSNE\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_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette positions 1-6 at 65% opacity to handle overlapping points\nIMPRINT_ALPHA = (\n    \"rgba(0,158,115,0.65)\",  # #009E73 brand green\n    \"rgba(196,117,253,0.65)\",  # #C475FD lavender\n    \"rgba(68,103,163,0.65)\",  # #4467A3 blue\n    \"rgba(189,130,51,0.65)\",  # #BD8233 ochre\n    \"rgba(174,48,48,0.65)\",  # #AE3030 matte red\n    \"rgba(42,188,205,0.65)\",  # #2ABCCD cyan\n)\n\nCELL_TYPES = [\"T-cells\", \"B-cells\", \"NK cells\", \"Monocytes\", \"Dendritic cells\", \"Neutrophils\"]\n\n# Data — 15-D single-cell RNA-seq-style blobs reduced to 2-D with t-SNE\nnp.random.seed(42)\nX_high, labels = make_blobs(n_samples=600, n_features=15, centers=len(CELL_TYPES), cluster_std=2.0, random_state=42)\n\ntsne = TSNE(n_components=2, perplexity=30, max_iter=500, random_state=42)\nX_2d = tsne.fit_transform(X_high)\n\ncentroids = [X_2d[labels == i].mean(axis=0) for i in range(len(CELL_TYPES))]\n\n# Plot — pygal splits the title on \"\\n\" into stacked lines, giving a native\n# subtitle slot for the algorithm + key parameter (pygal has no dedicated\n# subtitle field). value_formatter trims hover-tooltip coordinates to 3\n# significant digits instead of pygal's noisy float default.\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=IMPRINT_ALPHA + (INK,),\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n)\n\n# A thin ink-colored outline on every marker adds a CVD-safety margin for the\n# 6-series borderline color range (style-guide \"Optional outline pattern\"),\n# since pygal's XY chart has no per-series marker-shape API to fall back on.\ndot_outline_css = f\".dot {{ stroke: {INK} !important; stroke-width: 1.5px !important; stroke-opacity: 1 !important; }}\"\n\nchart = pygal.XY(\n    style=custom_style,\n    width=3200,\n    height=1800,\n    title=\"scatter-embedding · python · pygal · anyplot.ai\\nt-SNE embedding · perplexity=30\",\n    x_title=\"t-SNE Dimension 1\",\n    y_title=\"t-SNE Dimension 2\",\n    show_x_labels=False,\n    show_y_labels=False,\n    stroke=False,\n    dots_size=6,\n    print_values=False,\n    value_formatter=Significant(3),\n    truncate_legend=-1,\n    css=(\"file://style.css\", \"file://graph.css\", f\"inline:{dot_outline_css}\"),\n)\n\nfor i, name in enumerate(CELL_TYPES):\n    points = [(float(x), float(y)) for x, y in X_2d[labels == i]]\n    chart.add(name, points)\n\nchart.add(\"Cluster centroids\", [(float(x), float(y)) for x, y in centroids], dots_size=16)\n\n# Save\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}