Skip to content

Commit b412f22

Browse files
feat(highcharts): implement coefficient-confidence (#3601)
## Implementation: `coefficient-confidence` - highcharts Implements the **highcharts** version of `coefficient-confidence`. **File:** `plots/coefficient-confidence/implementations/highcharts.py` **Parent Issue:** #3576 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20868895448)* --------- 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 302f949 commit b412f22

File tree

2 files changed

+480
-0
lines changed

2 files changed

+480
-0
lines changed
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
""" pyplots.ai
2+
coefficient-confidence: Coefficient Plot with Confidence Intervals
3+
Library: highcharts unknown | Python 3.13.11
4+
Quality: 92/100 | Created: 2026-01-09
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+
17+
18+
# Data: Coefficients from a regression model predicting housing prices
19+
np.random.seed(42)
20+
21+
variables = [
22+
"Square Footage",
23+
"Number of Bedrooms",
24+
"Number of Bathrooms",
25+
"Garage Spaces",
26+
"Lot Size (acres)",
27+
"Year Built",
28+
"Distance to City Center",
29+
"School Rating",
30+
"Crime Rate Index",
31+
"Property Tax Rate",
32+
]
33+
34+
# Generate realistic coefficients with varying significance
35+
coefficients = np.array([0.45, 0.12, 0.28, 0.15, 0.08, 0.02, -0.22, 0.35, -0.18, -0.05])
36+
std_errors = np.array([0.08, 0.09, 0.07, 0.06, 0.10, 0.04, 0.05, 0.06, 0.07, 0.08])
37+
38+
# 95% confidence intervals
39+
ci_lower = coefficients - 1.96 * std_errors
40+
ci_upper = coefficients + 1.96 * std_errors
41+
42+
# Determine significance (CI does not cross zero)
43+
significant = (ci_lower > 0) | (ci_upper < 0)
44+
45+
# Sort by coefficient magnitude (highest at top, so reverse for display)
46+
sort_idx = np.argsort(coefficients)[::-1]
47+
variables = [variables[i] for i in sort_idx]
48+
coefficients = coefficients[sort_idx]
49+
ci_lower = ci_lower[sort_idx]
50+
ci_upper = ci_upper[sort_idx]
51+
significant = significant[sort_idx]
52+
53+
# Download Highcharts JS
54+
highcharts_url = "https://code.highcharts.com/highcharts.js"
55+
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
56+
highcharts_js = response.read().decode("utf-8")
57+
58+
highcharts_more_url = "https://code.highcharts.com/highcharts-more.js"
59+
with urllib.request.urlopen(highcharts_more_url, timeout=30) as response:
60+
highcharts_more_js = response.read().decode("utf-8")
61+
62+
xrange_url = "https://code.highcharts.com/modules/xrange.js"
63+
with urllib.request.urlopen(xrange_url, timeout=30) as response:
64+
xrange_js = response.read().decode("utf-8")
65+
66+
# Build data arrays for JavaScript
67+
sig_points = []
68+
nonsig_points = []
69+
70+
for i, (var, coef, sig) in enumerate(zip(variables, coefficients, significant, strict=True)):
71+
point = {"x": float(coef), "y": i, "name": var}
72+
if sig:
73+
sig_points.append(point)
74+
else:
75+
nonsig_points.append(point)
76+
77+
# Convert to JS format using json for proper escaping
78+
categories_js = json.dumps(variables)
79+
sig_points_js = json.dumps(sig_points)
80+
nonsig_points_js = json.dumps(nonsig_points)
81+
82+
# Build CI data for xrange series
83+
ci_data = []
84+
for i, (lower, upper, sig) in enumerate(zip(ci_lower, ci_upper, significant, strict=True)):
85+
ci_data.append({"x": float(lower), "x2": float(upper), "y": i, "color": "#306998" if sig else "#B8860B"})
86+
87+
ci_data_js = json.dumps(ci_data)
88+
89+
# Calculate x-axis range to ensure all points are visible
90+
x_min = float(min(ci_lower)) - 0.05
91+
x_max = float(max(ci_upper)) + 0.05
92+
93+
chart_js = f"""
94+
Highcharts.chart('container', {{
95+
chart: {{
96+
width: 4800,
97+
height: 2700,
98+
backgroundColor: '#ffffff',
99+
marginLeft: 420,
100+
marginRight: 180,
101+
marginTop: 180,
102+
marginBottom: 280,
103+
spacingBottom: 40
104+
}},
105+
title: {{
106+
text: 'coefficient-confidence · highcharts · pyplots.ai',
107+
style: {{ fontSize: '56px', fontWeight: 'bold' }}
108+
}},
109+
subtitle: {{
110+
text: 'Housing Price Regression Model Coefficients with 95% Confidence Intervals',
111+
style: {{ fontSize: '36px', color: '#555555' }}
112+
}},
113+
xAxis: {{
114+
min: {x_min},
115+
max: {x_max},
116+
title: {{
117+
text: 'Coefficient Estimate (Standardized)',
118+
style: {{ fontSize: '40px' }},
119+
margin: 25
120+
}},
121+
labels: {{
122+
style: {{ fontSize: '32px' }},
123+
format: '{{value:.1f}}'
124+
}},
125+
gridLineWidth: 1,
126+
gridLineColor: '#e0e0e0',
127+
tickInterval: 0.1,
128+
plotLines: [{{
129+
value: 0,
130+
color: '#666666',
131+
width: 4,
132+
dashStyle: 'Dash',
133+
zIndex: 3,
134+
label: {{
135+
text: 'Null (β = 0)',
136+
style: {{ fontSize: '28px', color: '#666666', fontWeight: 'bold' }},
137+
rotation: 0,
138+
y: 30,
139+
x: 10
140+
}}
141+
}}]
142+
}},
143+
yAxis: {{
144+
title: {{ text: null }},
145+
categories: {categories_js},
146+
labels: {{
147+
style: {{ fontSize: '32px' }}
148+
}},
149+
gridLineWidth: 0,
150+
reversed: false
151+
}},
152+
legend: {{
153+
enabled: true,
154+
itemStyle: {{ fontSize: '36px' }},
155+
verticalAlign: 'bottom',
156+
align: 'center',
157+
layout: 'horizontal',
158+
floating: false,
159+
y: -20,
160+
symbolRadius: 14,
161+
symbolHeight: 28,
162+
symbolWidth: 28,
163+
itemDistance: 100
164+
}},
165+
tooltip: {{
166+
style: {{ fontSize: '28px' }},
167+
useHTML: true,
168+
formatter: function() {{
169+
if (this.series.type === 'xrange') {{
170+
return '<b>' + this.yCategory + '</b><br/>CI: [' + this.point.x.toFixed(3) + ', ' + this.point.x2.toFixed(3) + ']';
171+
}}
172+
return '<b>' + this.point.name + '</b><br/>Coefficient: ' + this.x.toFixed(3);
173+
}}
174+
}},
175+
plotOptions: {{
176+
scatter: {{
177+
marker: {{
178+
radius: 22,
179+
symbol: 'circle'
180+
}},
181+
zIndex: 10
182+
}},
183+
xrange: {{
184+
borderRadius: 0,
185+
pointWidth: 8,
186+
zIndex: 5
187+
}}
188+
}},
189+
series: [{{
190+
type: 'xrange',
191+
name: '95% CI',
192+
data: {ci_data_js},
193+
showInLegend: false,
194+
enableMouseTracking: true
195+
}}, {{
196+
type: 'scatter',
197+
name: 'Significant (p < 0.05)',
198+
color: '#306998',
199+
data: {sig_points_js},
200+
marker: {{
201+
fillColor: '#306998',
202+
lineWidth: 0,
203+
radius: 22
204+
}},
205+
zIndex: 10,
206+
clip: false
207+
}}, {{
208+
type: 'scatter',
209+
name: 'Not Significant',
210+
color: '#FFD43B',
211+
data: {nonsig_points_js},
212+
marker: {{
213+
fillColor: '#FFD43B',
214+
lineWidth: 4,
215+
lineColor: '#B8860B',
216+
radius: 22
217+
}},
218+
zIndex: 10,
219+
clip: false
220+
}}]
221+
}});
222+
"""
223+
224+
# HTML content with inline scripts
225+
html_content = f"""<!DOCTYPE html>
226+
<html>
227+
<head>
228+
<meta charset="utf-8">
229+
<script>{highcharts_js}</script>
230+
<script>{highcharts_more_js}</script>
231+
<script>{xrange_js}</script>
232+
</head>
233+
<body style="margin:0; padding:0; background:#ffffff;">
234+
<div id="container" style="width: 4800px; height: 2700px;"></div>
235+
<script>{chart_js}</script>
236+
</body>
237+
</html>"""
238+
239+
# Write temp HTML and take screenshot
240+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
241+
f.write(html_content)
242+
temp_path = f.name
243+
244+
# Save HTML for interactive version
245+
with open("plot.html", "w", encoding="utf-8") as f:
246+
f.write(html_content)
247+
248+
# Chrome options for headless screenshot
249+
chrome_options = Options()
250+
chrome_options.add_argument("--headless")
251+
chrome_options.add_argument("--no-sandbox")
252+
chrome_options.add_argument("--disable-dev-shm-usage")
253+
chrome_options.add_argument("--disable-gpu")
254+
chrome_options.add_argument("--window-size=4800,2800") # Slightly larger to capture legend
255+
256+
driver = webdriver.Chrome(options=chrome_options)
257+
driver.get(f"file://{temp_path}")
258+
time.sleep(5) # Wait for chart to render
259+
260+
# Get the container element and take a screenshot of just that element
261+
container = driver.find_element("id", "container")
262+
container.screenshot("plot.png")
263+
264+
driver.quit()
265+
266+
Path(temp_path).unlink() # Clean up temp file

0 commit comments

Comments
 (0)