|
26 | 26 | position: absolute; |
27 | 27 | text-align: left; |
28 | 28 | width: 400px; |
| 29 | + max-width: 90vw; |
| 30 | + max-height: 60vh; |
| 31 | + overflow: auto; |
29 | 32 | padding: 10px; |
30 | 33 | font: 12px sans-serif; |
31 | 34 | background: #fff; |
|
35 | 38 | box-shadow: 2px 2px 8px #aaa; |
36 | 39 | z-index: 10; |
37 | 40 | } |
38 | | - pre { background: #f0f0f0; padding: 6px; border-radius: 4px; } |
| 41 | + pre { |
| 42 | + background: #f0f0f0; |
| 43 | + padding: 6px; |
| 44 | + border-radius: 4px; |
| 45 | + max-height: 200px; |
| 46 | + overflow: auto; |
| 47 | + white-space: pre; |
| 48 | + } |
39 | 49 | </style> |
40 | 50 | </head> |
41 | 51 | <body> |
|
95 | 105 | hideTooltip(true); |
96 | 106 | } |
97 | 107 |
|
| 108 | + function openInNewTab(event, d) { |
| 109 | + const url = `/program/${d.id}`; |
| 110 | + window.open(url, '_blank'); |
| 111 | + event.stopPropagation(); // Prevent tooltip from closing |
| 112 | + } |
| 113 | +
|
98 | 114 | function renderGraph(data) { |
99 | 115 | g.selectAll("*").remove(); |
100 | 116 | const simulation = d3.forceSimulation(data.nodes) |
|
120 | 136 | .attr("fill", d => d.island !== undefined ? d3.schemeCategory10[d.island % 10] : "#888") |
121 | 137 | .on("mouseover", showTooltip) |
122 | 138 | .on("click", showTooltipSticky) |
| 139 | + .on("dblclick", openInNewTab) |
123 | 140 | .on("mouseout", hideTooltip) |
124 | 141 | .call(d3.drag() |
125 | 142 | .on("start", dragstarted) |
|
164 | 181 | }); |
165 | 182 |
|
166 | 183 | function fetchAndRender() { |
167 | | - fetch('/data') |
| 184 | + fetch('/api/data') |
168 | 185 | .then(resp => resp.json()) |
169 | 186 | .then(data => { |
170 | 187 | const dataStr = JSON.stringify(data); |
|
189 | 206 | </body> |
190 | 207 | </html> |
191 | 208 | """ |
| 209 | +HTML_TEMPLATE_PROGRAM_PAGE = """ |
| 210 | + <!DOCTYPE html> |
| 211 | + <html lang="en"> |
| 212 | + <head> |
| 213 | + <meta charset="UTF-8"> |
| 214 | + <title>Program {{ program_data.id }}</title> |
| 215 | + <style> |
| 216 | + body { font-family: Arial, sans-serif; padding: 20px; } |
| 217 | + pre { background: #f0f0f0; padding: 10px; border-radius: 5px; } |
| 218 | + </style> |
| 219 | + </head> |
| 220 | + <body> |
| 221 | + <h1>Program ID: {{ program_data.id }}</h1> |
| 222 | + <h2>Island: {{ program_data.island }}</h2> |
| 223 | + <h3>Generation: {{ program_data.generation }}</h3> |
| 224 | + <h3>Parent ID: {{ program_data.parent_id or 'None' }}</h3> |
| 225 | + <h3>Metrics:</h3> |
| 226 | + <ul> |
| 227 | + {% for key, value in program_data.metrics.items() %} |
| 228 | + <li><strong>{{ key }}:</strong> {{ value }}</li> |
| 229 | + {% endfor %} |
| 230 | + </ul> |
| 231 | + <h3>Code:</h3> |
| 232 | + <pre>{{ program_data.code }}</pre> |
| 233 | + </body> |
| 234 | + </html> |
| 235 | +""" |
192 | 236 |
|
193 | 237 | logger = logging.getLogger("openevolve.visualizer") |
194 | 238 |
|
@@ -242,19 +286,41 @@ def load_evolution_data(checkpoint_folder): |
242 | 286 | def index(): |
243 | 287 | return render_template_string(HTML_TEMPLATE) |
244 | 288 |
|
245 | | -@app.route('/data') |
| 289 | +checkpoint_dir = None # Global variable to store the checkpoint directory |
| 290 | +@app.route('/api/data') |
246 | 291 | def data(): |
247 | 292 | base_folder = os.environ.get('EVOLVE_OUTPUT', 'examples/') |
248 | 293 | checkpoint = find_latest_checkpoint(base_folder) |
249 | 294 | if not checkpoint: |
250 | 295 | logger.info(f"No checkpoints found in {base_folder}") |
251 | 296 | return jsonify({"nodes": [], "edges": []}) |
252 | 297 |
|
| 298 | + # Memorize checkpoint directory for usage in other routes |
| 299 | + global checkpoint_dir |
| 300 | + checkpoint_dir = checkpoint |
| 301 | + |
253 | 302 | logger.info(f"Loading data from checkpoint: {checkpoint}") |
254 | 303 | data = load_evolution_data(checkpoint) |
255 | 304 | logger.debug(f"Data: {data}") |
256 | 305 | return jsonify(data) |
257 | 306 |
|
| 307 | + |
| 308 | +@app.route("/program/<program_id>") |
| 309 | +def program_page(program_id): |
| 310 | + global checkpoint_dir |
| 311 | + if checkpoint_dir is None: |
| 312 | + return "No checkpoint loaded", 500 |
| 313 | + |
| 314 | + program_path = os.path.join(checkpoint_dir, f"programs/{program_id}.json") |
| 315 | + if not os.path.exists(program_path): |
| 316 | + return "Program not found", 404 |
| 317 | + |
| 318 | + with open(program_path) as f: |
| 319 | + program_data = json.load(f) |
| 320 | + |
| 321 | + return render_template_string(HTML_TEMPLATE_PROGRAM_PAGE, program_data=program_data) |
| 322 | + |
| 323 | + |
258 | 324 | if __name__ == '__main__': |
259 | 325 | import argparse |
260 | 326 |
|
|
0 commit comments