|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | box-horizontal: Horizontal Box Plot |
3 | | -Library: highcharts unknown | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2025-12-30 |
| 3 | +Library: highcharts unknown | Python 3.13.13 |
| 4 | +Quality: 84/100 | Updated: 2026-05-12 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
7 | 8 | import tempfile |
8 | 9 | import time |
9 | 10 | import urllib.request |
|
17 | 18 | from selenium.webdriver.chrome.options import Options |
18 | 19 |
|
19 | 20 |
|
20 | | -# Data - Response times by service type (realistic scenario) |
| 21 | +# Theme tokens |
| 22 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 23 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 24 | +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
| 25 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 26 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 27 | +GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)" |
| 28 | +BRAND = "#009E73" # Okabe-Ito position 1 |
| 29 | + |
| 30 | +# Data - Response times by service type with explicit outliers |
21 | 31 | np.random.seed(42) |
22 | 32 |
|
23 | 33 | categories = ["API Gateway", "Database Query", "File Upload", "Authentication", "Payment Processing"] |
24 | 34 |
|
25 | 35 | # Generate data with different distributions for each service |
26 | | -# Each category needs: [low, q1, median, q3, high] |
| 36 | +# Include explicit outliers to better demonstrate box plot capabilities |
27 | 37 | data = [] |
28 | 38 | for cat in categories: |
29 | 39 | if cat == "API Gateway": |
30 | | - values = np.random.normal(50, 15, 200) # Fast, consistent |
| 40 | + values = np.random.normal(50, 15, 200) |
31 | 41 | elif cat == "Database Query": |
32 | | - values = np.random.normal(120, 40, 200) # Moderate with variation |
| 42 | + values = np.random.normal(120, 40, 200) |
33 | 43 | elif cat == "File Upload": |
34 | | - values = np.random.normal(250, 80, 200) # Slow, high variance |
| 44 | + values = np.random.normal(250, 80, 200) |
35 | 45 | elif cat == "Authentication": |
36 | | - values = np.random.normal(30, 8, 200) # Very fast |
| 46 | + values = np.random.normal(30, 8, 200) |
37 | 47 | else: # Payment Processing |
38 | | - values = np.random.normal(180, 50, 200) # Moderate-slow |
| 48 | + values = np.random.normal(180, 50, 200) |
39 | 49 |
|
40 | 50 | # Ensure positive values |
41 | 51 | values = np.maximum(values, 5) |
42 | 52 |
|
| 53 | + # Add explicit outliers to better demonstrate box plot |
| 54 | + outlier_indices = np.random.choice(len(values), size=3, replace=False) |
| 55 | + if cat == "API Gateway": |
| 56 | + values[outlier_indices] = [150, 160, 170] |
| 57 | + elif cat == "Database Query": |
| 58 | + values[outlier_indices] = [320, 350, 380] |
| 59 | + elif cat == "File Upload": |
| 60 | + values[outlier_indices] = [600, 620, 640] |
| 61 | + elif cat == "Authentication": |
| 62 | + values[outlier_indices] = [120, 130, 140] |
| 63 | + else: # Payment Processing |
| 64 | + values[outlier_indices] = [450, 480, 510] |
| 65 | + |
43 | 66 | # Calculate quartiles |
44 | 67 | q1, median, q3 = np.percentile(values, [25, 50, 75]) |
45 | 68 | iqr = q3 - q1 |
|
56 | 79 | # Chart configuration - use full 4800x2700 with proper margins |
57 | 80 | chart.options.chart = { |
58 | 81 | "type": "boxplot", |
59 | | - "inverted": True, # This makes it horizontal |
| 82 | + "inverted": True, |
60 | 83 | "width": 4800, |
61 | 84 | "height": 2700, |
62 | | - "backgroundColor": "#ffffff", |
63 | | - "marginLeft": 450, # Extra space for category labels and axis title |
64 | | - "marginBottom": 220, # Extra space for value axis labels and title |
| 85 | + "backgroundColor": PAGE_BG, |
| 86 | + "marginLeft": 450, |
| 87 | + "marginBottom": 220, |
65 | 88 | "marginTop": 180, |
66 | 89 | "marginRight": 120, |
67 | 90 | } |
68 | 91 |
|
69 | 92 | # Title |
70 | 93 | chart.options.title = { |
71 | | - "text": "box-horizontal · highcharts · pyplots.ai", |
72 | | - "style": {"fontSize": "64px", "fontWeight": "bold"}, |
73 | | - "y": 60, |
| 94 | + "text": "box-horizontal · highcharts · anyplot.ai", |
| 95 | + "style": {"fontSize": "28px", "fontWeight": "600", "color": INK}, |
| 96 | + "y": 40, |
74 | 97 | } |
75 | 98 |
|
76 | 99 | # Subtitle for context |
77 | 100 | chart.options.subtitle = { |
78 | 101 | "text": "Response Time Distribution by Service Type", |
79 | | - "style": {"fontSize": "42px", "color": "#666666"}, |
80 | | - "y": 120, |
| 102 | + "style": {"fontSize": "22px", "color": INK_SOFT}, |
| 103 | + "y": 90, |
81 | 104 | } |
82 | 105 |
|
83 | 106 | # X-axis (categories - shown on left due to inverted) |
84 | 107 | chart.options.x_axis = { |
85 | 108 | "categories": categories, |
86 | | - "title": {"text": "Service Type", "style": {"fontSize": "42px"}, "margin": 20}, |
87 | | - "labels": {"style": {"fontSize": "36px"}, "x": -10}, |
88 | | - "lineWidth": 2, |
89 | | - "lineColor": "#333333", |
| 109 | + "title": {"text": "Service Type", "style": {"fontSize": "22px", "color": INK}}, |
| 110 | + "labels": {"style": {"fontSize": "18px", "color": INK_SOFT}}, |
| 111 | + "lineColor": INK_SOFT, |
| 112 | + "tickColor": INK_SOFT, |
90 | 113 | } |
91 | 114 |
|
92 | 115 | # Y-axis (values - shown on bottom due to inverted) |
93 | 116 | chart.options.y_axis = { |
94 | | - "title": {"text": "Response Time (ms)", "style": {"fontSize": "42px"}, "margin": 20}, |
95 | | - "labels": {"style": {"fontSize": "32px"}, "y": 30}, |
96 | | - "gridLineColor": "#e0e0e0", |
| 117 | + "title": {"text": "Response Time (ms)", "style": {"fontSize": "22px", "color": INK}}, |
| 118 | + "labels": {"style": {"fontSize": "18px", "color": INK_SOFT}}, |
| 119 | + "gridLineColor": GRID, |
97 | 120 | "gridLineWidth": 1, |
98 | 121 | "min": 0, |
99 | | - "lineWidth": 2, |
100 | | - "lineColor": "#333333", |
| 122 | + "lineColor": INK_SOFT, |
| 123 | + "tickColor": INK_SOFT, |
101 | 124 | } |
102 | 125 |
|
103 | 126 | # Legend |
104 | 127 | chart.options.legend = {"enabled": False} |
105 | 128 |
|
106 | | -# Plot options for boxplot styling |
| 129 | +# Plot options for boxplot styling using Okabe-Ito palette |
107 | 130 | chart.options.plot_options = { |
108 | 131 | "boxplot": { |
109 | | - "fillColor": "rgba(48, 105, 152, 0.7)", # Python Blue with transparency |
110 | | - "color": "#306998", # Box outline color |
111 | | - "lineWidth": 4, |
112 | | - "medianColor": "#FFD43B", # Python Yellow for median |
113 | | - "medianWidth": 8, |
114 | | - "stemColor": "#306998", |
115 | | - "stemWidth": 4, |
116 | | - "whiskerColor": "#306998", |
| 132 | + "fillColor": BRAND, |
| 133 | + "color": BRAND, |
| 134 | + "lineWidth": 3, |
| 135 | + "medianColor": INK, |
| 136 | + "medianWidth": 4, |
| 137 | + "stemColor": BRAND, |
| 138 | + "stemWidth": 2, |
| 139 | + "whiskerColor": BRAND, |
117 | 140 | "whiskerLength": "40%", |
118 | | - "whiskerWidth": 4, |
119 | | - "pointWidth": 80, |
| 141 | + "whiskerWidth": 2, |
| 142 | + "pointWidth": 70, |
120 | 143 | } |
121 | 144 | } |
122 | 145 |
|
|
128 | 151 | chart.add_series(series) |
129 | 152 |
|
130 | 153 | # Download Highcharts JS files (required for headless Chrome) |
| 154 | +headers = { |
| 155 | + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36", |
| 156 | + "Accept": "text/javascript", |
| 157 | + "Referer": "https://www.highcharts.com/", |
| 158 | +} |
| 159 | + |
131 | 160 | highcharts_url = "https://code.highcharts.com/highcharts.js" |
132 | | -with urllib.request.urlopen(highcharts_url, timeout=30) as response: |
| 161 | +highcharts_req = urllib.request.Request(highcharts_url, headers=headers) |
| 162 | +with urllib.request.urlopen(highcharts_req, timeout=30) as response: |
133 | 163 | highcharts_js = response.read().decode("utf-8") |
134 | 164 |
|
135 | 165 | # BoxPlot requires highcharts-more.js |
136 | 166 | highcharts_more_url = "https://code.highcharts.com/highcharts-more.js" |
137 | | -with urllib.request.urlopen(highcharts_more_url, timeout=30) as response: |
| 167 | +highcharts_more_req = urllib.request.Request(highcharts_more_url, headers=headers) |
| 168 | +with urllib.request.urlopen(highcharts_more_req, timeout=30) as response: |
138 | 169 | highcharts_more_js = response.read().decode("utf-8") |
139 | 170 |
|
140 | 171 | # Generate HTML with INLINE scripts |
|
146 | 177 | <script>{highcharts_js}</script> |
147 | 178 | <script>{highcharts_more_js}</script> |
148 | 179 | </head> |
149 | | -<body style="margin:0; padding:0; overflow:hidden;"> |
| 180 | +<body style="margin:0; padding:0; overflow:hidden; background:{PAGE_BG};"> |
150 | 181 | <div id="container" style="width: 4800px; height: 2700px;"></div> |
151 | 182 | <script>{html_str}</script> |
152 | 183 | </body> |
153 | 184 | </html>""" |
154 | 185 |
|
| 186 | +# Write HTML for interactive version (theme-suffixed) |
| 187 | +with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: |
| 188 | + f.write(html_content) |
| 189 | + |
155 | 190 | # Write temp HTML and take screenshot |
156 | 191 | with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f: |
157 | 192 | f.write(html_content) |
158 | 193 | temp_path = f.name |
159 | 194 |
|
160 | | -# Also save HTML for interactive version |
161 | | -with open("plot.html", "w", encoding="utf-8") as f: |
162 | | - f.write(html_content) |
163 | | - |
164 | 195 | chrome_options = Options() |
165 | 196 | chrome_options.add_argument("--headless") |
166 | 197 | chrome_options.add_argument("--no-sandbox") |
|
172 | 203 | driver = webdriver.Chrome(options=chrome_options) |
173 | 204 | driver.set_window_size(4800, 2700) |
174 | 205 | driver.get(f"file://{temp_path}") |
175 | | -time.sleep(5) # Wait for chart to render |
176 | | -driver.save_screenshot("plot.png") |
| 206 | +time.sleep(5) |
| 207 | +driver.save_screenshot(f"plot-{THEME}.png") |
177 | 208 | driver.quit() |
178 | 209 |
|
179 | | -Path(temp_path).unlink() # Clean up temp file |
180 | | - |
181 | | -print("Generated plot.png and plot.html") |
| 210 | +Path(temp_path).unlink() |
0 commit comments