Skip to content

Commit 1f99646

Browse files
update(dendrogram-basic): highcharts — comprehensive quality review (#5207)
## Summary Updated **highcharts** implementation for **dendrogram-basic**. **Changes:** Comprehensive review improving code quality, data choice, visual design, spec compliance, and library feature usage. ## Test Plan - [x] Preview images uploaded to GCS staging - [x] Implementation file passes ruff format/check - [x] Metadata YAML updated with current versions - [ ] Automated review triggered --- Generated with [Claude Code](https://claude.com/claude-code) `/update` command --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent f617721 commit 1f99646

File tree

2 files changed

+201
-172
lines changed

2 files changed

+201
-172
lines changed

plots/dendrogram-basic/implementations/highcharts.py

Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
""" pyplots.ai
22
dendrogram-basic: Basic Dendrogram
3-
Library: highcharts 1.10.3 | Python 3.13.11
4-
Quality: 93/100 | Created: 2025-12-17
3+
Library: highcharts 1.10.3 | Python 3.14.3
4+
Quality: 84/100 | Created: 2026-04-05
55
"""
66

77
import tempfile
@@ -66,106 +66,109 @@
6666
# Compute linkage matrix using Ward's method
6767
linkage_matrix = linkage(data, method="ward")
6868

69-
# Get dendrogram structure
70-
dend = dendrogram(linkage_matrix, labels=labels, no_plot=True)
71-
72-
# Extract coordinates for drawing
73-
icoord = dend["icoord"] # x coordinates
74-
dcoord = dend["dcoord"] # y coordinates (distances)
75-
ivl = dend["ivl"] # leaf labels in order
76-
77-
# Create line series data for each U-shape in the dendrogram
78-
line_series_data = []
79-
for xs, ys in zip(icoord, dcoord, strict=True):
80-
# Each U-shape has 4 points
81-
points = [[xs[j], ys[j]] for j in range(4)]
82-
line_series_data.append(
83-
{"data": points, "color": "#306998", "lineWidth": 3, "marker": {"enabled": False}, "enableMouseTracking": False}
84-
)
69+
# Get dendrogram structure with color threshold to show clusters
70+
dend = dendrogram(linkage_matrix, labels=labels, no_plot=True, color_threshold=0.7 * max(linkage_matrix[:, 2]))
71+
72+
# Extract coordinates
73+
icoord = dend["icoord"]
74+
dcoord = dend["dcoord"]
75+
ivl = dend["ivl"]
76+
color_list = dend["color_list"]
77+
78+
# Map scipy default colors to a cohesive palette anchored on Python Blue
79+
color_map = {
80+
"C0": "#306998", # Python Blue - primary cluster
81+
"C1": "#E07A3A", # Warm orange - secondary cluster
82+
"C2": "#5BA05B", # Muted green - tertiary cluster
83+
"C3": "#8B6EB8", # Soft purple
84+
"C4": "#C75A5A", # Muted red
85+
"b": "#306998", # Default blue mapped to Python Blue
86+
}
8587

8688
# Create chart
8789
chart = Chart(container="container")
8890
chart.options = HighchartsOptions()
8991

90-
# Configure chart
9192
chart.options.chart = {
9293
"type": "line",
9394
"width": 4800,
9495
"height": 2700,
9596
"backgroundColor": "#ffffff",
96-
"marginBottom": 300,
97-
"spacingBottom": 50,
97+
"marginBottom": 280,
98+
"spacingTop": 30,
99+
"spacingLeft": 80,
100+
"spacingRight": 80,
98101
}
99102

100103
# Title
101104
chart.options.title = {
102-
"text": "dendrogram-basic · highcharts · pyplots.ai",
103-
"style": {"fontSize": "56px", "fontWeight": "bold"},
105+
"text": "Iris Species Clustering \u00b7 dendrogram-basic \u00b7 highcharts \u00b7 pyplots.ai",
106+
"style": {"fontSize": "44px", "fontWeight": "600", "color": "#2c3e50"},
107+
"margin": 40,
104108
}
105109

106110
# X-axis with sample labels
107-
# Dendrogram x-coords are at 5, 15, 25, ... for leaves
108111
x_positions = [(i * 10) + 5 for i in range(len(ivl))]
109112
chart.options.x_axis = {
110-
"title": {"text": "Sample", "style": {"fontSize": "44px"}},
113+
"title": {"text": "Sample", "style": {"fontSize": "32px", "color": "#555"}},
111114
"tickPositions": x_positions,
112-
"labels": {"style": {"fontSize": "24px"}, "rotation": 45},
115+
"labels": {"style": {"fontSize": "26px", "color": "#444"}, "rotation": 45},
113116
"min": 0,
114117
"max": len(ivl) * 10,
115118
"gridLineWidth": 0,
116119
"lineWidth": 2,
120+
"lineColor": "#cccccc",
121+
"tickWidth": 0,
117122
}
118123

119124
# Y-axis for distance
120125
chart.options.y_axis = {
121-
"title": {"text": "Distance (Ward)", "style": {"fontSize": "44px"}},
122-
"labels": {"style": {"fontSize": "32px"}},
123-
"gridLineColor": "rgba(0,0,0,0.1)",
126+
"title": {"text": "Distance (Ward)", "style": {"fontSize": "32px", "color": "#555"}},
127+
"labels": {"style": {"fontSize": "24px", "color": "#444"}},
128+
"gridLineColor": "rgba(0,0,0,0.08)",
124129
"gridLineWidth": 1,
130+
"lineWidth": 2,
131+
"lineColor": "#cccccc",
125132
"min": 0,
133+
"tickInterval": 1,
126134
}
127135

128136
# Hide legend
129137
chart.options.legend = {"enabled": False}
130138

131-
# Plot options for lines
139+
# Plot options
132140
chart.options.plot_options = {
133141
"line": {"lineWidth": 4, "marker": {"enabled": False}, "states": {"hover": {"enabled": False}}}
134142
}
135143

136-
# Add each line segment as a series
137-
for series_data in line_series_data:
144+
# Add each dendrogram branch as a colored series
145+
for xs, ys, color_key in zip(icoord, dcoord, color_list, strict=True):
138146
series = LineSeries()
139-
series.data = series_data["data"]
140-
series.color = series_data["color"]
147+
series.data = [[xs[j], ys[j]] for j in range(4)]
148+
series.color = color_map.get(color_key, "#306998")
141149
series.line_width = 4
142150
series.marker = {"enabled": False}
143151
series.enable_mouse_tracking = False
144152
chart.add_series(series)
145153

146154
# Download Highcharts JS for inline embedding
147-
highcharts_url = "https://code.highcharts.com/highcharts.js"
155+
highcharts_url = "https://cdn.jsdelivr.net/npm/highcharts@11/highcharts.js"
148156
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
149157
highcharts_js = response.read().decode("utf-8")
150158

151-
# Generate HTML with inline scripts
159+
# Generate JavaScript literal
152160
html_str = chart.to_js_literal()
153161

154-
# Create JavaScript formatter function for x-axis labels
162+
# Create formatter function for x-axis labels
155163
labels_js = str(ivl).replace("'", '"')
156164
formatter_js = f"""function() {{
157165
var labels = {labels_js};
158166
var idx = Math.round((this.value - 5) / 10);
159167
return (idx >= 0 && idx < labels.length) ? labels[idx] : '';
160168
}}"""
161169

162-
# Inject formatter into the xAxis labels configuration
163-
# Find the rotation property in labels and add formatter after it
164-
html_str = html_str.replace(
165-
"rotation: 45,",
166-
"rotation: 45,\nformatter: " + formatter_js + ",",
167-
1, # Replace only first occurrence
168-
)
170+
# Inject formatter into xAxis labels configuration
171+
html_str = html_str.replace("rotation: 45,", "rotation: 45,\nformatter: " + formatter_js + ",", 1)
169172

170173
html_content = f"""<!DOCTYPE html>
171174
<html>
@@ -179,11 +182,11 @@
179182
</body>
180183
</html>"""
181184

182-
# Save HTML file
185+
# Save HTML
183186
with open("plot.html", "w", encoding="utf-8") as f:
184187
f.write(html_content)
185188

186-
# Write temp HTML and take screenshot
189+
# Screenshot via headless Chrome
187190
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
188191
f.write(html_content)
189192
temp_path = f.name
@@ -199,7 +202,6 @@
199202
driver.get(f"file://{temp_path}")
200203
time.sleep(5)
201204

202-
# Get container element and screenshot it for exact dimensions
203205
container = driver.find_element("id", "container")
204206
container.screenshot("plot.png")
205207
driver.quit()

0 commit comments

Comments
 (0)