Skip to content

Commit e9d5b72

Browse files
feat(highcharts): implement confusion-matrix (#2288)
## Implementation: `confusion-matrix` - highcharts Implements the **highcharts** version of `confusion-matrix`. **File:** `plots/confusion-matrix/implementations/highcharts.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20526594255)* --------- 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 963bd2e commit e9d5b72

2 files changed

Lines changed: 224 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+
confusion-matrix: Confusion Matrix Heatmap
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.heatmap import HeatmapSeries
16+
from selenium import webdriver
17+
from selenium.webdriver.chrome.options import Options
18+
19+
20+
# Data - Image Classification Model Results (4-class problem)
21+
np.random.seed(42)
22+
class_names = ["Cat", "Dog", "Bird", "Fish"]
23+
n_classes = len(class_names)
24+
25+
# Realistic confusion matrix with varying accuracy per class
26+
# Diagonal dominates (correct predictions), with realistic misclassification patterns
27+
confusion_data = np.array(
28+
[
29+
[87, 8, 3, 2], # Cat: often confused with Dog
30+
[12, 76, 7, 5], # Dog: often confused with Cat
31+
[4, 6, 82, 8], # Bird: sometimes confused with Fish
32+
[2, 4, 9, 85], # Fish: sometimes confused with Bird
33+
]
34+
)
35+
36+
# Prepare data for heatmap format: [x_index, y_index, value]
37+
heatmap_data = []
38+
for i in range(n_classes):
39+
for j in range(n_classes):
40+
heatmap_data.append([j, n_classes - 1 - i, int(confusion_data[i, j])])
41+
42+
# Reversed class names for y-axis (to match matrix orientation)
43+
reversed_classes = list(reversed(class_names))
44+
45+
# Create chart
46+
chart = Chart(container="container")
47+
chart.options = HighchartsOptions()
48+
49+
# Chart configuration
50+
chart.options.chart = {
51+
"type": "heatmap",
52+
"width": 3600,
53+
"height": 3600,
54+
"backgroundColor": "#ffffff",
55+
"marginTop": 180,
56+
"marginBottom": 280,
57+
"marginLeft": 280,
58+
"marginRight": 220,
59+
}
60+
61+
# Title
62+
chart.options.title = {
63+
"text": "confusion-matrix · highcharts · pyplots.ai",
64+
"style": {"fontSize": "48px", "fontWeight": "bold"},
65+
}
66+
67+
# Subtitle
68+
chart.options.subtitle = {
69+
"text": "Image Classification Model Performance",
70+
"style": {"fontSize": "32px", "color": "#666666"},
71+
}
72+
73+
# X-axis - Predicted Labels
74+
chart.options.x_axis = {
75+
"categories": class_names,
76+
"title": {"text": "Predicted Label", "style": {"fontSize": "36px", "fontWeight": "bold"}},
77+
"labels": {"style": {"fontSize": "30px"}},
78+
}
79+
80+
# Y-axis - True Labels
81+
chart.options.y_axis = {
82+
"categories": reversed_classes,
83+
"title": {"text": "True Label", "style": {"fontSize": "36px", "fontWeight": "bold"}},
84+
"labels": {"style": {"fontSize": "30px"}},
85+
"reversed": False,
86+
}
87+
88+
# Color axis - Sequential Blues colormap (colorblind-friendly)
89+
chart.options.color_axis = {
90+
"min": 0,
91+
"max": 100,
92+
"stops": [
93+
[0, "#f7fbff"], # Lightest blue
94+
[0.2, "#c6dbef"],
95+
[0.4, "#6baed6"],
96+
[0.6, "#2171b5"],
97+
[0.8, "#08519c"],
98+
[1.0, "#08306b"], # Darkest blue
99+
],
100+
"labels": {"style": {"fontSize": "24px"}, "format": "{value}"},
101+
"tickInterval": 20,
102+
}
103+
104+
# Legend (colorbar)
105+
chart.options.legend = {
106+
"align": "right",
107+
"layout": "vertical",
108+
"verticalAlign": "middle",
109+
"symbolHeight": 600,
110+
"itemStyle": {"fontSize": "24px"},
111+
"title": {"text": "Count", "style": {"fontSize": "28px"}},
112+
}
113+
114+
# Tooltip
115+
chart.options.tooltip = {
116+
"formatter": """function() {
117+
var predicted = this.series.xAxis.categories[this.point.x];
118+
var actual = this.series.yAxis.categories[this.point.y];
119+
var isCorrect = this.point.x === (3 - this.point.y);
120+
var label = isCorrect ? 'Correct' : 'Misclassified';
121+
return '<b>True: ' + actual + '</b><br>' +
122+
'<b>Predicted: ' + predicted + '</b><br>' +
123+
'Count: <b>' + this.point.value + '</b><br>' +
124+
'(' + label + ')';
125+
}""",
126+
"style": {"fontSize": "20px"},
127+
}
128+
129+
# Disable credits
130+
chart.options.credits = {"enabled": False}
131+
132+
# Create and add series
133+
series = HeatmapSeries()
134+
series.name = "Classification Results"
135+
series.data = heatmap_data
136+
series.border_width = 2
137+
series.border_color = "#ffffff"
138+
series.data_labels = {
139+
"enabled": True,
140+
"formatter": """function() {
141+
return this.point.value;
142+
}""",
143+
"style": {"fontSize": "40px", "fontWeight": "bold", "textOutline": "2px white", "color": "#000000"},
144+
}
145+
146+
chart.add_series(series)
147+
148+
# Download Highcharts JS and Heatmap module
149+
highcharts_url = "https://code.highcharts.com/highcharts.js"
150+
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
151+
highcharts_js = response.read().decode("utf-8")
152+
153+
heatmap_url = "https://code.highcharts.com/modules/heatmap.js"
154+
with urllib.request.urlopen(heatmap_url, timeout=30) as response:
155+
heatmap_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>{heatmap_js}</script>
165+
</head>
166+
<body style="margin:0; background-color: #ffffff;">
167+
<div id="container" style="width: 3600px; height: 3600px;"></div>
168+
<script>{html_str}</script>
169+
</body>
170+
</html>"""
171+
172+
# Write temp HTML file for 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+
# Also save the HTML file for interactive viewing
178+
with open("plot.html", "w", encoding="utf-8") as f:
179+
f.write(html_content)
180+
181+
# Configure headless Chrome
182+
chrome_options = Options()
183+
chrome_options.add_argument("--headless")
184+
chrome_options.add_argument("--no-sandbox")
185+
chrome_options.add_argument("--disable-dev-shm-usage")
186+
chrome_options.add_argument("--disable-gpu")
187+
chrome_options.add_argument("--window-size=3600,3600")
188+
189+
# Take screenshot
190+
driver = webdriver.Chrome(options=chrome_options)
191+
driver.get(f"file://{temp_path}")
192+
time.sleep(5) # Wait for chart to render
193+
driver.save_screenshot("plot.png")
194+
driver.quit()
195+
196+
# Clean up temp file
197+
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: confusion-matrix
3+
created: '2025-12-26T17:37:21Z'
4+
updated: '2025-12-26T17:45:03Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20526594255
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: unknown
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/confusion-matrix/highcharts/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/confusion-matrix/highcharts/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/confusion-matrix/highcharts/plot.html
13+
quality_score: 92
14+
review:
15+
strengths:
16+
- Excellent visual clarity with well-sized text and cell annotations
17+
- Sequential blue colormap is colorblind-accessible and effectively shows magnitude
18+
- Proper confusion matrix orientation with True Labels on y-axis and Predicted Labels
19+
on x-axis
20+
- 'Good use of Highcharts-specific features: custom tooltip showing correct/misclassified
21+
status, color axis with gradient stops'
22+
- White cell borders provide clean visual separation
23+
- Realistic 4-class classification scenario with believable misclassification patterns
24+
weaknesses:
25+
- Layout could better utilize the square canvas - some wasted space around edges
26+
- np.random.seed(42) is set but data is entirely hardcoded, making the seed unnecessary
27+
- Colorbar scale goes to 100 but max value is 87, slightly misleading range

0 commit comments

Comments
 (0)