Skip to content

Commit 4c41d1d

Browse files
feat(highcharts): implement contour-decision-boundary (#2986)
## Implementation: `contour-decision-boundary` - highcharts Implements the **highcharts** version of `contour-decision-boundary`. **File:** `plots/contour-decision-boundary/implementations/highcharts.py` **Parent Issue:** #2921 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20612962959)* --------- 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 aa4c76d commit 4c41d1d

2 files changed

Lines changed: 233 additions & 0 deletions

File tree

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
""" pyplots.ai
2+
contour-decision-boundary: Decision Boundary Classifier Visualization
3+
Library: highcharts unknown | Python 3.13.11
4+
Quality: 90/100 | Created: 2025-12-31
5+
"""
6+
7+
import json
8+
import tempfile
9+
import time
10+
import urllib.request
11+
from pathlib import Path
12+
13+
import numpy as np
14+
from selenium import webdriver
15+
from selenium.webdriver.chrome.options import Options
16+
from sklearn.datasets import make_moons
17+
from sklearn.neighbors import KNeighborsClassifier
18+
19+
20+
# Data: Generate moon-shaped classification dataset
21+
np.random.seed(42)
22+
X, y = make_moons(n_samples=150, noise=0.25, random_state=42)
23+
24+
# Train a KNN classifier
25+
classifier = KNeighborsClassifier(n_neighbors=15)
26+
classifier.fit(X, y)
27+
28+
# Create mesh grid for decision boundary
29+
x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5
30+
y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5
31+
resolution = 80
32+
33+
xx, yy = np.meshgrid(np.linspace(x_min, x_max, resolution), np.linspace(y_min, y_max, resolution))
34+
35+
# Predict class probabilities on mesh grid
36+
mesh_points = np.c_[xx.ravel(), yy.ravel()]
37+
Z_proba = classifier.predict_proba(mesh_points)[:, 1]
38+
Z = Z_proba.reshape(xx.shape)
39+
40+
# Prepare heatmap data with actual coordinate values [x, y, value]
41+
heatmap_data = []
42+
x_step = (x_max - x_min) / resolution
43+
y_step = (y_max - y_min) / resolution
44+
for i in range(resolution):
45+
for j in range(resolution):
46+
x_val = round(xx[i, j], 4)
47+
y_val = round(yy[i, j], 4)
48+
heatmap_data.append([x_val, y_val, round(Z[i, j], 3)])
49+
50+
# Prepare scatter data for training points
51+
class_0_points = [[round(float(X[i, 0]), 4), round(float(X[i, 1]), 4)] for i in range(len(y)) if y[i] == 0]
52+
class_1_points = [[round(float(X[i, 0]), 4), round(float(X[i, 1]), 4)] for i in range(len(y)) if y[i] == 1]
53+
54+
# Download Highcharts JS and heatmap module
55+
highcharts_url = "https://code.highcharts.com/highcharts.js"
56+
heatmap_url = "https://code.highcharts.com/modules/heatmap.js"
57+
58+
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
59+
highcharts_js = response.read().decode("utf-8")
60+
61+
with urllib.request.urlopen(heatmap_url, timeout=30) as response:
62+
heatmap_js = response.read().decode("utf-8")
63+
64+
# Build complete options as dict
65+
options_dict = {
66+
"chart": {
67+
"width": 4800,
68+
"height": 2700,
69+
"backgroundColor": "#ffffff",
70+
"marginBottom": 320,
71+
"marginLeft": 260,
72+
"marginRight": 440,
73+
"marginTop": 200,
74+
"spacingBottom": 40,
75+
},
76+
"title": {
77+
"text": "contour-decision-boundary · highcharts · pyplots.ai",
78+
"style": {"fontSize": "56px", "fontWeight": "bold"},
79+
},
80+
"subtitle": {"text": "KNN Classifier Decision Boundary on Moon-shaped Data", "style": {"fontSize": "38px"}},
81+
"xAxis": {
82+
"min": x_min,
83+
"max": x_max,
84+
"title": {
85+
"text": "Feature X1",
86+
"style": {"fontSize": "46px", "fontWeight": "bold"},
87+
"margin": 30,
88+
"enabled": True,
89+
},
90+
"labels": {"style": {"fontSize": "34px"}, "format": "{value:.1f}"},
91+
"tickInterval": 0.5,
92+
"gridLineWidth": 1,
93+
"gridLineColor": "#cccccc",
94+
"offset": 0,
95+
},
96+
"yAxis": {
97+
"min": y_min,
98+
"max": y_max,
99+
"title": {
100+
"text": "Feature X2",
101+
"style": {"fontSize": "46px", "fontWeight": "bold"},
102+
"margin": 40,
103+
"enabled": True,
104+
},
105+
"labels": {"style": {"fontSize": "34px"}, "format": "{value:.1f}"},
106+
"tickInterval": 0.5,
107+
"gridLineWidth": 1,
108+
"gridLineColor": "#cccccc",
109+
},
110+
"colorAxis": {
111+
"min": 0,
112+
"max": 1,
113+
"stops": [[0, "#306998"], [0.5, "#E8E8E8"], [1, "#FFD43B"]],
114+
"labels": {"style": {"fontSize": "34px"}, "format": "{value:.1f}"},
115+
},
116+
"legend": {
117+
"enabled": True,
118+
"align": "right",
119+
"verticalAlign": "top",
120+
"layout": "vertical",
121+
"itemStyle": {"fontSize": "38px", "fontWeight": "normal"},
122+
"symbolRadius": 16,
123+
"symbolHeight": 40,
124+
"symbolWidth": 40,
125+
"itemMarginBottom": 40,
126+
"x": -50,
127+
"y": 120,
128+
"width": 380,
129+
"padding": 25,
130+
"backgroundColor": "rgba(255, 255, 255, 0.95)",
131+
"borderWidth": 2,
132+
"borderColor": "#999999",
133+
"title": {"text": "Training Data", "style": {"fontSize": "40px", "fontWeight": "bold"}},
134+
},
135+
"plotOptions": {
136+
"heatmap": {"borderWidth": 0, "colsize": x_step, "rowsize": y_step, "nullColor": "#E8E8E8"},
137+
"scatter": {
138+
"marker": {"radius": 22, "lineWidth": 5, "lineColor": "#222222"},
139+
"states": {"hover": {"enabled": True, "lineWidth": 0}},
140+
},
141+
},
142+
"tooltip": {"enabled": True, "style": {"fontSize": "24px"}},
143+
"series": [
144+
{"type": "heatmap", "name": "Decision Region", "data": heatmap_data, "showInLegend": False},
145+
{
146+
"type": "scatter",
147+
"name": "Class 0 (Moon A)",
148+
"data": class_0_points,
149+
"color": "#306998",
150+
"marker": {"symbol": "circle", "fillColor": "#306998"},
151+
"showInLegend": True,
152+
},
153+
{
154+
"type": "scatter",
155+
"name": "Class 1 (Moon B)",
156+
"data": class_1_points,
157+
"color": "#FFD43B",
158+
"marker": {"symbol": "diamond", "fillColor": "#FFD43B"},
159+
"showInLegend": True,
160+
},
161+
],
162+
}
163+
164+
# Generate JavaScript from options dict
165+
options_js = json.dumps(options_dict)
166+
chart_js = f"Highcharts.chart('container', {options_js});"
167+
168+
# HTML content with inline scripts
169+
html_content = f"""<!DOCTYPE html>
170+
<html>
171+
<head>
172+
<meta charset="utf-8">
173+
<script>{highcharts_js}</script>
174+
<script>{heatmap_js}</script>
175+
</head>
176+
<body style="margin:0; padding:0;">
177+
<div id="container" style="width: 4800px; height: 2700px;"></div>
178+
<script>{chart_js}</script>
179+
</body>
180+
</html>"""
181+
182+
# Save HTML for debugging
183+
with open("plot.html", "w", encoding="utf-8") as f:
184+
f.write(html_content)
185+
186+
# Write temp HTML and take screenshot
187+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
188+
f.write(html_content)
189+
temp_path = f.name
190+
191+
chrome_options = Options()
192+
chrome_options.add_argument("--headless")
193+
chrome_options.add_argument("--no-sandbox")
194+
chrome_options.add_argument("--disable-dev-shm-usage")
195+
chrome_options.add_argument("--disable-gpu")
196+
chrome_options.add_argument("--window-size=4800,2700")
197+
198+
driver = webdriver.Chrome(options=chrome_options)
199+
driver.get(f"file://{temp_path}")
200+
time.sleep(5)
201+
driver.save_screenshot("plot.png")
202+
driver.quit()
203+
204+
Path(temp_path).unlink()
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
library: highcharts
2+
specification_id: contour-decision-boundary
3+
created: '2025-12-31T05:51:30Z'
4+
updated: '2025-12-31T15:12:41Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20612962959
7+
issue: 2921
8+
python_version: 3.13.11
9+
library_version: unknown
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/contour-decision-boundary/highcharts/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/contour-decision-boundary/highcharts/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/contour-decision-boundary/highcharts/plot.html
13+
quality_score: 90
14+
review:
15+
strengths:
16+
- Excellent decision boundary visualization with clear separation between blue and
17+
yellow regions
18+
- Good use of different marker shapes (circles for Class 0, diamonds for Class 1)
19+
to distinguish classes
20+
- Proper colorblind-safe palette using blue (#306998) and yellow (#FFD43B)
21+
- 'Title follows correct format: contour-decision-boundary · highcharts · pyplots.ai'
22+
- Clean heatmap implementation showing probability gradients through the decision
23+
space
24+
weaknesses:
25+
- X-axis label (Feature X1) is missing - only Y-axis label (Feature X2) is visible
26+
- Color axis legend on right side shows truncated values (1.0 and 0.0 partially
27+
visible)
28+
- Legend box with Training Data title and class names is not visible in the rendered
29+
image

0 commit comments

Comments
 (0)