Skip to content

Commit d8f24f8

Browse files
Merge branch 'main' into implementation/dendrogram-basic/altair
2 parents be73d61 + d591a9c commit d8f24f8

8 files changed

Lines changed: 628 additions & 476 deletions

File tree

Lines changed: 57 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
""" pyplots.ai
22
dendrogram-basic: Basic Dendrogram
3-
Library: matplotlib 3.10.8 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-23
3+
Library: matplotlib 3.10.8 | Python 3.14.3
4+
Quality: 92/100 | Updated: 2026-04-05
55
"""
66

77
import matplotlib.pyplot as plt
88
import numpy as np
9-
from scipy.cluster.hierarchy import dendrogram, linkage
9+
from matplotlib.collections import LineCollection
10+
from scipy.cluster.hierarchy import dendrogram, linkage, set_link_color_palette
1011

1112

1213
# Data - Iris flower measurements (4 features for 15 samples)
1314
np.random.seed(42)
1415

15-
# Simulate iris-like measurements: sepal length, sepal width, petal length, petal width
16-
# Three species with distinct characteristics
1716
samples_per_species = 5
18-
1917
labels = []
2018
data = []
2119

@@ -24,10 +22,10 @@
2422
labels.append(f"Setosa-{i + 1}")
2523
data.append(
2624
[
27-
5.0 + np.random.randn() * 0.3, # sepal length
28-
3.4 + np.random.randn() * 0.3, # sepal width
29-
1.5 + np.random.randn() * 0.2, # petal length
30-
0.3 + np.random.randn() * 0.1, # petal width
25+
5.0 + np.random.randn() * 0.3,
26+
3.4 + np.random.randn() * 0.3,
27+
1.5 + np.random.randn() * 0.2,
28+
0.3 + np.random.randn() * 0.1,
3129
]
3230
)
3331

@@ -36,10 +34,10 @@
3634
labels.append(f"Versicolor-{i + 1}")
3735
data.append(
3836
[
39-
5.9 + np.random.randn() * 0.4, # sepal length
40-
2.8 + np.random.randn() * 0.3, # sepal width
41-
4.3 + np.random.randn() * 0.4, # petal length
42-
1.3 + np.random.randn() * 0.2, # petal width
37+
5.9 + np.random.randn() * 0.4,
38+
2.8 + np.random.randn() * 0.3,
39+
4.3 + np.random.randn() * 0.4,
40+
1.3 + np.random.randn() * 0.2,
4341
]
4442
)
4543

@@ -48,10 +46,10 @@
4846
labels.append(f"Virginica-{i + 1}")
4947
data.append(
5048
[
51-
6.6 + np.random.randn() * 0.5, # sepal length
52-
3.0 + np.random.randn() * 0.3, # sepal width
53-
5.5 + np.random.randn() * 0.5, # petal length
54-
2.0 + np.random.randn() * 0.3, # petal width
49+
6.6 + np.random.randn() * 0.5,
50+
3.0 + np.random.randn() * 0.3,
51+
5.5 + np.random.randn() * 0.5,
52+
2.0 + np.random.randn() * 0.3,
5553
]
5654
)
5755

@@ -61,33 +59,57 @@
6159
linkage_matrix = linkage(data, method="ward")
6260

6361
# Plot
64-
fig, ax = plt.subplots(figsize=(16, 9))
62+
fig, ax = plt.subplots(figsize=(16, 9), facecolor="white")
63+
ax.set_facecolor("#FAFAFA")
64+
65+
# Custom cluster colors via set_link_color_palette (matplotlib/scipy integration)
66+
cluster_colors = ["#306998", "#D4722A", "#3A8A5C"]
67+
set_link_color_palette(cluster_colors)
68+
69+
# Set threshold between the 2nd and 3rd highest merge distances to reveal 3 clusters
70+
sorted_distances = sorted(linkage_matrix[:, 2])
71+
color_threshold = (sorted_distances[-2] + sorted_distances[-3]) / 2
6572

