diff --git a/README.md b/README.md index 95010bf..0484652 100644 --- a/README.md +++ b/README.md @@ -125,19 +125,20 @@ graphtty reads a simple JSON format: ## Benchmarks -graphtty uses a custom Sugiyama-style layout engine and optimized canvas operations for fast rendering. Benchmarks across all 9 sample graphs (50 iterations each, Python 3.11): +graphtty uses a custom Sugiyama-style layout engine and optimized canvas operations for fast rendering. Benchmarks across all 10 sample graphs (50 iterations each, Python 3.11): | Sample | Avg (ms) | Ops/sec | |---|---:|---:| -| react-agent (4 nodes) | 0.15 | 6,522 | -| deep-agent (7 nodes) | 0.31 | 3,161 | -| function-agent (8 nodes) | 0.36 | 2,806 | -| workflow-agent (11 nodes) | 0.42 | 2,370 | -| world-map (15 nodes) | 0.55 | 1,818 | -| supervisor-agent (7+subs) | 0.71 | 1,406 | -| rag-pipeline (10 nodes) | 0.74 | 1,347 | -| etl-pipeline (12 nodes) | 0.84 | 1,193 | -| code-review (8+subs) | 1.16 | 864 | +| react-agent (4 nodes) | 0.15 | 6,593 | +| book-writer-agent (6 nodes) | 0.25 | 4,083 | +| deep-agent (7 nodes) | 0.32 | 3,144 | +| function-agent (8 nodes) | 0.37 | 2,686 | +| workflow-agent (11 nodes) | 0.49 | 2,060 | +| world-map (15 nodes) | 0.61 | 1,651 | +| rag-pipeline (10 nodes) | 0.70 | 1,427 | +| supervisor-agent (7+subs) | 0.80 | 1,250 | +| etl-pipeline (12 nodes) | 0.82 | 1,225 | +| code-review (8+subs) | 1.21 | 829 | Run `python scripts/benchmark.py` to reproduce on your machine. diff --git a/pyproject.toml b/pyproject.toml index 31c6570..d33e1be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "graphtty" -version = "0.1.4" +version = "0.1.5" description = "Turn any directed graph into colored ASCII art for your terminal" readme = "README.md" license = "MIT" diff --git a/samples/book-writer-agent/graph.json b/samples/book-writer-agent/graph.json new file mode 100644 index 0000000..30a0497 --- /dev/null +++ b/samples/book-writer-agent/graph.json @@ -0,0 +1,67 @@ +{ + "nodes": [ + { + "id": "__start__", + "name": "__start__", + "type": "__start__", + "subgraph": null + }, + { + "id": "create_chapter_files", + "name": "create_chapter_files", + "type": "node", + "subgraph": null + }, + { + "id": "generate_book_outline", + "name": "generate_book_outline", + "type": "node", + "subgraph": null + }, + { + "id": "generate_chapters_content", + "name": "generate_chapters_content", + "type": "node", + "subgraph": null + }, + { + "id": "upload_chapter_files", + "name": "upload_chapter_files", + "type": "node", + "subgraph": null + }, + { + "id": "__end__", + "name": "__end__", + "type": "__end__", + "subgraph": null + } + ], + "edges": [ + { + "source": "create_chapter_files", + "target": "upload_chapter_files", + "label": "ChaptersFilesEvent" + }, + { + "source": "__start__", + "target": "generate_book_outline", + "label": "BookRequestEvent" + }, + { + "source": "generate_book_outline", + "target": "generate_chapters_content", + "label": "BookOutlineEvent" + }, + { + "source": "generate_chapters_content", + "target": "create_chapter_files", + "label": "ChaptersContentEvent" + }, + { + "source": "upload_chapter_files", + "target": "__end__", + "label": "BookCompleteEvent" + } + ] +} diff --git a/screenshots/book-writer-agent.png b/screenshots/book-writer-agent.png new file mode 100644 index 0000000..8794e6b Binary files /dev/null and b/screenshots/book-writer-agent.png differ diff --git a/screenshots/code-review.png b/screenshots/code-review.png index 627834e..45ee94d 100644 Binary files a/screenshots/code-review.png and b/screenshots/code-review.png differ diff --git a/screenshots/deep-agent.png b/screenshots/deep-agent.png index 711e05e..d70687c 100644 Binary files a/screenshots/deep-agent.png and b/screenshots/deep-agent.png differ diff --git a/screenshots/etl-pipeline.png b/screenshots/etl-pipeline.png index 494cbd4..bb18015 100644 Binary files a/screenshots/etl-pipeline.png and b/screenshots/etl-pipeline.png differ diff --git a/screenshots/function-agent.png b/screenshots/function-agent.png index 329565c..64c09ce 100644 Binary files a/screenshots/function-agent.png and b/screenshots/function-agent.png differ diff --git a/screenshots/rag-pipeline.png b/screenshots/rag-pipeline.png index 23da917..85f820d 100644 Binary files a/screenshots/rag-pipeline.png and b/screenshots/rag-pipeline.png differ diff --git a/screenshots/react-agent.png b/screenshots/react-agent.png index d9c4b88..2c8cf97 100644 Binary files a/screenshots/react-agent.png and b/screenshots/react-agent.png differ diff --git a/screenshots/supervisor-agent.png b/screenshots/supervisor-agent.png index 87800e5..1612ad3 100644 Binary files a/screenshots/supervisor-agent.png and b/screenshots/supervisor-agent.png differ diff --git a/screenshots/workflow-agent.png b/screenshots/workflow-agent.png index 41fe94f..deb16af 100644 Binary files a/screenshots/workflow-agent.png and b/screenshots/workflow-agent.png differ diff --git a/screenshots/world-map.png b/screenshots/world-map.png index a2c090b..3d327a4 100644 Binary files a/screenshots/world-map.png and b/screenshots/world-map.png differ diff --git a/scripts/generate_screenshots.py b/scripts/generate_screenshots.py index 80ff75f..821b90d 100644 --- a/scripts/generate_screenshots.py +++ b/scripts/generate_screenshots.py @@ -198,6 +198,11 @@ def main(): "monokai", "Function Agent (monokai)", ), + ( + "samples/book-writer-agent/graph.json", + "nord", + "Book Writer Agent (nord)", + ), ] images: list[tuple[str, Image.Image]] = [] diff --git a/src/graphtty/layout.py b/src/graphtty/layout.py index 74199d1..8a351b5 100644 --- a/src/graphtty/layout.py +++ b/src/graphtty/layout.py @@ -304,7 +304,7 @@ def _assign_coordinates( new_centers: dict[str, float] = {} for nid in order: w = node_sizes[nid][0] - ideal_x = int(desired[nid] - w / 2.0) + ideal_x = round(desired[nid]) - w // 2 x = max(ideal_x, cx) new_x[nid] = x new_centers[nid] = x + w / 2.0 @@ -346,7 +346,7 @@ def _assign_coordinates( continue desired_center = sum(child_cx) / len(child_cx) w = node_sizes[nid][0] - x = max(int(desired_center - w / 2.0), 0) + x = max(round(desired_center) - w // 2, 0) layer_x[li] = {nid: x} layer_centers[li] = {nid: x + w / 2.0} @@ -363,7 +363,7 @@ def _assign_coordinates( continue desired_center = sum(parent_cx) / len(parent_cx) w = node_sizes[nid][0] - x = max(int(desired_center - w / 2.0), 0) + x = max(round(desired_center) - w // 2, 0) layer_x[li] = {nid: x} layer_centers[li] = {nid: x + w / 2.0} diff --git a/src/graphtty/renderer.py b/src/graphtty/renderer.py index 7f93965..e59d8c9 100644 --- a/src/graphtty/renderer.py +++ b/src/graphtty/renderer.py @@ -175,7 +175,27 @@ def _do_render_canvas( ) corridor_map.update(fwd_map) extra_right = max(extra_right, fwd_extra) - max_x = max(b.x + b.w for b in boxes.values()) + extra_right + options.padding + # Account for edge labels that extend past the rightmost box + label_max_x = 0 + for edge in graph.edges: + if not edge.label: + continue + src_box = boxes.get(edge.source) + tgt_box = boxes.get(edge.target) + if src_box is None or tgt_box is None: + continue + # Straight forward edges: label at tgt_cx + 2 + if src_box.bottom < tgt_box.top: + cx = ( + tgt_box.cx + if abs(src_box.cx - tgt_box.cx) <= _STRAIGHT_TOLERANCE + else (src_box.cx + tgt_box.cx) // 2 + ) + label_right = cx + 2 + len(edge.label) + if label_right > label_max_x: + label_max_x = label_right + box_max_x = max(b.x + b.w for b in boxes.values()) + max_x = max(box_max_x + extra_right, label_max_x) + options.padding max_y = max(b.y + b.h for b in boxes.values()) + options.padding canvas = Canvas(max_x, max_y) diff --git a/uv.lock b/uv.lock index 41f566c..c603dc1 100644 --- a/uv.lock +++ b/uv.lock @@ -105,7 +105,7 @@ toml = [ [[package]] name = "graphtty" -version = "0.1.4" +version = "0.1.5" source = { editable = "." } [package.dev-dependencies]