|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | rug-basic: Basic Rug Plot |
3 | | -Library: highcharts unknown | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2025-12-23 |
| 3 | +Library: highcharts unknown | Python 3.13.13 |
| 4 | +Quality: 89/100 | Updated: 2026-04-30 |
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 (ms) with realistic clustering and gaps |
| 21 | +# Theme tokens |
| 22 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 23 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 24 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 25 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 26 | +TICK_COLOR = "rgba(0, 158, 115, 0.7)" # #009E73 (Okabe-Ito pos 1) with alpha |
| 27 | + |
| 28 | +# Data |
21 | 29 | np.random.seed(42) |
22 | 30 | values = np.concatenate( |
23 | 31 | [ |
24 | 32 | np.random.normal(50, 8, 40), # Fast responses cluster |
25 | 33 | np.random.normal(120, 15, 35), # Medium responses cluster |
26 | 34 | np.random.normal(250, 20, 15), # Slow responses cluster |
27 | | - np.array([380, 420, 510]), # Outliers (occasional slow requests) |
| 35 | + np.array([380, 420, 510]), # Outliers |
28 | 36 | ] |
29 | 37 | ) |
30 | 38 | values = np.clip(values, 10, 600) |
31 | 39 |
|
32 | | -# Create chart |
| 40 | +# Chart |
33 | 41 | chart = Chart(container="container") |
34 | 42 | chart.options = HighchartsOptions() |
35 | 43 |
|
36 | | -# Chart configuration - optimize margins for tight layout |
37 | 44 | chart.options.chart = { |
38 | 45 | "width": 4800, |
39 | 46 | "height": 2700, |
40 | | - "backgroundColor": "#ffffff", |
| 47 | + "backgroundColor": PAGE_BG, |
41 | 48 | "marginBottom": 200, |
42 | | - "marginTop": 250, # More top margin to balance with title/subtitle |
| 49 | + "marginTop": 250, |
43 | 50 | "marginLeft": 150, |
44 | 51 | "marginRight": 150, |
45 | 52 | } |
46 | 53 |
|
47 | | -# Title |
48 | 54 | chart.options.title = { |
49 | | - "text": "rug-basic · highcharts · pyplots.ai", |
50 | | - "style": {"fontSize": "72px", "fontWeight": "bold"}, |
| 55 | + "text": "rug-basic · highcharts · anyplot.ai", |
| 56 | + "style": {"fontSize": "72px", "fontWeight": "bold", "color": INK}, |
51 | 57 | } |
52 | 58 |
|
53 | | -# Subtitle |
54 | | -chart.options.subtitle = {"text": "API Response Times (ms)", "style": {"fontSize": "48px"}} |
| 59 | +chart.options.subtitle = {"text": "API Response Times (ms)", "style": {"fontSize": "48px", "color": INK_SOFT}} |
55 | 60 |
|
56 | | -# X-axis - continuous scale for response times |
57 | | -# Removed grid lines per feedback - reduces visual clutter for rug plot |
58 | 61 | chart.options.x_axis = { |
59 | | - "title": {"text": "Response Time (ms)", "style": {"fontSize": "48px"}}, |
60 | | - "labels": {"style": {"fontSize": "36px"}}, |
61 | | - "gridLineWidth": 0, # No grid lines - cleaner look for rug plot |
| 62 | + "title": {"text": "Response Time (ms)", "style": {"fontSize": "48px", "color": INK}}, |
| 63 | + "labels": {"style": {"fontSize": "36px", "color": INK_SOFT}}, |
| 64 | + "gridLineWidth": 0, |
62 | 65 | "min": 0, |
63 | 66 | "max": 600, |
64 | 67 | "tickInterval": 50, |
65 | 68 | "lineWidth": 3, |
66 | | - "lineColor": "#333333", |
| 69 | + "lineColor": INK_SOFT, |
| 70 | + "tickColor": INK_SOFT, |
67 | 71 | } |
68 | 72 |
|
69 | | -# Y-axis - tight range to minimize whitespace (key fix!) |
70 | | -# Max set to 1.2 to leave minimal space above tick marks |
71 | 73 | chart.options.y_axis = { |
72 | 74 | "title": {"text": None}, |
73 | 75 | "labels": {"enabled": False}, |
74 | 76 | "gridLineWidth": 0, |
75 | 77 | "min": 0, |
76 | | - "max": 1.2, # Tight! Rug marks go to y=1, minimal whitespace above |
| 78 | + "max": 1.2, |
77 | 79 | "visible": False, |
78 | | - "plotLines": [{"value": 0, "width": 3, "color": "#333333", "zIndex": 2}], # Baseline |
| 80 | + "plotLines": [{"value": 0, "width": 3, "color": INK_SOFT, "zIndex": 2}], |
79 | 81 | } |
80 | 82 |
|
81 | | -# Legend and credits |
82 | 83 | chart.options.legend = {"enabled": False} |
83 | 84 | chart.options.credits = {"enabled": False} |
84 | | - |
85 | | -# Tooltip disabled for cleaner look |
86 | 85 | chart.options.tooltip = {"enabled": False} |
87 | 86 |
|
88 | | -# Add rug ticks as a single LineSeries with multiple line segments |
89 | | -# Use LineSeries with data containing multiple segments encoded as breaks |
90 | | -# Highcharts approach: Each rug tick is a very short vertical line |
91 | | -# We use one LineSeries per tick, but this is unavoidable in Highcharts |
92 | | -# without columnrange/dumbbell extensions |
93 | | - |
94 | | -# Create all rug ticks - vertical lines from y=0 to y=1 |
| 87 | +# Rug ticks: short vertical marks at axis edge (0 to 0.15 = 12.5% of y-range) |
95 | 88 | for v in sorted(values): |
96 | 89 | tick_series = LineSeries() |
97 | | - # Vertical line from baseline to tick height (fills most of vertical space now) |
98 | | - tick_series.data = [[float(v), 0], [float(v), 1]] |
99 | | - tick_series.color = "rgba(48, 105, 152, 0.6)" # Python Blue with transparency |
100 | | - tick_series.line_width = 5 # Visible but thin ticks |
| 90 | + tick_series.data = [[float(v), 0], [float(v), 0.15]] |
| 91 | + tick_series.color = TICK_COLOR |
| 92 | + tick_series.line_width = 7 |
101 | 93 | tick_series.marker = {"enabled": False} |
102 | 94 | tick_series.enable_mouse_tracking = False |
103 | 95 | tick_series.states = {"hover": {"enabled": False}} |
104 | 96 | tick_series.show_in_legend = False |
105 | 97 | chart.add_series(tick_series) |
106 | 98 |
|
107 | | -# Download Highcharts JS (required for headless Chrome) |
108 | | -highcharts_url = "https://code.highcharts.com/highcharts.js" |
| 99 | +# Download Highcharts JS (required for headless Chrome — CDN blocked on file://) |
| 100 | +highcharts_url = "https://cdn.jsdelivr.net/npm/highcharts@latest/highcharts.js" |
109 | 101 | with urllib.request.urlopen(highcharts_url, timeout=30) as response: |
110 | 102 | highcharts_js = response.read().decode("utf-8") |
111 | 103 |
|
112 | | -# Generate HTML with inline scripts |
113 | 104 | html_str = chart.to_js_literal() |
114 | 105 | html_content = f"""<!DOCTYPE html> |
115 | 106 | <html> |
|
118 | 109 | <script>{highcharts_js}</script> |
119 | 110 | <style> |
120 | 111 | * {{ margin: 0; padding: 0; box-sizing: border-box; }} |
121 | | - html, body {{ width: 4800px; height: 2700px; overflow: hidden; }} |
| 112 | + html, body {{ width: 4800px; height: 2700px; overflow: hidden; background: {PAGE_BG}; }} |
122 | 113 | #container {{ width: 4800px; height: 2700px; }} |
123 | 114 | </style> |
124 | 115 | </head> |
|
128 | 119 | </body> |
129 | 120 | </html>""" |
130 | 121 |
|
131 | | -# Write temp HTML and take screenshot |
| 122 | +# Save HTML artifact |
| 123 | +with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: |
| 124 | + f.write(html_content) |
| 125 | + |
| 126 | +# Screenshot via headless Chrome |
132 | 127 | with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f: |
133 | 128 | f.write(html_content) |
134 | 129 | temp_path = f.name |
|
144 | 139 | driver.get(f"file://{temp_path}") |
145 | 140 | time.sleep(5) |
146 | 141 |
|
147 | | -# Take screenshot of the container element for exact dimensions |
148 | 142 | container = driver.find_element("id", "container") |
149 | | -container.screenshot("plot.png") |
| 143 | +container.screenshot(f"plot-{THEME}.png") |
150 | 144 | driver.quit() |
151 | 145 |
|
152 | 146 | Path(temp_path).unlink() |
153 | | - |
154 | | -# Also save HTML for interactive version |
155 | | -with open("plot.html", "w", encoding="utf-8") as f: |
156 | | - interactive_html = f"""<!DOCTYPE html> |
157 | | -<html> |
158 | | -<head> |
159 | | - <meta charset="utf-8"> |
160 | | - <script src="https://code.highcharts.com/highcharts.js"></script> |
161 | | - <style> |
162 | | - * {{ margin: 0; padding: 0; box-sizing: border-box; }} |
163 | | - html, body {{ width: 100%; height: 100vh; overflow: hidden; }} |
164 | | - #container {{ width: 100%; height: 100%; }} |
165 | | - </style> |
166 | | -</head> |
167 | | -<body> |
168 | | - <div id="container"></div> |
169 | | - <script>{html_str}</script> |
170 | | -</body> |
171 | | -</html>""" |
172 | | - f.write(interactive_html) |
0 commit comments