Skip to content

Commit 88f0518

Browse files
feat(highcharts): implement roc-curve (#2320)
## Implementation: `roc-curve` - highcharts Implements the **highcharts** version of `roc-curve`. **File:** `plots/roc-curve/implementations/highcharts.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20526595882)* --------- 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 1dd3c9f commit 88f0518

2 files changed

Lines changed: 225 additions & 0 deletions

File tree

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
""" pyplots.ai
2+
roc-curve: ROC Curve with AUC
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 AreaSeries
16+
from highcharts_core.options.series.spline import SplineSeries
17+
from selenium import webdriver
18+
from selenium.webdriver.chrome.options import Options
19+
20+
21+
# Data - simulate ROC curve from a binary classifier
22+
np.random.seed(42)
23+
24+
# Generate synthetic prediction scores and true labels
25+
n_samples = 500
26+
y_true = np.concatenate([np.zeros(250), np.ones(250)])
27+
# Good classifier: positive class has higher scores
28+
y_scores = np.concatenate(
29+
[
30+
np.random.beta(2, 5, 250), # Negative class - lower scores
31+
np.random.beta(5, 2, 250), # Positive class - higher scores
32+
]
33+
)
34+
35+
# Compute ROC curve manually
36+
thresholds = np.linspace(0, 1, 200)
37+
fpr_list = []
38+
tpr_list = []
39+
for thresh in thresholds:
40+
predictions = (y_scores >= thresh).astype(int)
41+
tp = np.sum((predictions == 1) & (y_true == 1))
42+
fp = np.sum((predictions == 1) & (y_true == 0))
43+
tn = np.sum((predictions == 0) & (y_true == 0))
44+
fn = np.sum((predictions == 0) & (y_true == 1))
45+
tpr = tp / (tp + fn) if (tp + fn) > 0 else 0
46+
fpr = fp / (fp + tn) if (fp + tn) > 0 else 0
47+
fpr_list.append(fpr)
48+
tpr_list.append(tpr)
49+
50+
fpr = np.array(fpr_list)
51+
tpr = np.array(tpr_list)
52+
53+
# Sort by FPR for proper curve plotting
54+
sorted_indices = np.argsort(fpr)
55+
fpr = fpr[sorted_indices]
56+
tpr = tpr[sorted_indices]
57+
58+
# Calculate AUC using trapezoidal rule
59+
auc = np.trapezoid(tpr, fpr)
60+
61+
# Create chart with container
62+
chart = Chart(container="container")
63+
chart.options = HighchartsOptions()
64+
65+
# Chart configuration for 4800x2700 canvas
66+
chart.options.chart = {
67+
"type": "area",
68+
"width": 4800,
69+
"height": 2700,
70+
"backgroundColor": "#ffffff",
71+
"marginBottom": 250,
72+
"marginLeft": 250,
73+
"marginTop": 200,
74+
"marginRight": 150,
75+
}
76+
77+
# Title
78+
chart.options.title = {
79+
"text": "roc-curve · highcharts · pyplots.ai",
80+
"style": {"fontSize": "48px", "fontWeight": "bold"},
81+
}
82+
83+
# Subtitle with AUC
84+
chart.options.subtitle = {"text": "Binary Classifier Performance", "style": {"fontSize": "32px"}}
85+
86+
# X-axis configuration
87+
chart.options.x_axis = {
88+
"title": {"text": "False Positive Rate", "style": {"fontSize": "36px"}},
89+
"labels": {"style": {"fontSize": "28px"}},
90+
"min": 0,
91+
"max": 1,
92+
"tickInterval": 0.2,
93+
"gridLineWidth": 1,
94+
"gridLineColor": "rgba(0, 0, 0, 0.1)",
95+
}
96+
97+
# Y-axis configuration
98+
chart.options.y_axis = {
99+
"title": {"text": "True Positive Rate", "style": {"fontSize": "36px"}},
100+
"labels": {"style": {"fontSize": "28px"}},
101+
"min": 0,
102+
"max": 1,
103+
"tickInterval": 0.2,
104+
"gridLineWidth": 1,
105+
"gridLineColor": "rgba(0, 0, 0, 0.1)",
106+
}
107+
108+
# Legend configuration - position in bottom-right of plot area
109+
chart.options.legend = {
110+
"enabled": True,
111+
"align": "right",
112+
"verticalAlign": "bottom",
113+
"layout": "vertical",
114+
"floating": True,
115+
"x": -150,
116+
"y": -150,
117+
"backgroundColor": "rgba(255, 255, 255, 0.95)",
118+
"borderWidth": 2,
119+
"borderColor": "#cccccc",
120+
"borderRadius": 5,
121+
"padding": 20,
122+
"itemStyle": {"fontSize": "28px"},
123+
"symbolRadius": 0,
124+
"symbolWidth": 50,
125+
"symbolHeight": 5,
126+
}
127+
128+
# Plot options
129+
chart.options.plot_options = {
130+
"area": {"fillOpacity": 0.3, "lineWidth": 6, "marker": {"enabled": False}},
131+
"spline": {"lineWidth": 5, "dashStyle": "Dash", "marker": {"enabled": False}},
132+
}
133+
134+
# ROC Curve series (area under curve)
135+
roc_data = [[float(x), float(y)] for x, y in zip(fpr, tpr, strict=True)]
136+
roc_series = AreaSeries()
137+
roc_series.data = roc_data
138+
roc_series.name = f"ROC Curve (AUC = {auc:.3f})"
139+
roc_series.color = "#306998" # Python Blue
140+
roc_series.fill_color = {
141+
"linearGradient": {"x1": 0, "y1": 0, "x2": 0, "y2": 1},
142+
"stops": [[0, "rgba(48, 105, 152, 0.5)"], [1, "rgba(48, 105, 152, 0.1)"]],
143+
}
144+
chart.add_series(roc_series)
145+
146+
# Random classifier reference line (diagonal)
147+
diagonal_data = [[0, 0], [1, 1]]
148+
diagonal_series = SplineSeries()
149+
diagonal_series.data = diagonal_data
150+
diagonal_series.name = "Random Classifier (AUC = 0.5)"
151+
diagonal_series.color = "#8B8000" # Dark yellow/olive for visibility on white
152+
diagonal_series.dash_style = "ShortDash"
153+
diagonal_series.line_width = 5
154+
chart.add_series(diagonal_series)
155+
156+
# Download Highcharts JS for inline embedding
157+
highcharts_url = "https://code.highcharts.com/highcharts.js"
158+
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
159+
highcharts_js = response.read().decode("utf-8")
160+
161+
# Generate HTML with inline scripts
162+
html_str = chart.to_js_literal()
163+
html_content = f"""<!DOCTYPE html>
164+
<html>
165+
<head>
166+
<meta charset="utf-8">
167+
<script>{highcharts_js}</script>
168+
</head>
169+
<body style="margin:0;">
170+
<div id="container" style="width: 4800px; height: 2700px;"></div>
171+
<script>{html_str}</script>
172+
</body>
173+
</html>"""
174+
175+
# Save HTML file for interactive viewing
176+
with open("plot.html", "w", encoding="utf-8") as f:
177+
f.write(html_content)
178+
179+
# Take screenshot with headless Chrome
180+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
181+
f.write(html_content)
182+
temp_path = f.name
183+
184+
chrome_options = Options()
185+
chrome_options.add_argument("--headless")
186+
chrome_options.add_argument("--no-sandbox")
187+
chrome_options.add_argument("--disable-dev-shm-usage")
188+
chrome_options.add_argument("--disable-gpu")
189+
chrome_options.add_argument("--window-size=4800,2700")
190+
191+
driver = webdriver.Chrome(options=chrome_options)
192+
driver.get(f"file://{temp_path}")
193+
time.sleep(5)
194+
driver.save_screenshot("plot.png")
195+
driver.quit()
196+
197+
Path(temp_path).unlink()
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
library: highcharts
2+
specification_id: roc-curve
3+
created: '2025-12-26T17:41:05Z'
4+
updated: '2025-12-26T17:52:23Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20526595882
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: unknown
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/roc-curve/highcharts/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/roc-curve/highcharts/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/roc-curve/highcharts/plot.html
13+
quality_score: 92
14+
review:
15+
strengths:
16+
- Excellent use of AreaSeries with gradient fill to visually represent the AUC area
17+
under the ROC curve
18+
- Clean implementation following the library rules for Highcharts with inline JS
19+
embedding
20+
- Proper manual ROC curve calculation demonstrating the algorithm
21+
- Good colorblind-safe color choices (Python Blue and olive yellow)
22+
- Correctly formatted title following pyplots.ai convention
23+
weaknesses:
24+
- Legend position causes slight overlap with the x-axis 1 tick label
25+
- Could leverage Highcharts tooltip feature to show threshold values at each point
26+
on the curve
27+
- Example data shows only one high-performing classifier; could include comparison
28+
with a second model to better demonstrate ROC curve usage

0 commit comments

Comments
 (0)