{"spec_id":"scatter-embedding","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nscatter-embedding: t-SNE and UMAP Embedding Visualization\nLibrary: altair 6.2.2 | Python 3.13.14\nQuality: 93/100 | Updated: 2026-08-11\n\"\"\"\n\nimport os\nimport sys\n\n\nsys.path = [p for p in sys.path if not p.endswith(\"implementations/python\")]\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom sklearn.datasets import make_blobs\nfrom sklearn.manifold import TSNE\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Imprint palette (positions 1-7, canonical order)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\"]\n\n# Single-cell RNA-seq scenario with domain-specific cell type labels\nCELL_TYPES = [\"T cells\", \"B cells\", \"NK cells\", \"Monocytes\", \"Dendritic cells\", \"Macrophages\", \"Plasma cells\"]\nCELL_ABBR = {\n    \"T cells\": \"T\",\n    \"B cells\": \"B\",\n    \"NK cells\": \"NK\",\n    \"Monocytes\": \"Mono\",\n    \"Dendritic cells\": \"DC\",\n    \"Macrophages\": \"Mac\",\n    \"Plasma cells\": \"PC\",\n}\n\n# Data — varying cluster_std for realistic compactness differences across cell types\nnp.random.seed(42)\nn_clusters = 7\ncluster_stds = [1.2, 2.0, 1.5, 1.8, 1.0, 2.2, 1.6]\nX_high, y_labels = make_blobs(\n    n_samples=700, n_features=20, centers=n_clusters, cluster_std=cluster_stds, random_state=42\n)\nX_2d = TSNE(n_components=2, perplexity=30, random_state=42).fit_transform(X_high)\n\ndf = pd.DataFrame({\"tsne_1\": X_2d[:, 0], \"tsne_2\": X_2d[:, 1], \"cluster\": [CELL_TYPES[idx] for idx in y_labels]})\n\ncentroids = df.groupby(\"cluster\")[[\"tsne_1\", \"tsne_2\"]].mean()\ncentroids = centroids.loc[CELL_TYPES].reset_index()\ncentroids[\"abbr\"] = [CELL_ABBR[c] for c in centroids[\"cluster\"]]\n\n# Tighten the scale domain to the data extent (+6% pad) instead of Altair's\n# default \"nice\" auto-domain — sparse t-SNE clusters otherwise leave large\n# unused margins on the canvas.\nx_min, x_max = df[\"tsne_1\"].min(), df[\"tsne_1\"].max()\ny_min, y_max = df[\"tsne_2\"].min(), df[\"tsne_2\"].max()\nx_pad, y_pad = (x_max - x_min) * 0.06, (y_max - y_min) * 0.06\nx_domain = [x_min - x_pad, x_max + x_pad]\ny_domain = [y_min - y_pad, y_max + y_pad]\n\n# Interactive selection bound to legend — clicking a cell type highlights its cluster\nselection = alt.selection_point(fields=[\"cluster\"], bind=\"legend\")\n\n# Marker size/opacity tuned for 700 overlapping points (high-density heuristic)\nscatter = (\n    alt.Chart(df)\n    .mark_circle(size=45, strokeWidth=0.5)\n    .encode(\n        x=alt.X(\n            \"tsne_1:Q\",\n            scale=alt.Scale(domain=x_domain, nice=False),\n            axis=alt.Axis(labels=False, ticks=False, domain=False, grid=False, title=\"t-SNE Dimension 1\"),\n        ),\n        y=alt.Y(\n            \"tsne_2:Q\",\n            scale=alt.Scale(domain=y_domain, nice=False),\n            axis=alt.Axis(labels=False, ticks=False, domain=False, grid=False, title=\"t-SNE Dimension 2\"),\n        ),\n        color=alt.Color(\n            \"cluster:N\", scale=alt.Scale(domain=CELL_TYPES, range=IMPRINT), legend=alt.Legend(title=\"Cell Type\")\n        ),\n        opacity=alt.condition(selection, alt.value(0.55), alt.value(0.12)),\n        stroke=alt.value(PAGE_BG),\n        tooltip=[\n            \"cluster:N\",\n            alt.Tooltip(\"tsne_1:Q\", title=\"t-SNE 1\", format=\".2f\"),\n            alt.Tooltip(\"tsne_2:Q\", title=\"t-SNE 2\", format=\".2f\"),\n        ],\n    )\n    .add_params(selection)\n)\n\n# Direct abbreviated-name labels on centroids (no legend cross-referencing needed).\n# A background-colored halo stroke behind the text keeps labels legible against\n# every cluster hue in both themes, independent of the underlying data color.\ncentroid_marks = (\n    alt.Chart(centroids)\n    .mark_text(fontSize=13, fontWeight=\"bold\", dy=-9, stroke=PAGE_BG, strokeWidth=0.75)\n    .encode(\n        x=alt.X(\"tsne_1:Q\", scale=alt.Scale(domain=x_domain, nice=False)),\n        y=alt.Y(\"tsne_2:Q\", scale=alt.Scale(domain=y_domain, nice=False)),\n        text=\"abbr:N\",\n        color=alt.value(INK),\n    )\n)\n\ntitle_text = \"scatter-embedding · python · altair · anyplot.ai\"\ntitle_params = alt.TitleParams(\n    text=title_text,\n    subtitle=\"t-SNE (perplexity=30) · 20-dimensional synthetic scRNA-seq · 7 cell types, 700 cells\",\n    fontSize=round(18 * min(1.0, 67 / len(title_text))),\n    subtitleFontSize=12,\n    color=INK,\n    subtitleColor=INK_SOFT,\n    anchor=\"start\",\n)\n\nchart = (\n    alt.layer(scatter, centroid_marks)\n    .properties(\n        width=620,\n        height=320,\n        padding={\"left\": 0, \"right\": 0, \"top\": 0, \"bottom\": 0},\n        title=title_params,\n        background=PAGE_BG,\n    )\n    .interactive()\n    .configure_view(continuousWidth=620, continuousHeight=320, fill=PAGE_BG, stroke=\"transparent\")\n    .configure_axis(titleColor=INK, titleFontSize=12)\n    .configure_legend(\n        fillColor=ELEVATED_BG,\n        strokeColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=10,\n        titleFontSize=10,\n        cornerRadius=4,\n        padding=8,\n    )\n)\n\n# Save\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\n# Pad-only to the canonical canvas — vl-convert's title/legend padding makes the\n# saved PNG larger than width*scale_factor. Never crop: cropping clips title/axis\n# labels and trips the AR-09 edge-clipping auto-reject.\nTW, TH = 3200, 1800\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}x{_h}, exceeds target {TW}x{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\nchart.save(f\"plot-{THEME}.html\")\n"}