Skip to content

Commit dcf40b0

Browse files
feat(highcharts): implement slider-control-basic (#3117)
## Implementation: `slider-control-basic` - highcharts Implements the **highcharts** version of `slider-control-basic`. **File:** `plots/slider-control-basic/implementations/highcharts.py` **Parent Issue:** #3071 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20620380550)* --------- 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 e1e2cab commit dcf40b0

2 files changed

Lines changed: 280 additions & 0 deletions

File tree

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
""" pyplots.ai
2+
slider-control-basic: Interactive Plot with Slider Control
3+
Library: highcharts unknown | Python 3.13.11
4+
Quality: 91/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+
17+
18+
# Data - Monthly sales data across 5 years (2019-2023)
19+
np.random.seed(42)
20+
years = [2019, 2020, 2021, 2022, 2023]
21+
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
22+
23+
# Generate sales data with yearly trends
24+
base_sales = np.array([120, 110, 135, 150, 165, 180, 175, 190, 160, 145, 170, 210])
25+
yearly_data = {}
26+
for i, year in enumerate(years):
27+
growth = 1 + 0.08 * i # 8% yearly growth
28+
seasonal = base_sales * growth + np.random.randn(12) * 15
29+
yearly_data[year] = [round(max(50, v), 1) for v in seasonal]
30+
31+
# Download Highcharts JS
32+
highcharts_url = "https://code.highcharts.com/highcharts.js"
33+
with urllib.request.urlopen(highcharts_url, timeout=30) as response:
34+
highcharts_js = response.read().decode("utf-8")
35+
36+
# Build series data for all years - colorblind-safe palette
37+
colors = ["#306998", "#FFD43B", "#9467BD", "#17BECF", "#8C564B"]
38+
series_configs = []
39+
40+
for i, year in enumerate(years):
41+
series_configs.append(
42+
{
43+
"name": str(year),
44+
"data": yearly_data[year],
45+
"color": colors[i % len(colors)],
46+
"visible": year == 2023, # Only show 2023 initially
47+
"lineWidth": 6,
48+
"marker": {"radius": 14, "symbol": "circle", "lineWidth": 2, "lineColor": "#ffffff"},
49+
}
50+
)
51+
52+
# Create Highcharts options with slider control
53+
chart_options = {
54+
"chart": {
55+
"type": "line",
56+
"width": 4800,
57+
"height": 2200,
58+
"backgroundColor": "#ffffff",
59+
"marginBottom": 180,
60+
"marginTop": 200,
61+
"marginLeft": 280,
62+
"marginRight": 120,
63+
"style": {"fontFamily": "Arial, sans-serif"},
64+
},
65+
"title": {
66+
"text": "slider-control-basic · highcharts · pyplots.ai",
67+
"style": {"fontSize": "72px", "fontWeight": "bold", "color": "#333333"},
68+
"y": 80,
69+
},
70+
"subtitle": {
71+
"text": "Monthly Sales by Year - Use slider below to filter by year",
72+
"style": {"fontSize": "44px", "color": "#666666"},
73+
"y": 140,
74+
},
75+
"xAxis": {
76+
"categories": months,
77+
"title": {"text": "Month", "style": {"fontSize": "48px", "color": "#333333"}, "margin": 25},
78+
"labels": {"style": {"fontSize": "40px", "color": "#333333"}, "y": 35},
79+
"lineWidth": 3,
80+
"tickWidth": 3,
81+
"tickLength": 15,
82+
},
83+
"yAxis": {
84+
"title": {"text": "Sales (thousands USD)", "style": {"fontSize": "48px", "color": "#333333"}, "margin": 25},
85+
"labels": {"style": {"fontSize": "40px", "color": "#333333"}, "x": -15},
86+
"gridLineWidth": 2,
87+
"gridLineColor": "rgba(0,0,0,0.12)",
88+
"gridLineDashStyle": "Dash",
89+
"min": 0,
90+
},
91+
"legend": {
92+
"enabled": False # Disabled - using slider instead
93+
},
94+
"tooltip": {
95+
"enabled": True,
96+
"style": {"fontSize": "32px"},
97+
"headerFormat": '<span style="font-size: 32px; font-weight: bold;">{point.key}</span><br/>',
98+
"pointFormat": '<span style="color:{series.color}">●</span> {series.name}: <b>${point.y:.1f}K</b><br/>',
99+
},
100+
"plotOptions": {
101+
"line": {"lineWidth": 6, "marker": {"radius": 14, "symbol": "circle"}, "states": {"hover": {"lineWidth": 8}}},
102+
"series": {"animation": False},
103+
},
104+
"credits": {"enabled": False},
105+
"series": series_configs,
106+
}
107+
108+
# Convert to JavaScript - using JSON for clean serialization
109+
chart_json = json.dumps(chart_options)
110+
111+
# Custom slider HTML with JavaScript for interactivity
112+
slider_html = """
113+
<div id="slider-container" style="
114+
position: absolute;
115+
bottom: 80px;
116+
left: 50%;
117+
transform: translateX(-50%);
118+
width: 70%;
119+
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
120+
padding: 50px 80px;
121+
border-radius: 30px;
122+
box-shadow: 0 8px 32px rgba(0,0,0,0.15);
123+
text-align: center;
124+
z-index: 1000;
125+
border: 3px solid #dee2e6;
126+
">
127+
<div style="margin-bottom: 30px; font-size: 42px; color: #495057; font-weight: 600;">
128+
Select Year to Display
129+
</div>
130+
<div style="display: flex; align-items: center; justify-content: center; gap: 60px;">
131+
<span style="font-size: 48px; font-weight: bold; color: #306998;">2019</span>
132+
<input type="range" id="year-slider" min="2019" max="2023" value="2023" step="1"
133+
style="
134+
width: 55%;
135+
height: 40px;
136+
cursor: pointer;
137+
-webkit-appearance: none;
138+
appearance: none;
139+
background: linear-gradient(to right, #306998, #FFD43B);
140+
border-radius: 20px;
141+
outline: none;
142+
box-shadow: inset 0 2px 8px rgba(0,0,0,0.2);
143+
"
144+
/>
145+
<span style="font-size: 48px; font-weight: bold; color: #FFD43B; text-shadow: 1px 1px 2px #999;">2023</span>
146+
</div>
147+
<div id="current-year" style="
148+
margin-top: 40px;
149+
font-size: 72px;
150+
font-weight: bold;
151+
color: #306998;
152+
text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
153+
">2023</div>
154+
</div>
155+
"""
156+
157+
slider_js = """
158+
<script>
159+
document.getElementById('year-slider').addEventListener('input', function() {
160+
var selectedYear = parseInt(this.value);
161+
document.getElementById('current-year').textContent = selectedYear;
162+
163+
var chart = Highcharts.charts[0];
164+
if (chart) {
165+
chart.series.forEach(function(series) {
166+
var seriesYear = parseInt(series.name);
167+
if (seriesYear === selectedYear) {
168+
series.show();
169+
} else {
170+
series.hide();
171+
}
172+
});
173+
}
174+
});
175+
176+
// Style the slider thumb
177+
var style = document.createElement('style');
178+
style.textContent = `
179+
#year-slider::-webkit-slider-thumb {
180+
-webkit-appearance: none;
181+
appearance: none;
182+
width: 70px;
183+
height: 70px;
184+
background: #306998;
185+
border-radius: 50%;
186+
cursor: pointer;
187+
box-shadow: 0 6px 20px rgba(0,0,0,0.4);
188+
border: 4px solid #ffffff;
189+
}
190+
#year-slider::-moz-range-thumb {
191+
width: 70px;
192+
height: 70px;
193+
background: #306998;
194+
border-radius: 50%;
195+
cursor: pointer;
196+
box-shadow: 0 6px 20px rgba(0,0,0,0.4);
197+
border: 4px solid #ffffff;
198+
}
199+
`;
200+
document.head.appendChild(style);
201+
</script>
202+
"""
203+
204+
# Create HTML with embedded Highcharts
205+
html_content = f"""<!DOCTYPE html>
206+
<html>
207+
<head>
208+
<meta charset="utf-8">
209+
<script>{highcharts_js}</script>
210+
<style>
211+
body {{
212+
margin: 0;
213+
padding: 0;
214+
background: #ffffff;
215+
}}
216+
</style>
217+
</head>
218+
<body>
219+
<div id="container" style="width: 4800px; height: 2200px;"></div>
220+
{slider_html}
221+
<script>
222+
Highcharts.chart('container', {chart_json});
223+
</script>
224+
{slider_js}
225+
</body>
226+
</html>"""
227+
228+
# Save HTML for interactive version
229+
with open("plot.html", "w", encoding="utf-8") as f:
230+
f.write(html_content)
231+
232+
# Screenshot with Selenium
233+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f:
234+
f.write(html_content)
235+
temp_path = f.name
236+
237+
chrome_options = Options()
238+
chrome_options.add_argument("--headless")
239+
chrome_options.add_argument("--no-sandbox")
240+
chrome_options.add_argument("--disable-dev-shm-usage")
241+
chrome_options.add_argument("--disable-gpu")
242+
chrome_options.add_argument("--window-size=4800,2700")
243+
chrome_options.add_argument("--force-device-scale-factor=1")
244+
245+
driver = webdriver.Chrome(options=chrome_options)
246+
driver.get(f"file://{temp_path}")
247+
time.sleep(5) # Wait for chart to render
248+
driver.save_screenshot("plot.png")
249+
driver.quit()
250+
251+
Path(temp_path).unlink() # Clean up temp file
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
library: highcharts
2+
specification_id: slider-control-basic
3+
created: '2025-12-31T13:59:23Z'
4+
updated: '2025-12-31T14:10:59Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20620380550
7+
issue: 3071
8+
python_version: 3.13.11
9+
library_version: unknown
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/slider-control-basic/highcharts/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/slider-control-basic/highcharts/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/slider-control-basic/highcharts/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent slider widget design with clear labels, gradient styling, and prominent
17+
current value display
18+
- Clean title format following spec requirements exactly
19+
- Good data generation with realistic yearly growth trends and seasonal patterns
20+
- Proper use of colorblind-safe color palette
21+
- Responsive slider interaction that shows/hides series appropriately
22+
- Professional-looking chart with appropriate text sizes for the canvas size
23+
weaknesses:
24+
- Legend is disabled; would be clearer to show a small legend indicating the currently
25+
selected year
26+
- Grid could be slightly more subtle (alpha is 0.12 which is good, but visual weight
27+
is noticeable)
28+
- The line color shown (brownish-red) differs from the blue (#306998) assigned to
29+
2023 - appears series colors are not rendering as expected

0 commit comments

Comments
 (0)