{"spec_id":"dendrogram-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\ndendrogram-basic: Basic Dendrogram\nLibrary: seaborn 0.13.2 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-06-18\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.colors import LinearSegmentedColormap\n\n\n# Theme tokens (Imprint palette, theme-adaptive chrome)\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 categorical palette — first series always #009E73\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Imprint sequential colormap for single-polarity heatmap values\nimprint_seq = LinearSegmentedColormap.from_list(\"imprint_seq\", [\"#009E73\", \"#4467A3\"])\n\n# Species colors: Imprint positions 1-3\nspecies_names = [\"Setosa\", \"Versicolor\", \"Virginica\"]\nspecies_colors = dict(zip(species_names, IMPRINT_PALETTE[:3], strict=True))\n\n# Apply theme-adaptive seaborn theme before any figure is created\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Data — iris dataset, 10 samples per species (30 total for readable dendrogram)\nnp.random.seed(42)\niris = sns.load_dataset(\"iris\")\nsamples = (\n    iris.groupby(\"species\").apply(lambda g: g.sample(10, random_state=42), include_groups=False).reset_index(level=0)\n)\n\nfeature_cols = [\"sepal_length\", \"sepal_width\", \"petal_length\", \"petal_width\"]\nfeatures = samples[feature_cols].copy()\n\n# Sample labels: Species-Number\ncounters = dict.fromkeys([\"setosa\", \"versicolor\", \"virginica\"], 0)\nlabels = []\nspecies_list = []\nfor species in samples[\"species\"]:\n    counters[species] += 1\n    labels.append(f\"{species.title()}-{counters[species]}\")\n    species_list.append(species.title())\n\nfeatures.index = labels\nfeatures.columns = [\"Sepal Length\", \"Sepal Width\", \"Petal Length\", \"Petal Width\"]\n\n# Row color strip by species (seaborn's distinctive clustermap feature)\nrow_colors = pd.Series([species_colors[sp] for sp in species_list], index=labels, name=\"Species\")\n\n# Plot — square canvas suits the symmetric clustermap grid layout\ng = sns.clustermap(\n    features,\n    method=\"ward\",\n    row_colors=row_colors,\n    col_cluster=True,\n    cmap=imprint_seq,\n    figsize=(6, 6),\n    dendrogram_ratio=(0.25, 0.12),\n    linewidths=0.5,\n    linecolor=PAGE_BG,\n    cbar_kws={\"label\": \"Feature Value\"},\n    tree_kws={\"linewidths\": 2.0, \"colors\": INK_SOFT},\n    xticklabels=True,\n    yticklabels=True,\n)\n\ng.figure.set_facecolor(PAGE_BG)\n\n# Axis labels and tick sizes\ng.ax_heatmap.set_xlabel(\"Iris Features\", fontsize=10, color=INK)\ng.ax_heatmap.set_ylabel(\"Iris Samples (by Species)\", fontsize=10, color=INK)\ng.ax_heatmap.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\n\n# Color y-axis labels by species for visual storytelling\nfor lbl in g.ax_heatmap.get_yticklabels():\n    species = lbl.get_text().rsplit(\"-\", 1)[0]\n    if species in species_colors:\n        lbl.set_color(species_colors[species])\n        lbl.set_fontweight(\"bold\")\n\n# Style x-axis (feature) labels\nfor lbl in g.ax_heatmap.get_xticklabels():\n    lbl.set_fontsize(8)\n    lbl.set_color(INK_SOFT)\n    lbl.set_rotation(30)\n    lbl.set_ha(\"right\")\n\n# Remove the 'Species' column label from the row colors strip x-axis\ng.ax_row_colors.tick_params(bottom=False, labelbottom=False)\n\n# Style colorbar ticks\ng.cax.tick_params(labelsize=8, colors=INK_SOFT)\ng.cax.set_facecolor(PAGE_BG)\n\n# Species legend — placed outside the heatmap to the right to avoid data overlap\nlegend_handles = [\n    plt.Line2D([0], [0], marker=\"s\", color=\"none\", markerfacecolor=c, markeredgecolor=INK_SOFT, markersize=10, label=n)\n    for n, c in species_colors.items()\n]\ng.ax_heatmap.legend(\n    handles=legend_handles,\n    title=\"Species\",\n    loc=\"upper left\",\n    bbox_to_anchor=(1.02, 1.0),\n    fontsize=8,\n    title_fontsize=9,\n    framealpha=0.95,\n    facecolor=ELEVATED_BG,\n    edgecolor=INK_SOFT,\n)\n\n# Title\ntitle = \"dendrogram-basic · python · seaborn · anyplot.ai\"\ng.figure.suptitle(title, fontsize=12, fontweight=\"medium\", color=INK, y=0.99)\n\n# Save — square canvas: figsize=(6,6) × dpi=400 → 2400×2400 px (no bbox_inches)\ng.figure.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}