|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | network-force-directed: Force-Directed Graph |
3 | | -Library: highcharts unknown | Python 3.13.11 |
4 | | -Quality: 91/100 | Created: 2025-12-23 |
| 3 | +Library: highcharts unknown | Python 3.14.4 |
| 4 | +Quality: 83/100 | Updated: 2026-04-26 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
7 | 8 | import random |
8 | 9 | import tempfile |
9 | 10 | import time |
|
14 | 15 | from selenium.webdriver.chrome.options import Options |
15 | 16 |
|
16 | 17 |
|
| 18 | +# Theme tokens |
| 19 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 20 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 21 | +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
| 22 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 23 | +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
| 24 | +GRID = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)" |
| 25 | +LABEL_OUTLINE = PAGE_BG |
| 26 | + |
17 | 27 | # Data - Social network with communities |
18 | 28 | random.seed(42) |
19 | 29 |
|
|
25 | 35 | "Design": ["Amy", "Ben", "Chloe", "Dan", "Emma", "Finn", "Gina", "Hugo"], |
26 | 36 | } |
27 | 37 |
|
28 | | -# Community colors (colorblind-safe) |
29 | | -community_colors = { |
30 | | - "Tech": "#306998", # Python Blue |
31 | | - "Marketing": "#FFD43B", # Python Yellow |
32 | | - "Finance": "#9467BD", # Purple |
33 | | - "Design": "#17BECF", # Cyan |
34 | | -} |
| 38 | +# Community colors — Okabe-Ito positions 1-4 |
| 39 | +community_colors = {"Tech": "#009E73", "Marketing": "#D55E00", "Finance": "#0072B2", "Design": "#CC79A7"} |
35 | 40 |
|
36 | 41 | # Build nodes with community info |
37 | 42 | nodes = [] |
|
70 | 75 | edges.append({"from": person1, "to": person2}) |
71 | 76 | added_edges.add(edge) |
72 | 77 |
|
73 | | -# Prepare series data |
74 | | -series_data = [] |
75 | | -for edge in edges: |
76 | | - series_data.append([edge["from"], edge["to"]]) |
77 | | - |
78 | | -# Create node configurations with larger markers |
| 78 | +# Create node configurations |
79 | 79 | node_configs = [] |
80 | 80 | for n in nodes: |
81 | | - node_configs.append( |
82 | | - {"id": n["id"], "color": n["color"], "marker": {"radius": 45}, "dataLabels": {"style": {"fontSize": "28px"}}} |
83 | | - ) |
| 81 | + node_configs.append({"id": n["id"], "color": n["color"], "marker": {"radius": 32}}) |
84 | 82 |
|
85 | 83 | # Download Highcharts JS and networkgraph module |
86 | | -highcharts_url = "https://code.highcharts.com/highcharts.js" |
87 | | -networkgraph_url = "https://code.highcharts.com/modules/networkgraph.js" |
| 84 | +highcharts_url = "https://cdnjs.cloudflare.com/ajax/libs/highcharts/11.4.8/highcharts.js" |
| 85 | +networkgraph_url = "https://cdnjs.cloudflare.com/ajax/libs/highcharts/11.4.8/modules/networkgraph.js" |
88 | 86 |
|
89 | 87 | with urllib.request.urlopen(highcharts_url, timeout=30) as response: |
90 | 88 | highcharts_js = response.read().decode("utf-8") |
|
95 | 93 | # Build chart configuration as raw JavaScript for better control |
96 | 94 | nodes_js = "[\n" |
97 | 95 | for n in node_configs: |
98 | | - nodes_js += f' {{id: "{n["id"]}", color: "{n["color"]}", marker: {{radius: 45}}}},\n' |
| 96 | + nodes_js += f' {{id: "{n["id"]}", color: "{n["color"]}", marker: {{radius: 32}}}},\n' |
99 | 97 | nodes_js += "]" |
100 | 98 |
|
101 | 99 | data_js = "[\n" |
|
109 | 107 | type: 'networkgraph', |
110 | 108 | width: 4800, |
111 | 109 | height: 2700, |
112 | | - backgroundColor: '#ffffff' |
| 110 | + backgroundColor: '{PAGE_BG}', |
| 111 | + style: {{color: '{INK}'}} |
113 | 112 | }}, |
114 | 113 | title: {{ |
115 | | - text: 'network-force-directed · highcharts · pyplots.ai', |
116 | | - style: {{fontSize: '56px', fontWeight: 'bold'}} |
| 114 | + text: 'network-force-directed · highcharts · anyplot.ai', |
| 115 | + style: {{fontSize: '56px', fontWeight: 'bold', color: '{INK}'}} |
117 | 116 | }}, |
118 | 117 | subtitle: {{ |
119 | | - text: 'Social Network with 4 Communities (Tech: Blue, Marketing: Yellow, Finance: Purple, Design: Cyan)', |
120 | | - style: {{fontSize: '32px', color: '#666666'}} |
| 118 | + useHTML: true, |
| 119 | + text: '<span style="font-size:32px;color:{INK_SOFT};">Communication ties across four departments — dense within, sparse between</span><br/>' |
| 120 | + + '<span style="font-size:30px;">' |
| 121 | + + '<span style="color:#009E73;">●</span> <span style="color:{INK};">Tech</span> ' |
| 122 | + + '<span style="color:#D55E00;">●</span> <span style="color:{INK};">Marketing</span> ' |
| 123 | + + '<span style="color:#0072B2;">●</span> <span style="color:{INK};">Finance</span> ' |
| 124 | + + '<span style="color:#CC79A7;">●</span> <span style="color:{INK};">Design</span>' |
| 125 | + + '</span>', |
| 126 | + style: {{fontSize: '32px', color: '{INK_SOFT}'}} |
121 | 127 | }}, |
| 128 | + legend: {{enabled: false}}, |
122 | 129 | plotOptions: {{ |
123 | 130 | networkgraph: {{ |
124 | 131 | layoutAlgorithm: {{ |
125 | 132 | enableSimulation: true, |
126 | 133 | friction: -0.9, |
127 | | - linkLength: 250, |
128 | | - gravitationalConstant: 0.02, |
| 134 | + linkLength: 650, |
| 135 | + gravitationalConstant: 0.06, |
129 | 136 | integration: 'verlet', |
130 | | - maxIterations: 1000 |
| 137 | + maxIterations: 1500 |
131 | 138 | }}, |
132 | 139 | dataLabels: {{ |
133 | 140 | enabled: true, |
134 | 141 | linkFormat: '', |
135 | 142 | style: {{ |
136 | 143 | fontSize: '26px', |
137 | 144 | fontWeight: 'bold', |
138 | | - textOutline: '3px white' |
| 145 | + color: '{INK}', |
| 146 | + textOutline: '3px {LABEL_OUTLINE}' |
139 | 147 | }} |
140 | 148 | }}, |
141 | 149 | link: {{ |
142 | 150 | width: 4, |
143 | | - color: '#999999' |
| 151 | + color: '{GRID}' |
144 | 152 | }} |
145 | 153 | }} |
146 | 154 | }}, |
|
161 | 169 | <script>{highcharts_js}</script> |
162 | 170 | <script>{networkgraph_js}</script> |
163 | 171 | </head> |
164 | | -<body style="margin:0; padding:0;"> |
| 172 | +<body style="margin:0; padding:0; background:{PAGE_BG};"> |
165 | 173 | <div id="container" style="width: 4800px; height: 2700px;"></div> |
166 | 174 | <script>{chart_js}</script> |
167 | 175 | </body> |
168 | 176 | </html>""" |
169 | 177 |
|
170 | 178 | # Save HTML file |
171 | | -with open("plot.html", "w", encoding="utf-8") as f: |
| 179 | +with open(f"plot-{THEME}.html", "w", encoding="utf-8") as f: |
172 | 180 | f.write(html_content) |
173 | 181 |
|
174 | 182 | # Take screenshot with Selenium |
|
186 | 194 | driver = webdriver.Chrome(options=chrome_options) |
187 | 195 | driver.get(f"file://{temp_path}") |
188 | 196 | time.sleep(12) # Wait longer for force simulation to stabilize |
189 | | -driver.save_screenshot("plot.png") |
| 197 | +driver.save_screenshot(f"plot-{THEME}.png") |
190 | 198 | driver.quit() |
191 | 199 |
|
192 | 200 | Path(temp_path).unlink() |
0 commit comments