|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | span-basic: Basic Span Plot (Highlighted Region) |
3 | | -Library: highcharts unknown | Python 3.13.11 |
4 | | -Quality: 93/100 | Created: 2025-12-23 |
| 3 | +Library: highcharts unknown | Python 3.13.13 |
| 4 | +Quality: 90/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 |
|
16 | 17 | from selenium.webdriver.chrome.options import Options |
17 | 18 |
|
18 | 19 |
|
19 | | -# Data - Monthly stock price with recession period highlighted |
20 | | -months = [ |
21 | | - "Jan 2007", |
22 | | - "Apr 2007", |
23 | | - "Jul 2007", |
24 | | - "Oct 2007", |
25 | | - "Jan 2008", |
26 | | - "Apr 2008", |
27 | | - "Jul 2008", |
28 | | - "Oct 2008", |
29 | | - "Jan 2009", |
30 | | - "Apr 2009", |
31 | | - "Jul 2009", |
32 | | - "Oct 2009", |
33 | | - "Jan 2010", |
34 | | - "Apr 2010", |
35 | | - "Jul 2010", |
36 | | - "Oct 2010", |
| 20 | +# Theme tokens |
| 21 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 22 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 23 | +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
| 24 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 25 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 26 | +GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)" |
| 27 | +BRAND = "#009E73" # Okabe-Ito position 1 — always first series |
| 28 | + |
| 29 | +# Data - S&P 500 approximate quarterly closing values during the 2007-2010 financial crisis |
| 30 | +quarters = [ |
| 31 | + "Q1 2007", |
| 32 | + "Q2 2007", |
| 33 | + "Q3 2007", |
| 34 | + "Q4 2007", |
| 35 | + "Q1 2008", |
| 36 | + "Q2 2008", |
| 37 | + "Q3 2008", |
| 38 | + "Q4 2008", |
| 39 | + "Q1 2009", |
| 40 | + "Q2 2009", |
| 41 | + "Q3 2009", |
| 42 | + "Q4 2009", |
| 43 | + "Q1 2010", |
| 44 | + "Q2 2010", |
| 45 | + "Q3 2010", |
| 46 | + "Q4 2010", |
37 | 47 | ] |
38 | | -prices = [145, 152, 158, 155, 148, 135, 125, 95, 85, 78, 88, 105, 115, 122, 128, 135] |
| 48 | +sp500_values = [1421, 1503, 1527, 1468, 1323, 1280, 1166, 903, 798, 919, 1057, 1115, 1169, 1031, 1141, 1258] |
39 | 49 |
|
40 | 50 | # Create chart |
41 | 51 | chart = Chart(container="container") |
42 | 52 | chart.options = HighchartsOptions() |
43 | 53 |
|
44 | | -# Chart configuration |
45 | 54 | chart.options.chart = { |
46 | 55 | "type": "line", |
47 | 56 | "width": 4800, |
48 | 57 | "height": 2700, |
49 | | - "backgroundColor": "#ffffff", |
50 | | - "marginBottom": 200, |
51 | | - "spacingBottom": 20, |
| 58 | + "backgroundColor": PAGE_BG, |
| 59 | + "marginBottom": 220, |
| 60 | + "spacingTop": 40, |
52 | 61 | } |
53 | 62 |
|
54 | | -# Title |
55 | 63 | chart.options.title = { |
56 | | - "text": "span-basic · highcharts · pyplots.ai", |
57 | | - "style": {"fontSize": "72px", "fontWeight": "bold"}, |
| 64 | + "text": "span-basic · highcharts · anyplot.ai", |
| 65 | + "style": {"fontSize": "72px", "fontWeight": "bold", "color": INK}, |
58 | 66 | } |
59 | 67 |
|
60 | | -# Subtitle |
61 | | -chart.options.subtitle = {"text": "Stock Price with Recession Period Highlighted", "style": {"fontSize": "48px"}} |
| 68 | +chart.options.subtitle = { |
| 69 | + "text": "S&P 500 Index with Financial Crisis Period Highlighted", |
| 70 | + "style": {"fontSize": "48px", "color": INK_SOFT}, |
| 71 | +} |
62 | 72 |
|
63 | | -# X-axis with categories |
64 | 73 | chart.options.x_axis = { |
65 | | - "categories": months, |
66 | | - "title": {"text": "Date", "style": {"fontSize": "48px"}}, |
67 | | - "labels": {"style": {"fontSize": "36px"}}, |
68 | | - # Vertical span - highlight recession period (indices 4-8, Jan 2008 - Jan 2009) |
| 74 | + "categories": quarters, |
| 75 | + "title": {"text": "Quarter", "style": {"fontSize": "44px", "color": INK}, "margin": 20}, |
| 76 | + "labels": {"style": {"fontSize": "32px", "color": INK_SOFT}}, |
| 77 | + "lineColor": INK_SOFT, |
| 78 | + "tickColor": INK_SOFT, |
| 79 | + "gridLineColor": GRID, |
| 80 | + # Vertical span — highlight financial crisis (Q4 2007 through Q1 2009, indices 3–8) |
69 | 81 | "plotBands": [ |
70 | 82 | { |
71 | | - "from": 4, |
72 | | - "to": 8, |
73 | | - "color": "rgba(48, 105, 152, 0.25)", |
| 83 | + "from": 3.5, |
| 84 | + "to": 8.5, |
| 85 | + "color": "rgba(213,94,0,0.18)", |
74 | 86 | "label": { |
75 | | - "text": "Recession Period", |
76 | | - "style": {"fontSize": "42px", "color": "#306998", "fontWeight": "bold"}, |
| 87 | + "text": "Financial Crisis", |
| 88 | + "style": {"fontSize": "40px", "color": "#D55E00", "fontWeight": "bold"}, |
77 | 89 | "verticalAlign": "top", |
78 | | - "y": 60, |
| 90 | + "y": 80, |
79 | 91 | }, |
80 | 92 | } |
81 | 93 | ], |
82 | 94 | } |
83 | 95 |
|
84 | | -# Y-axis with horizontal span for threshold zone |
85 | 96 | chart.options.y_axis = { |
86 | | - "title": {"text": "Stock Price ($)", "style": {"fontSize": "48px"}}, |
87 | | - "labels": {"style": {"fontSize": "36px"}}, |
88 | | - "min": 50, |
89 | | - "max": 180, |
90 | | - # Horizontal span - highlight danger zone below $100 |
| 97 | + "title": {"text": "Index Value (Points)", "style": {"fontSize": "44px", "color": INK}}, |
| 98 | + "labels": {"style": {"fontSize": "36px", "color": INK_SOFT}}, |
| 99 | + "lineColor": INK_SOFT, |
| 100 | + "tickColor": INK_SOFT, |
| 101 | + "gridLineColor": GRID, |
| 102 | + "min": 600, |
| 103 | + "max": 1700, |
| 104 | + # Horizontal span — highlight bear market zone (below 1000 points) |
91 | 105 | "plotBands": [ |
92 | 106 | { |
93 | | - "from": 50, |
94 | | - "to": 100, |
95 | | - "color": "rgba(255, 212, 59, 0.25)", |
| 107 | + "from": 600, |
| 108 | + "to": 1000, |
| 109 | + "color": "rgba(230,159,0,0.18)", |
96 | 110 | "label": { |
97 | | - "text": "Below Target Price", |
98 | | - "style": {"fontSize": "36px", "color": "#B8860B"}, |
| 111 | + "text": "Bear Market Zone", |
| 112 | + "style": {"fontSize": "36px", "color": "#E69F00", "fontWeight": "500"}, |
99 | 113 | "align": "left", |
100 | | - "x": 50, |
| 114 | + "x": 40, |
| 115 | + "y": -15, |
101 | 116 | }, |
102 | 117 | } |
103 | 118 | ], |
104 | 119 | } |
105 | 120 |
|
106 | | -# Legend |
107 | | -chart.options.legend = {"enabled": True, "itemStyle": {"fontSize": "36px"}} |
| 121 | +chart.options.legend = { |
| 122 | + "enabled": True, |
| 123 | + "itemStyle": {"fontSize": "36px", "color": INK_SOFT, "fontWeight": "normal"}, |
| 124 | + "backgroundColor": ELEVATED_BG, |
| 125 | + "borderColor": INK_SOFT, |
| 126 | + "borderWidth": 1, |
| 127 | +} |
108 | 128 |
|
109 | | -# Plot options |
110 | 129 | chart.options.plot_options = {"line": {"lineWidth": 6, "marker": {"radius": 12, "enabled": True}}} |
111 | 130 |
|
112 | | -# Add line series |
113 | 131 | series = LineSeries() |
114 | | -series.name = "Stock Price" |
115 | | -series.data = prices |
116 | | -series.color = "#306998" |
117 | | -series.marker = {"fillColor": "#306998", "lineColor": "#306998", "lineWidth": 2} |
118 | | - |
| 132 | +series.name = "S&P 500" |
| 133 | +series.data = sp500_values |
| 134 | +series.color = BRAND |
| 135 | +series.marker = {"fillColor": BRAND, "lineColor": PAGE_BG, "lineWidth": 2} |
119 | 136 | chart.add_series(series) |
120 | 137 |
|
121 | 138 | # Download Highcharts JS for inline embedding |
122 | | -highcharts_url = "https://code.highcharts.com/highcharts.js" |
| 139 | +highcharts_url = "https://cdn.jsdelivr.net/npm/highcharts@latest/highcharts.js" |
123 | 140 | with urllib.request.urlopen(highcharts_url, timeout=30) as response: |
124 | 141 | highcharts_js = response.read().decode("utf-8") |
125 | 142 |
|
126 | | -# Generate HTML with inline scripts |
127 | 143 | html_str = chart.to_js_literal() |
128 | 144 | html_content = f"""<!DOCTYPE html> |
129 | 145 | <html> |
130 | 146 | <head> |
131 | 147 | <meta charset="utf-8"> |
132 | 148 | <script>{highcharts_js}</script> |
133 | 149 | </head> |
134 | | -<body style="margin:0;"> |
| 150 | +<body style="margin:0; background:{PAGE_BG};"> |
135 | 151 | <div id="container" style="width: 4800px; height: 2700px;"></div> |
136 | 152 | <script>{html_str}</script> |
137 | 153 | </body> |
138 | 154 | </html>""" |
139 | 155 |
|
140 | | -# Write temp HTML file |
| 156 | +with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: |
| 157 | + f.write(html_content) |
| 158 | + |
141 | 159 | with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f: |
142 | 160 | f.write(html_content) |
143 | 161 | temp_path = f.name |
144 | 162 |
|
145 | | -# Take screenshot with headless Chrome |
146 | 163 | chrome_options = Options() |
147 | 164 | chrome_options.add_argument("--headless") |
148 | 165 | chrome_options.add_argument("--no-sandbox") |
|
153 | 170 | driver = webdriver.Chrome(options=chrome_options) |
154 | 171 | driver.get(f"file://{temp_path}") |
155 | 172 | time.sleep(5) |
156 | | -driver.save_screenshot("plot.png") |
| 173 | +driver.save_screenshot(f"plot-{THEME}.png") |
157 | 174 | driver.quit() |
158 | 175 |
|
159 | | -# Save HTML file for interactive viewing |
160 | | -Path("plot.html").write_text(html_content, encoding="utf-8") |
161 | | - |
162 | | -# Clean up temp file |
163 | 176 | Path(temp_path).unlink() |
0 commit comments