66-
# Create dendrogram with custom colors
67-
dendrogram(
73+
dendro = dendrogram(
6874
linkage_matrix,
6975
labels=labels,
7076
ax=ax,
71-
leaf_rotation=45,
72-
leaf_font_size=14,
73-
above_threshold_color="#306998", # Python Blue for main branches
74-
color_threshold=0.7 * max(linkage_matrix[:, 2]), # Color threshold for clusters
77+
leaf_rotation=40,
78+
leaf_font_size=16,
79+
above_threshold_color="#AAAAAA",
80+
color_threshold=color_threshold,
7581
)
7682

83+
# Post-render enhancement: adjust line widths via LineCollection traversal
84+
for child in ax.get_children():
85+
if isinstance(child, LineCollection):
86+
child.set_linewidths(3.0)
87+
child.set_capstyle("round")
88+
child.set_joinstyle("round")
89+
7790
# Style
78-
ax.set_xlabel("Sample", fontsize=20)
79-
ax.set_ylabel("Distance (Ward)", fontsize=20)
80-
ax.set_title("dendrogram-basic · matplotlib · pyplots.ai", fontsize=24)
81-
ax.tick_params(axis="both", labelsize=16)
82-
ax.tick_params(axis="x", labelsize=14, rotation=45)
91+
ax.set_xlabel("Iris Sample", fontsize=20, labelpad=10)
92+
ax.set_ylabel("Ward Linkage Distance", fontsize=20, labelpad=10)
93+
ax.set_title(
94+
"Iris Species Clustering · dendrogram-basic · matplotlib · pyplots.ai",
95+
fontsize=24,
96+
fontweight="medium",
97+
pad=20,
98+
color="#333333",
99+
)
100+
ax.tick_params(axis="both", labelsize=16, colors="#555555")
101+
ax.tick_params(axis="x", labelsize=16, rotation=40)
83102

84-
# Adjust spines for cleaner look
85103
ax.spines["top"].set_visible(False)
86104
ax.spines["right"].set_visible(False)
105+
ax.spines["left"].set_linewidth(0.6)
106+
ax.spines["left"].set_color("#CCCCCC")
107+
ax.spines["bottom"].set_linewidth(0.6)
108+
ax.spines["bottom"].set_color("#CCCCCC")
87109

88-
# Add subtle grid on y-axis only
89-
ax.yaxis.grid(True, alpha=0.3, linestyle="--")
110+
# Subtle grid on y-axis only
111+
ax.yaxis.grid(True, alpha=0.15, linewidth=0.6, color="#888888")
90112
ax.set_axisbelow(True)
91113

92-
plt.tight_layout()
93-
plt.savefig("plot.png", dpi=300, bbox_inches="tight")
114+
plt.tight_layout(pad=1.5)
115+
plt.savefig("plot.png", dpi=300, bbox_inches="tight", facecolor="white")
Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
""" pyplots.ai
22
dendrogram-basic: Basic Dendrogram
3-
Library: plotly 6.5.0 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-23
3+
Library: plotly 6.5.2 | Python 3.14.3
4+
Quality: 90/100 | Updated: 2026-04-05
55
"""
66

77
import numpy as np
88
import plotly.figure_factory as ff
9+
from scipy.cluster.hierarchy import linkage
910

1011

11-
# Data - Iris-like flower measurements for 15 samples
12+
# Data - Iris flower measurements for 15 samples across 3 species
1213
np.random.seed(42)
1314
labels = [
1415
"Setosa-1",
@@ -28,38 +29,68 @@
2829
"Virginica-5",
2930
]
3031

31-
# Create clustered data representing flower measurements (sepal length, sepal width, petal length, petal width)
32-
# Setosa: small petals, medium sepals
32+
# Sepal length, sepal width, petal length, petal width
3333
setosa = np.random.randn(5, 4) * 0.3 + np.array([5.0, 3.4, 1.5, 0.2])
34-
# Versicolor: medium measurements
3534
versicolor = np.random.randn(5, 4) * 0.4 + np.array([5.9, 2.8, 4.3, 1.3])
36-
# Virginica: large petals and sepals
3735
virginica = np.random.randn(5, 4) * 0.4 + np.array([6.6, 3.0, 5.5, 2.0])
38-
3936
data = np.vstack([setosa, versicolor, virginica])
4037

41-
# Create dendrogram
38+
# Colorscale: maps to scipy color keys (b, c, g, k, m, r, w, y) alphabetically
39+
# C0->b(idx 0), C1->g(idx 2), C2->r(idx 5), C3->c(idx 1), above-threshold->C0
40+
colorscale = [
41+
"#306998", # b -> Python Blue (above-threshold merges)
42+
"#E1974C", # c -> Warm amber
43+
"#52A675", # g -> Muted green
44+
"#333333", # k
45+
"#8B6BAE", # m
46+
"#D45B5B", # r -> Soft coral
47+
"#ffffff", # w
48+
"#C4A437", # y
49+
]
50+
4251
fig = ff.create_dendrogram(
43-
data,
44-
labels=labels,
45-
linkagefun=lambda x: __import__("scipy.cluster.hierarchy", fromlist=["linkage"]).linkage(x, method="ward"),
46-
color_threshold=3.0,
52+
data, labels=labels, colorscale=colorscale, linkagefun=lambda x: linkage(x, method="ward"), color_threshold=3.5
4753
)
4854

49-
# Update layout for large canvas
55+
# Add merge distance hover to each branch
56+
for trace in fig.data:
57+
trace.line.width = 3
58+
merge_height = max(y for y in trace.y if y > 0) if any(y > 0 for y in trace.y) else 0
59+
trace.hovertemplate = f"Merge distance: {merge_height:.2f}<extra></extra>"
60+
61+
# Layout
5062
fig.update_layout(
51-
title={"text": "dendrogram-basic · plotly · pyplots.ai", "font": {"size": 28}, "x": 0.5, "xanchor": "center"},
52-
xaxis={"title": {"text": "Iris Flower Samples", "font": {"size": 22}}, "tickfont": {"size": 16}, "tickangle": -45},
53-
yaxis={"title": {"text": "Distance (Ward)", "font": {"size": 22}}, "tickfont": {"size": 18}},
63+
title={
64+
"text": "dendrogram-basic · plotly · pyplots.ai",
65+
"font": {"size": 28, "color": "#2a2a2a", "family": "Arial, sans-serif"},
66+
"x": 0.5,
67+
"xanchor": "center",
68+
},
69+
xaxis={
70+
"title": {"text": "Iris Flower Samples", "font": {"size": 22}},
71+
"tickfont": {"size": 16},
72+
"tickangle": -35,
73+
"showline": True,
74+
"linecolor": "#cccccc",
75+
"zeroline": False,
76+
},
77+
yaxis={
78+
"title": {"text": "Distance (Ward linkage)", "font": {"size": 22}},
79+
"tickfont": {"size": 18},
80+
"showline": True,
81+
"linecolor": "#cccccc",
82+
"gridcolor": "rgba(0,0,0,0.06)",
83+
"gridwidth": 1,
84+
"zeroline": False,
85+
},
5486
template="plotly_white",
5587
width=1600,
5688
height=900,
57-
margin={"l": 80, "r": 40, "t": 100, "b": 150},
89+
margin={"l": 90, "r": 50, "t": 100, "b": 150},
90+
plot_bgcolor="#ffffff",
91+
hoverlabel={"bgcolor": "white", "font_size": 14, "bordercolor": "#cccccc"},
5892
)
5993

60-
# Update line widths for visibility
61-
fig.update_traces(line={"width": 3})
62-
63-
# Save outputs
94+
# Save
6495
fig.write_image("plot.png", width=1600, height=900, scale=3)
65-
fig.write_html("plot.html")
96+
fig.write_html("plot.html", include_plotlyjs="cdn")

0 commit comments

Comments
 (0)