|
1 | 1 | """ |
2 | 2 | area-basic: Basic Area Chart |
3 | | -Implementation for: highcharts |
4 | | -Variant: default |
5 | | -Python: 3.10+ |
6 | | -
|
7 | | -Note: Highcharts requires a license for commercial use. |
| 3 | +Library: highcharts |
8 | 4 | """ |
9 | 5 |
|
10 | | -from typing import Optional |
| 6 | +import tempfile |
| 7 | +import time |
| 8 | +import urllib.request |
| 9 | +from pathlib import Path |
11 | 10 |
|
12 | | -import pandas as pd |
13 | 11 | from highcharts_core.chart import Chart |
14 | 12 | from highcharts_core.options import HighchartsOptions |
15 | 13 | from highcharts_core.options.series.area import AreaSeries |
16 | | - |
17 | | - |
18 | | -def create_plot( |
19 | | - data: pd.DataFrame, |
20 | | - x: str, |
21 | | - y: str, |
22 | | - title: Optional[str] = None, |
23 | | - xlabel: Optional[str] = None, |
24 | | - ylabel: Optional[str] = None, |
25 | | - color: str = "#4682B4", |
26 | | - fill_opacity: float = 0.5, |
27 | | - line_width: int = 2, |
28 | | - width: int = 1600, |
29 | | - height: int = 900, |
30 | | - **kwargs, |
31 | | -) -> Chart: |
32 | | - """ |
33 | | - Create a basic area chart showing magnitude and trends using Highcharts. |
34 | | -
|
35 | | - Args: |
36 | | - data: Input DataFrame with required columns |
37 | | - x: Column name for x-axis values |
38 | | - y: Column name for y-axis values (numeric) |
39 | | - title: Plot title (optional) |
40 | | - xlabel: Custom x-axis label (optional, defaults to x column name) |
41 | | - ylabel: Custom y-axis label (optional, defaults to y column name) |
42 | | - color: Fill and line color (default: "#4682B4" steelblue) |
43 | | - fill_opacity: Transparency level for the fill area (default: 0.5) |
44 | | - line_width: Width of the line on top of the area (default: 2) |
45 | | - width: Figure width in pixels (default: 1600) |
46 | | - height: Figure height in pixels (default: 900) |
47 | | - **kwargs: Additional parameters for Highcharts configuration |
48 | | -
|
49 | | - Returns: |
50 | | - Highcharts Chart object |
51 | | -
|
52 | | - Raises: |
53 | | - ValueError: If data is empty |
54 | | - KeyError: If required columns not found |
55 | | -
|
56 | | - Example: |
57 | | - >>> data = pd.DataFrame({ |
58 | | - ... 'month': [1, 2, 3, 4, 5, 6], |
59 | | - ... 'sales': [100, 150, 130, 180, 200, 220] |
60 | | - ... }) |
61 | | - >>> chart = create_plot(data, x='month', y='sales') |
62 | | - """ |
63 | | - # Input validation |
64 | | - if data.empty: |
65 | | - raise ValueError("Data cannot be empty") |
66 | | - |
67 | | - # Check required columns |
68 | | - for col in [x, y]: |
69 | | - if col not in data.columns: |
70 | | - available = ", ".join(data.columns) |
71 | | - raise KeyError(f"Column '{col}' not found. Available columns: {available}") |
72 | | - |
73 | | - # Prepare data - sort by x values for proper area rendering |
74 | | - sorted_data = data[[x, y]].dropna().sort_values(by=x) |
75 | | - x_values = sorted_data[x].tolist() |
76 | | - y_values = sorted_data[y].tolist() |
77 | | - |
78 | | - # Determine if x-axis should be categorical or numeric |
79 | | - x_is_numeric = pd.api.types.is_numeric_dtype(data[x]) |
80 | | - |
81 | | - # Create chart |
82 | | - chart = Chart() |
83 | | - |
84 | | - # Configure chart options |
85 | | - chart.options = HighchartsOptions() |
86 | | - |
87 | | - # Title |
88 | | - chart.options.title = { |
89 | | - "text": title or "Area Chart", |
90 | | - "style": {"fontSize": "16px", "fontWeight": "bold"}, |
91 | | - } |
92 | | - |
93 | | - # X-axis configuration |
94 | | - if x_is_numeric: |
95 | | - chart.options.x_axis = { |
96 | | - "title": {"text": xlabel or x}, |
97 | | - "gridLineWidth": 1, |
98 | | - "gridLineDashStyle": "Dot", |
99 | | - "gridLineColor": "rgba(0, 0, 0, 0.1)", |
100 | | - } |
101 | | - else: |
102 | | - # Categorical x-axis |
103 | | - chart.options.x_axis = { |
104 | | - "categories": x_values, |
105 | | - "title": {"text": xlabel or x}, |
106 | | - "gridLineWidth": 1, |
107 | | - "gridLineDashStyle": "Dot", |
108 | | - "gridLineColor": "rgba(0, 0, 0, 0.1)", |
109 | | - } |
110 | | - |
111 | | - # Y-axis |
112 | | - chart.options.y_axis = { |
113 | | - "title": {"text": ylabel or y}, |
114 | | - "gridLineWidth": 1, |
115 | | - "gridLineDashStyle": "Dot", |
116 | | - "gridLineColor": "rgba(0, 0, 0, 0.1)", |
117 | | - "min": 0, # Area charts typically start from 0 |
118 | | - } |
119 | | - |
120 | | - # Plot options for area series |
121 | | - chart.options.plot_options = { |
122 | | - "area": { |
123 | | - "fillOpacity": fill_opacity, |
124 | | - "lineWidth": line_width, |
125 | | - "marker": { |
126 | | - "enabled": True, |
127 | | - "radius": 4, |
128 | | - "fillColor": color, |
129 | | - "lineWidth": 1, |
130 | | - "lineColor": "#ffffff", |
131 | | - }, |
132 | | - } |
133 | | - } |
134 | | - |
135 | | - # Tooltip |
136 | | - chart.options.tooltip = { |
137 | | - "shared": False, |
138 | | - "useHTML": True, |
139 | | - "headerFormat": "<b>{point.key}</b><br/>", |
140 | | - "pointFormat": f"<span>{ylabel or y}: {{point.y}}</span>", |
141 | | - } |
142 | | - |
143 | | - # Chart dimensions and type |
144 | | - chart.options.chart = { |
145 | | - "type": "area", |
146 | | - "width": width, |
147 | | - "height": height, |
148 | | - "backgroundColor": "white", |
149 | | - } |
150 | | - |
151 | | - # Create area series |
152 | | - area_series = AreaSeries() |
153 | | - area_series.name = ylabel or y |
154 | | - area_series.color = color |
155 | | - |
156 | | - # Set data based on x-axis type |
157 | | - if x_is_numeric: |
158 | | - # For numeric x-axis, use [x, y] pairs |
159 | | - area_series.data = list(zip(x_values, y_values, strict=True)) |
160 | | - else: |
161 | | - # For categorical x-axis, use y values only (categories are set on x-axis) |
162 | | - area_series.data = y_values |
163 | | - |
164 | | - chart.add_series(area_series) |
165 | | - |
166 | | - # Legend |
167 | | - chart.options.legend = { |
168 | | - "enabled": False, # Single series, no legend needed |
169 | | - } |
170 | | - |
171 | | - # Credits |
172 | | - chart.options.credits = {"enabled": False} |
173 | | - |
174 | | - return chart |
175 | | - |
176 | | - |
177 | | -if __name__ == "__main__": |
178 | | - import tempfile |
179 | | - import time |
180 | | - from pathlib import Path |
181 | | - |
182 | | - from selenium import webdriver |
183 | | - from selenium.webdriver.chrome.options import Options |
184 | | - |
185 | | - # Sample data for testing - monthly sales data |
186 | | - sample_data = pd.DataFrame({ |
187 | | - "Month": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], |
188 | | - "Sales": [120, 150, 170, 160, 180, 220, 250, 240, 210, 190, 230, 280], |
189 | | - }) |
190 | | - |
191 | | - # Create plot with categorical x-axis |
192 | | - chart = create_plot( |
193 | | - sample_data, |
194 | | - x="Month", |
195 | | - y="Sales", |
196 | | - title="Monthly Sales Performance", |
197 | | - ylabel="Sales ($K)", |
198 | | - xlabel="Month", |
199 | | - color="#4682B4", |
200 | | - fill_opacity=0.4, |
201 | | - ) |
202 | | - |
203 | | - # Generate HTML content |
204 | | - html_str = chart.to_js_literal() |
205 | | - html_content = f"""<!DOCTYPE html> |
| 14 | +from selenium import webdriver |
| 15 | +from selenium.webdriver.chrome.options import Options |
| 16 | + |
| 17 | + |
| 18 | +# Data - Monthly sales data |
| 19 | +sales = [100, 150, 130, 180, 200, 220, 195, 240, 260, 245, 280, 310] |
| 20 | + |
| 21 | +# Create chart |
| 22 | +chart = Chart(container="container") |
| 23 | +chart.options = HighchartsOptions() |
| 24 | + |
| 25 | +# Chart configuration |
| 26 | +chart.options.chart = {"type": "area", "width": 4800, "height": 2700, "backgroundColor": "#ffffff"} |
| 27 | + |
| 28 | +# Title |
| 29 | +chart.options.title = {"text": "Basic Area Chart", "style": {"fontSize": "48px"}} |
| 30 | + |
| 31 | +# Axes |
| 32 | +chart.options.x_axis = { |
| 33 | + "title": {"text": "Month", "style": {"fontSize": "40px"}}, |
| 34 | + "categories": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], |
| 35 | + "labels": {"style": {"fontSize": "32px"}}, |
| 36 | + "gridLineWidth": 1, |
| 37 | + "gridLineColor": "rgba(0, 0, 0, 0.1)", |
| 38 | +} |
| 39 | + |
| 40 | +chart.options.y_axis = { |
| 41 | + "title": {"text": "Sales", "style": {"fontSize": "40px"}}, |
| 42 | + "labels": {"style": {"fontSize": "32px"}}, |
| 43 | + "gridLineWidth": 1, |
| 44 | + "gridLineColor": "rgba(0, 0, 0, 0.1)", |
| 45 | +} |
| 46 | + |
| 47 | +# Legend styling |
| 48 | +chart.options.legend = {"itemStyle": {"fontSize": "32px"}} |
| 49 | + |
| 50 | +# Area series |
| 51 | +series = AreaSeries() |
| 52 | +series.data = sales |
| 53 | +series.name = "Monthly Sales" |
| 54 | +series.color = "#306998" # Python Blue |
| 55 | +series.fill_opacity = 0.5 |
| 56 | +series.line_width = 4 |
| 57 | + |
| 58 | +chart.add_series(series) |
| 59 | + |
| 60 | +# Download Highcharts JS for inline embedding |
| 61 | +highcharts_url = "https://code.highcharts.com/highcharts.js" |
| 62 | +with urllib.request.urlopen(highcharts_url, timeout=30) as response: |
| 63 | + highcharts_js = response.read().decode("utf-8") |
| 64 | + |
| 65 | +# Generate HTML with inline scripts |
| 66 | +html_str = chart.to_js_literal() |
| 67 | +html_content = f"""<!DOCTYPE html> |
206 | 68 | <html> |
207 | 69 | <head> |
208 | 70 | <meta charset="utf-8"> |
209 | | - <script src="https://code.highcharts.com/highcharts.js"></script> |
| 71 | + <script>{highcharts_js}</script> |
210 | 72 | </head> |
211 | 73 | <body style="margin:0;"> |
212 | | - <div id="container" style="width: 1600px; height: 900px;"></div> |
| 74 | + <div id="container" style="width: 4800px; height: 2700px;"></div> |
213 | 75 | <script>{html_str}</script> |
214 | 76 | </body> |
215 | 77 | </html>""" |
216 | 78 |
|
217 | | - # Write temp HTML and take screenshot |
218 | | - with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False) as f: |
219 | | - f.write(html_content) |
220 | | - temp_path = f.name |
221 | | - |
222 | | - chrome_options = Options() |
223 | | - chrome_options.add_argument("--headless") |
224 | | - chrome_options.add_argument("--no-sandbox") |
225 | | - chrome_options.add_argument("--disable-dev-shm-usage") |
226 | | - chrome_options.add_argument("--window-size=1600,900") |
227 | | - |
228 | | - driver = webdriver.Chrome(options=chrome_options) |
229 | | - driver.get(f"file://{temp_path}") |
230 | | - time.sleep(1) # Wait for chart to render |
231 | | - driver.save_screenshot("plot.png") |
232 | | - driver.quit() |
233 | | - |
234 | | - Path(temp_path).unlink() # Clean up temp file |
235 | | - print("Plot saved to plot.png") |
| 79 | +# Write temp HTML and take screenshot |
| 80 | +with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f: |
| 81 | + f.write(html_content) |
| 82 | + temp_path = f.name |
| 83 | + |
| 84 | +chrome_options = Options() |
| 85 | +chrome_options.add_argument("--headless") |
| 86 | +chrome_options.add_argument("--no-sandbox") |
| 87 | +chrome_options.add_argument("--disable-dev-shm-usage") |
| 88 | +chrome_options.add_argument("--disable-gpu") |
| 89 | +chrome_options.add_argument("--window-size=4800,2700") |
| 90 | + |
| 91 | +driver = webdriver.Chrome(options=chrome_options) |
| 92 | +driver.get(f"file://{temp_path}") |
| 93 | +time.sleep(5) # Wait for chart to render |
| 94 | +driver.save_screenshot("plot.png") |
| 95 | +driver.quit() |
| 96 | + |
| 97 | +Path(temp_path).unlink() # Clean up temp file |
0 commit comments