Skip to content

Commit ecacd9a

Browse files
feat(highcharts): implement elbow-curve (#2357)
## Implementation: `elbow-curve` - highcharts Implements the **highcharts** version of `elbow-curve`. **File:** `plots/elbow-curve/implementations/highcharts.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20528205442)* --------- 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 dc16b71 commit ecacd9a

2 files changed

Lines changed: 221 additions & 0 deletions

File tree

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
""" pyplots.ai
2+
elbow-curve: Elbow Curve for K-Means Clustering
3+
Library: highcharts unknown | Python 3.13.11
4+
Quality: 92/100 | Created: 2025-12-26
5+
"""
6+
7+
import tempfile
8+
import time
9+
import urllib.request
10+
from pathlib import Path
11+
12+
import numpy as np
13+
from highcharts_core.chart import Chart
14+
from highcharts_core.options import HighchartsOptions
15+
from highcharts_core.options.series.area import LineSeries
16+
from highcharts_core.options.series.scatter import ScatterSeries
17+
from selenium import webdriver
18+
from selenium.webdriver.chrome.options import Options
19+
20+
21+
# Data - simulate elbow curve from K-means clustering
22+
np.random.seed(42)
23+
k_values = list(range(1, 13))
24+
25+
# Simulate realistic inertia values (decreasing with diminishing returns)
26+
# Using exponential decay with noise for realism
27+
base_inertia = 15000
28+
inertia = []
29+
for k in k_values:
30+
# Exponential decay with elbow around k=4
31+
decay = base_inertia * np.exp(-0.4 * (k - 1))
32+
noise = np.random.uniform(-200, 200)
33+
inertia.append(max(500, decay + noise))
34+
35+
# Optimal elbow point at k=4
36+
optimal_k = 4
37+
optimal_inertia = inertia[optimal_k - 1]
38+
39+
# Create chart
40+
chart = Chart(container="container")
41+
chart.options = HighchartsOptions()
42+
43+
# Chart configuration
44+
chart.options.chart = {
45+
"type": "line",
46+
"width": 4800,
47+
"height": 2700,
48+
"backgroundColor": "#ffffff",
49+
"marginBottom": 250,
50+
"marginLeft": 250,
51+
"marginTop": 120,
52+
"marginRight": 100,
53+
}
54+
55+
# Title
56+
chart.options.title = {
57+
"text": "elbow-curve \u00b7 highcharts \u00b7 pyplots.ai",
58+
"style": {"fontSize": "48px", "fontWeight": "bold"},
59+
}
60+
61+
# X-axis configuration
62+
chart.options.x_axis = {
63+
"title": {"text": "Number of Clusters (k)", "style": {"fontSize": "42px"}, "margin": 30},
64+
"labels": {"style": {"fontSize": "32px"}, "y": 40},
65+
"categories": [str(k) for k in k_values],
66+
"gridLineWidth": 1,
67+
"gridLineColor": "#e0e0e0",
68+
"gridLineDashStyle": "Dash",
69+
"tickInterval": 1,
70+
}
71+
72+
# Y-axis configuration
73+
chart.options.y_axis = {
74+
"title": {"text": "Inertia (Within-cluster Sum of Squares)", "style": {"fontSize": "42px"}, "margin": 40},
75+
"labels": {"style": {"fontSize": "32px"}, "x": -15},
76+
"gridLineWidth": 1,
77+
"gridLineColor": "#e0e0e0",
78+
"gridLineDashStyle": "Dash",
79+
"min": 0,
80+
}
81+
82+
# Legend
83+
chart.options.legend = {
84+
"enabled": True,
85+
"itemStyle": {"fontSize": "32px"},
86+
"align": "right",
87+
"verticalAlign": "top",
88+
"layout": "vertical",
89+
"x": -80,
90+
"y": 80,
91+
}
92+
93+
# Plot options
94+
chart.options.plot_options = {
95+
"line": {"lineWidth": 5, "marker": {"enabled": True, "radius": 14, "symbol": "circle"}},
96+
"scatter": {"marker": {"radius": 20, "symbol": "diamond"}},
97+
}
98+
99+
# Main elbow curve line series
100+
line_series = LineSeries()
101+
line_series.name = "Inertia"
102+
line_series.data = [[i, round(v, 1)] for i, v in enumerate(inertia)]
103+
line_series.color = "#306998"
104+
line_series.marker = {"fillColor": "#306998", "lineWidth": 3, "lineColor": "#ffffff"}
105+
chart.add_series(line_series)
106+
107+
# Optimal point marker
108+
optimal_series = ScatterSeries()
109+
optimal_series.name = f"Optimal k = {optimal_k}"
110+
optimal_series.data = [[optimal_k - 1, round(optimal_inertia, 1)]]
111+
optimal_series.color = "#FFD43B"
112+
optimal_series.marker = {
113+
"radius": 24,
114+
"symbol": "diamond",
115+
"fillColor": "#FFD43B",
116+
"lineWidth": 4,
117+
"lineColor": "#306998",
118+
}
119+
chart.add_series(optimal_series)
120+
121+
# Add annotation for elbow point
122+
chart.options.annotations = [
123+
{
124+
"labels": [
125+
{
126+
"point": {"x": optimal_k - 1, "y": optimal_inertia, "xAxis": 0, "yAxis": 0},
127+
"text": f"Elbow Point (k={optimal_k})",
128+
"style": {"fontSize": "32px", "fontWeight": "bold"},
129+
"backgroundColor": "rgba(255, 212, 59, 0.9)",
130+
"borderColor": "#306998",
131+
"borderWidth": 3,
132+
"borderRadius": 8,
133+
"padding": 15,
134+
"y": -60,
135+
}
136+
]
137+
}
138+
]
139+
140+
# Tooltip
141+
chart.options.tooltip = {
142+
"style": {"fontSize": "24px"},
143+
"headerFormat": "<b>k = {point.key}</b><br/>",
144+
"pointFormat": "Inertia: {point.y:.1f}",
145+
}
146+
147+
# Download Highcharts JS (required for headless Chrome)
148+
highcharts_url = "https://code.highcharts.com/highcharts.js"
149+
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
150+
highcharts_js = response.read().decode("utf-8")
151+
152+
# Download annotations module for elbow point label
153+
annotations_url = "https://code.highcharts.com/modules/annotations.js"
154+
with urllib.request.urlopen(annotations_url, timeout=30) as response:
155+
annotations_js = response.read().decode("utf-8")
156+
157+
# Generate HTML with inline scripts
158+
html_str = chart.to_js_literal()
159+
html_content = f"""<!DOCTYPE html>
160+
<html>
161+
<head>
162+
<meta charset="utf-8">
163+
<script>{highcharts_js}</script>
164+
<script>{annotations_js}</script>
165+
</head>
166+
<body style="margin:0;">
167+
<div id="container" style="width: 4800px; height: 2700px;"></div>
168+
<script>{html_str}</script>
169+
</body>
170+
</html>"""
171+
172+
# Write temp HTML and take screenshot
173+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
174+
f.write(html_content)
175+
temp_path = f.name
176+
177+
chrome_options = Options()
178+
chrome_options.add_argument("--headless")
179+
chrome_options.add_argument("--no-sandbox")
180+
chrome_options.add_argument("--disable-dev-shm-usage")
181+
chrome_options.add_argument("--disable-gpu")
182+
chrome_options.add_argument("--window-size=4800,2800")
183+
184+
driver = webdriver.Chrome(options=chrome_options)
185+
driver.get(f"file://{temp_path}")
186+
time.sleep(5)
187+
driver.save_screenshot("plot.png")
188+
driver.quit()
189+
190+
# Also save HTML for interactive version
191+
with open("plot.html", "w", encoding="utf-8") as f:
192+
f.write(html_content)
193+
194+
Path(temp_path).unlink()
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
library: highcharts
2+
specification_id: elbow-curve
3+
created: '2025-12-26T19:37:11Z'
4+
updated: '2025-12-26T19:43:59Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20528205442
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: unknown
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/elbow-curve/highcharts/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/elbow-curve/highcharts/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/elbow-curve/highcharts/plot.html
13+
quality_score: 92
14+
review:
15+
strengths:
16+
- Excellent visual clarity with appropriate font sizes and marker visibility
17+
- Proper implementation of elbow curve with clear elbow point annotation using Highcharts
18+
annotations module
19+
- Colorblind-safe color scheme with good contrast
20+
- Well-structured code following KISS principles with reproducible random seed
21+
- Correctly uses both LineSeries and ScatterSeries for different purposes
22+
- Title follows the required format exactly
23+
weaknesses:
24+
- Legend positioned in upper right corner could be better integrated with the chart
25+
area
26+
- Could leverage more Highcharts-specific interactive features for the interactive
27+
HTML output

0 commit comments

Comments
 (0)