Skip to content

Commit 3917615

Browse files
feat(highcharts): implement line-interactive (#2837)
## Implementation: `line-interactive` - highcharts Implements the **highcharts** version of `line-interactive`. **File:** `plots/line-interactive/implementations/highcharts.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20603305896)* --------- 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 7b44804 commit 3917615

2 files changed

Lines changed: 194 additions & 0 deletions

File tree

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
""" pyplots.ai
2+
line-interactive: Interactive Line Chart with Hover and Zoom
3+
Library: highcharts unknown | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-30
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+
import pandas as pd
15+
from selenium import webdriver
16+
from selenium.webdriver.chrome.options import Options
17+
18+
19+
# Data - Server CPU usage over 7 days (hourly readings)
20+
np.random.seed(42)
21+
n_points = 168 # 7 days * 24 hours
22+
23+
dates = pd.date_range("2024-01-01", periods=n_points, freq="h")
24+
25+
# Simulate realistic CPU usage pattern with daily cycles and some anomalies
26+
base = 35 # base CPU usage
27+
daily_pattern = 20 * np.sin(np.linspace(0, 7 * 2 * np.pi, n_points)) # daily cycle
28+
weekly_trend = np.linspace(0, 10, n_points) # slight upward trend
29+
noise = np.random.normal(0, 5, n_points)
30+
31+
# Add some random spikes (anomalies)
32+
spikes = np.zeros(n_points)
33+
spike_indices = [45, 92, 120, 155]
34+
for idx in spike_indices:
35+
spikes[idx] = np.random.uniform(20, 35)
36+
37+
cpu_usage = base + daily_pattern + weekly_trend + noise + spikes
38+
cpu_usage = np.clip(cpu_usage, 5, 100) # Keep within 5-100%
39+
40+
# Convert to timestamp milliseconds for Highcharts datetime axis
41+
timestamps = [int(d.timestamp() * 1000) for d in dates]
42+
data_points = [[ts, round(val, 1)] for ts, val in zip(timestamps, cpu_usage, strict=True)]
43+
44+
# Build Highcharts configuration as Python dict, then serialize to JSON
45+
highcharts_config = {
46+
"chart": {
47+
"type": "line",
48+
"width": 4800,
49+
"height": 2700,
50+
"backgroundColor": "#ffffff",
51+
"spacingTop": 60,
52+
"spacingBottom": 100,
53+
"spacingLeft": 80,
54+
"spacingRight": 80,
55+
"zoomType": "x",
56+
},
57+
"title": {
58+
"text": "line-interactive \u00b7 highcharts \u00b7 pyplots.ai",
59+
"style": {"fontSize": "56px", "fontWeight": "bold"},
60+
"margin": 40,
61+
},
62+
"subtitle": {
63+
"text": "Server CPU Usage - Click and drag to zoom (7 Days of Hourly Data)",
64+
"style": {"fontSize": "36px", "color": "#666666"},
65+
},
66+
"xAxis": {
67+
"type": "datetime",
68+
"title": {"text": "Date and Time", "style": {"fontSize": "36px"}, "margin": 25},
69+
"labels": {"style": {"fontSize": "28px"}},
70+
"gridLineWidth": 1,
71+
"gridLineColor": "rgba(0, 0, 0, 0.1)",
72+
"tickInterval": 24 * 3600 * 1000,
73+
"dateTimeLabelFormats": {"day": "%b %d"},
74+
"crosshair": {"width": 2, "color": "#306998"},
75+
},
76+
"yAxis": {
77+
"title": {"text": "CPU Usage (%)", "style": {"fontSize": "36px"}, "margin": 25},
78+
"labels": {"style": {"fontSize": "28px"}, "format": "{value}%"},
79+
"gridLineWidth": 1,
80+
"gridLineColor": "rgba(0, 0, 0, 0.1)",
81+
"min": 0,
82+
"max": 105,
83+
},
84+
"legend": {"enabled": True, "itemStyle": {"fontSize": "32px"}, "margin": 30},
85+
"tooltip": {
86+
"xDateFormat": "%A, %b %d, %H:%M",
87+
"valueSuffix": "%",
88+
"style": {"fontSize": "28px"},
89+
"headerFormat": "<b>{point.key}</b><br/>",
90+
"pointFormat": "CPU: <b>{point.y}%</b>",
91+
},
92+
"plotOptions": {
93+
"line": {
94+
"lineWidth": 5,
95+
"marker": {"enabled": False, "radius": 8, "states": {"hover": {"enabled": True, "radius": 10}}},
96+
"states": {"hover": {"lineWidth": 6}},
97+
}
98+
},
99+
"series": [{"name": "CPU Usage", "data": data_points, "color": "#306998", "type": "line"}],
100+
}
101+
102+
# Convert to JSON string
103+
config_json = json.dumps(highcharts_config)
104+
105+
# Download Highcharts JS for inline embedding
106+
highcharts_url = "https://code.highcharts.com/highcharts.js"
107+
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
108+
highcharts_js = response.read().decode("utf-8")
109+
110+
# Generate HTML with proper JSON config
111+
html_content = f"""<!DOCTYPE html>
112+
<html>
113+
<head>
114+
<meta charset="utf-8">
115+
<script>{highcharts_js}</script>
116+
</head>
117+
<body style="margin:0;">
118+
<div id="container" style="width: 4800px; height: 2700px;"></div>
119+
<script>
120+
document.addEventListener('DOMContentLoaded', function() {{
121+
Highcharts.chart('container', {config_json});
122+
}});
123+
</script>
124+
</body>
125+
</html>"""
126+
127+
# Save HTML version (interactive)
128+
with open("plot.html", "w", encoding="utf-8") as f:
129+
cdn_html = f"""<!DOCTYPE html>
130+
<html>
131+
<head>
132+
<meta charset="utf-8">
133+
<title>line-interactive · highcharts · pyplots.ai</title>
134+
<script src="https://code.highcharts.com/highcharts.js"></script>
135+
</head>
136+
<body style="margin:0;">
137+
<div id="container" style="width: 100%; height: 100vh;"></div>
138+
<script>
139+
document.addEventListener('DOMContentLoaded', function() {{
140+
Highcharts.chart('container', {config_json});
141+
}});
142+
</script>
143+
</body>
144+
</html>"""
145+
f.write(cdn_html)
146+
147+
# Take screenshot with headless Chrome
148+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
149+
f.write(html_content)
150+
temp_path = f.name
151+
152+
chrome_options = Options()
153+
chrome_options.add_argument("--headless")
154+
chrome_options.add_argument("--no-sandbox")
155+
chrome_options.add_argument("--disable-dev-shm-usage")
156+
chrome_options.add_argument("--disable-gpu")
157+
chrome_options.add_argument("--window-size=5000,3000")
158+
159+
driver = webdriver.Chrome(options=chrome_options)
160+
driver.get(f"file://{temp_path}")
161+
time.sleep(5)
162+
163+
# Screenshot the chart container element for exact dimensions
164+
container = driver.find_element("id", "container")
165+
container.screenshot("plot.png")
166+
driver.quit()
167+
168+
Path(temp_path).unlink()
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
library: highcharts
2+
specification_id: line-interactive
3+
created: '2025-12-30T18:38:29Z'
4+
updated: '2025-12-30T18:40:57Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20603305896
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: unknown
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/line-interactive/highcharts/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/line-interactive/highcharts/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/line-interactive/highcharts/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent title formatting matching the required spec format
17+
- Clean realistic data scenario with CPU usage showing daily cycles and anomalies
18+
- Good implementation of core interactive features (zoom, crosshair, tooltips)
19+
- Proper colorblind-safe color choice (#306998)
20+
- Both PNG and HTML outputs generated correctly
21+
- Appropriate font sizing for the 4800x2700 canvas
22+
weaknesses:
23+
- Missing Highcharts navigator/range selector which would enhance interactivity
24+
for time series
25+
- Legend marker at bottom is quite small
26+
- Uses raw dict configuration instead of highcharts-core Python wrapper classes

0 commit comments

Comments
 (0)