From 789a820b1c67c4949e499366342343bf2af72f34 Mon Sep 17 00:00:00 2001 From: robby Date: Fri, 23 Oct 2026 10:45:46 -0400 Subject: [PATCH 1/4] Enable the bin extra now that d2-bin 0.9.0 is on PyPI Co-Authored-By: Claude Opus 5.5 --- examples/simple.py | 22 ------------------ examples/simple_sql_schema.py | 43 ----------------------------------- pyproject.toml | 5 ++-- uv.lock | 24 ++++++++++++++++++- 4 files changed, 25 insertions(+), 69 deletions(-) delete mode 100644 examples/simple.py delete mode 100644 examples/simple_sql_schema.py diff --git a/examples/simple.py b/examples/simple.py deleted file mode 100644 index f52d948..0000000 --- a/examples/simple.py +++ /dev/null @@ -1,22 +0,0 @@ -from pathlib import Path - -from d2.connection import Connection -from d2.diagram import Diagram -from d2.shape import Shape -from d2.style import Style - - -def main() -> None: - shapes = [ - Shape(name="shape_name1", style=Style(fill="red")), - Shape(name="shape_name2", style=Style(fill="blue")), - ] - connections = [Connection(shape_1="shape_name1", shape_2="shape_name2")] - - diagram = Diagram(shapes=shapes, connections=connections) - - Path("graph.d2").write_text(str(diagram), encoding="utf-8") - - -if __name__ == "__main__": - main() diff --git a/examples/simple_sql_schema.py b/examples/simple_sql_schema.py deleted file mode 100644 index f997f9d..0000000 --- a/examples/simple_sql_schema.py +++ /dev/null @@ -1,43 +0,0 @@ -from pathlib import Path - -from d2 import D2Error, render -from d2.diagram import Diagram -from d2.sql_table import SQLConstraint, SQLTable, create_foreign_key_connection - -FILE_NAME = "simple_sql_schema" - -# Create a new diagram -diagram = Diagram() - -# Create Users table -users = SQLTable("users") -users.add_field("id", "int", SQLConstraint.PRIMARY_KEY) -users.add_field("name", "varchar(255)") - -# Create Orders table -orders = SQLTable("orders") -orders.add_field("id", "int", SQLConstraint.PRIMARY_KEY) -orders.add_field("user_id", "int", SQLConstraint.FOREIGN_KEY) -orders.add_field("total", "decimal(10,2)") - -# Create connection -fk = create_foreign_key_connection("orders", "user_id", "users", "id") - -# Add tables and connections to the diagram -diagram.add_shape(users) -diagram.add_shape(orders) -diagram.add_connection(fk) - -# Write the diagram to a file -d2_path = Path(f"{FILE_NAME}.d2") -d2_path.write_text(str(diagram), encoding="utf-8") - -print(f"D2 diagram file created: {d2_path}") -print(str(diagram)) - -try: - svg_path = Path(f"{FILE_NAME}.svg") - svg_path.write_bytes(render(diagram, layout="elk")) - print(f"SVG diagram generated: {svg_path.resolve()}") -except (D2Error, FileNotFoundError) as e: - print(f"Error generating SVG: {e}") diff --git a/pyproject.toml b/pyproject.toml index 09ea4fa..de422dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,9 +36,8 @@ classifiers = [ "Typing :: Typed", ] -# Enable once d2-bin 0.9.0 is on PyPI (uv lock cannot resolve it before then): -# [project.optional-dependencies] -# bin = ["d2-bin==0.9.*"] +[project.optional-dependencies] +bin = ["d2-bin==0.9.*"] [project.urls] Repository = "https://github.com/h0rv/d2-python" diff --git a/uv.lock b/uv.lock index 7843f41..35f72d8 100644 --- a/uv.lock +++ b/uv.lock @@ -70,6 +70,19 @@ toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] +[[package]] +name = "d2-bin" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/aa/21dc6c5f8ef145809aabb89ad029ead453b8c491cc5f652292fc10d60729/d2_bin-0.9.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b7cea6b2022d6d7c4fe358ffd8b2be29abca389b3378346fb374f43b1f243953", size = 18094023, upload-time = "2026-10-23T14:41:56.732Z" }, + { url = "https://files.pythonhosted.org/packages/00/3f/15fa684e26c46957dbef38a7dbdb64362fb5b6d6097ee2573d6071e7e2fb/d2_bin-0.9.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e9a61b4f72ce46fc7c55ef681831767a3f33328416ed04f9db0382d8def68340", size = 16905960, upload-time = "2026-10-23T14:42:00.156Z" }, + { url = "https://files.pythonhosted.org/packages/1d/96/fbeaf9163fdd6b98ed45a113d221acc1121478880e5f3e9b1666f6bd58e0/d2_bin-0.9.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:ae3a6c06bee5b29ca85a6a38fdd19ac9b6b4b753958f5d290d69a7b696a24f3c", size = 16383517, upload-time = "2026-10-23T14:42:03.232Z" }, + { url = "https://files.pythonhosted.org/packages/51/d0/23c5373d79fb21818832173be73751a29a23ebeb89e246c9f215cad55b8d/d2_bin-0.9.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:bd87ecd963ce58d3c50a3be3b9931e630fc575323cf9a0c6b1c1af7bc25e2d0e", size = 17877389, upload-time = "2026-10-23T14:42:06.33Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e7/b41b55bec17c28d5ed4c520310fcc09d311397fd030bd3599deebcc7c40a/d2_bin-0.9.0-py3-none-win_amd64.whl", hash = "sha256:67e89438698da14a1a97d81f2061a3f5a281f78449073f7edb470ea7c7d8e9ac", size = 18105299, upload-time = "2026-10-23T14:42:09.453Z" }, + { url = "https://files.pythonhosted.org/packages/b9/51/46551ba410d6919d230cdb28d96795bd58158df6a34ece340b4ed38847db/d2_bin-0.9.0-py3-none-win_arm64.whl", hash = "sha256:1d653c49aa7696013eaac6d1aa4484c8f45f0386469d698dc0ca36747dd05043", size = 16484657, upload-time = "2026-10-23T14:42:12.853Z" }, +] + [[package]] name = "d2-python" version = "1.0.0" @@ -78,6 +91,11 @@ dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.12'" }, ] +[package.optional-dependencies] +bin = [ + { name = "d2-bin" }, +] + [package.dev-dependencies] dev = [ { name = "pytest" }, @@ -87,7 +105,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "typing-extensions", marker = "python_full_version < '3.12'", specifier = ">=4.5" }] +requires-dist = [ + { name = "d2-bin", marker = "extra == 'bin'", specifier = "==0.9.*" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'", specifier = ">=4.5" }, +] +provides-extras = ["bin"] [package.metadata.requires-dev] dev = [ From 7a55922e67f700d8e901bcb97c32f32e3ee7ee21 Mon Sep 17 00:00:00 2001 From: robby Date: Fri, 23 Oct 2026 10:45:46 -0400 Subject: [PATCH 2/4] Add one runnable example per feature and run them in CI Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yaml | 16 ++++++++++++++++ .gitignore | 4 ++++ README.md | 18 +++++++++++++----- examples/README.md | 27 +++++++++++++++++++++++++++ examples/ascii.py | 13 +++++++++++++ examples/basic.py | 18 ++++++++++++++++++ examples/boards.py | 31 +++++++++++++++++++++++++++++++ examples/classes.py | 23 +++++++++++++++++++++++ examples/config.py | 17 +++++++++++++++++ examples/connections.py | 34 ++++++++++++++++++++++++++++++++++ examples/containers.py | 29 +++++++++++++++++++++++++++++ examples/grids.py | 21 +++++++++++++++++++++ examples/shapes.py | 24 ++++++++++++++++++++++++ examples/sql_tables.py | 25 +++++++++++++++++++++++++ examples/styles.py | 33 +++++++++++++++++++++++++++++++++ examples/text_blocks.py | 24 ++++++++++++++++++++++++ examples/tooltips_and_links.py | 21 +++++++++++++++++++++ examples/uml_classes.py | 31 +++++++++++++++++++++++++++++++ 18 files changed, 404 insertions(+), 5 deletions(-) create mode 100644 examples/README.md create mode 100644 examples/ascii.py create mode 100644 examples/basic.py create mode 100644 examples/boards.py create mode 100644 examples/classes.py create mode 100644 examples/config.py create mode 100644 examples/connections.py create mode 100644 examples/containers.py create mode 100644 examples/grids.py create mode 100644 examples/shapes.py create mode 100644 examples/sql_tables.py create mode 100644 examples/styles.py create mode 100644 examples/text_blocks.py create mode 100644 examples/tooltips_and_links.py create mode 100644 examples/uml_classes.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 852d74d..3edc248 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,3 +32,19 @@ jobs: python-version: ${{ matrix.python-version }} - run: uv sync --locked - run: uv run pytest + + examples: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: astral-sh/setup-uv@v7 + - run: uv sync --locked --extra bin + - run: | + project="$PWD" + output="$(mktemp -d)" + cd "$output" + for example in "$project"/examples/*.py; do + echo "::group::$example" + "$project/.venv/bin/python" "$example" + echo "::endgroup::" + done diff --git a/.gitignore b/.gitignore index ca9fb53..a3bfc0b 100644 --- a/.gitignore +++ b/.gitignore @@ -147,3 +147,7 @@ _cache/ **/*/*test*.jpeg **/*/*test*.jpg **/*/*test*.png + +# example output +/examples/*.svg +/examples/*.d2 diff --git a/README.md b/README.md index a42ccad..c1d86ef 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,18 @@ This can be rendered using `d2 graph.d2 graph.svg && open graph.svg` or [https:/ ![example graph](/docs/images/d2.svg) +Pick a shape type with `shape=d2.ShapeType.` and add a tooltip with `tooltip=`: + +```python +db = d2.Shape( + name="db", + shape=d2.ShapeType.cylinder, + tooltip="Primary database", +) +``` + +A tooltip is shown when you hover the shape in the rendered SVG. Connections take `tooltip=` too. + ## Rendering `d2.render` runs the d2 command and returns the image as bytes. It uses the binary from `d2-bin` when that is installed, and otherwise looks for `d2` on your PATH. @@ -152,11 +164,7 @@ Use `layers`, `scenarios` and `steps` for other boards. Use `comments` (or `add_ ## Examples -See [`examples/`](examples/): - -```sh -uv run python examples/simple_sql_schema.py -``` +Runnable examples for every feature are in [`examples/`](examples/README.md). ## Development diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..86fd226 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,27 @@ +# Examples + +Each script builds one diagram and writes it next to where you run it. Install the d2 binary with the `bin` extra, then run any example: + +```bash +uv sync --extra bin +uv run python examples/shapes.py +``` + +| Example | What it shows | +| --- | --- | +| [basic.py](basic.py) | Shapes, labels and a connection | +| [shapes.py](shapes.py) | Shape types such as cylinder, queue and person, and icons | +| [containers.py](containers.py) | Shapes inside shapes, and connections to nested shapes with dotted paths | +| [styles.py](styles.py) | Fill, stroke, font, bold, opacity, 3d, shadow and border radius | +| [connections.py](connections.py) | Labels, directions, styles and arrowheads, including crow's foot | +| [tooltips_and_links.py](tooltips_and_links.py) | Tooltips on shapes and connections, and shape links | +| [text_blocks.py](text_blocks.py) | Markdown and code text inside a shape | +| [grids.py](grids.py) | Grid layout with columns and gap | +| [classes.py](classes.py) | Reusable style classes | +| [sql_tables.py](sql_tables.py) | SQL tables with constraints and a foreign key | +| [uml_classes.py](uml_classes.py) | UML class shapes with fields, methods and inheritance | +| [boards.py](boards.py) | Layers, scenarios and steps, written to a `.d2` file | +| [config.py](config.py) | Layout engine, theme, direction and sketch mode through `vars` | +| [ascii.py](ascii.py) | ASCII output printed with `d2.render(diagram, "txt")` | + +The `tala` layout engine is a separate install from d2, so the examples use `elk`. diff --git a/examples/ascii.py b/examples/ascii.py new file mode 100644 index 0000000..e1c4bf0 --- /dev/null +++ b/examples/ascii.py @@ -0,0 +1,13 @@ +import d2 + + +def main() -> None: + diagram = d2.Diagram( + shapes=[d2.Shape(name="a"), d2.Shape(name="b")], + connections=[d2.Connection("a", "b", "hello")], + ) + print(d2.render(diagram, "txt").decode()) + + +if __name__ == "__main__": + main() diff --git a/examples/basic.py b/examples/basic.py new file mode 100644 index 0000000..6436891 --- /dev/null +++ b/examples/basic.py @@ -0,0 +1,18 @@ +from pathlib import Path + +import d2 + + +def main() -> None: + diagram = d2.Diagram( + shapes=[ + d2.Shape(name="client", label="Web client"), + d2.Shape(name="server", label="API server"), + ], + connections=[d2.Connection("client", "server", "request")], + ) + Path("basic.svg").write_bytes(d2.render(diagram)) + + +if __name__ == "__main__": + main() diff --git a/examples/boards.py b/examples/boards.py new file mode 100644 index 0000000..d76d390 --- /dev/null +++ b/examples/boards.py @@ -0,0 +1,31 @@ +from pathlib import Path + +import d2 + + +def main() -> None: + detail = d2.Diagram( + shapes=[d2.Shape(name="api"), d2.Shape(name="db")], + connections=[d2.Connection("api", "db")], + ) + diagram = d2.Diagram( + shapes=[d2.Shape(name="system")], + layers=[d2.Layer(name="detail", diagram=detail)], + scenarios=[ + d2.Layer( + name="db_down", + diagram=d2.Diagram( + shapes=[d2.Shape(name="db", style=d2.Style(fill="red"))] + ), + ) + ], + steps=[ + d2.Layer(name="one", diagram=d2.Diagram(shapes=[d2.Shape(name="start")])), + d2.Layer(name="two", diagram=d2.Diagram(shapes=[d2.Shape(name="finish")])), + ], + ) + Path("boards.d2").write_text(str(diagram), encoding="utf-8") + + +if __name__ == "__main__": + main() diff --git a/examples/classes.py b/examples/classes.py new file mode 100644 index 0000000..9cc4e20 --- /dev/null +++ b/examples/classes.py @@ -0,0 +1,23 @@ +from pathlib import Path + +import d2 + + +def main() -> None: + diagram = d2.Diagram( + classes={ + "service": d2.Style(fill="lightblue", stroke="blue", border_radius="8"), + "store": d2.Style(fill="lightgreen", stroke="green"), + }, + shapes=[ + d2.Shape(name="api", classes=["service"]), + d2.Shape(name="worker", classes=["service"]), + d2.Shape(name="db", shape=d2.ShapeType.cylinder, classes=["store"]), + ], + connections=[d2.Connection("api", "db"), d2.Connection("worker", "db")], + ) + Path("classes.svg").write_bytes(d2.render(diagram)) + + +if __name__ == "__main__": + main() diff --git a/examples/config.py b/examples/config.py new file mode 100644 index 0000000..257bb4d --- /dev/null +++ b/examples/config.py @@ -0,0 +1,17 @@ +from pathlib import Path + +import d2 + + +def main() -> None: + diagram = d2.Diagram( + direction="right", + vars=d2.Config(layout_engine="elk", theme_id=4, sketch=True, pad=40), + shapes=[d2.Shape(name="a"), d2.Shape(name="b"), d2.Shape(name="c")], + connections=[d2.Connection("a", "b"), d2.Connection("b", "c")], + ) + Path("config.svg").write_bytes(d2.render(diagram)) + + +if __name__ == "__main__": + main() diff --git a/examples/connections.py b/examples/connections.py new file mode 100644 index 0000000..0b14f2a --- /dev/null +++ b/examples/connections.py @@ -0,0 +1,34 @@ +from pathlib import Path + +import d2 + + +def main() -> None: + diagram = d2.Diagram( + direction="right", + shapes=[d2.Shape(name=name) for name in ("a", "b", "c", "d", "e")], + connections=[ + d2.Connection("a", "b", "sends"), + d2.Connection("b", "a", direction=d2.Direction.FROM), + d2.Connection("a", "c", "both ways", direction=d2.Direction.BOTH), + d2.Connection("c", "d", direction=d2.Direction.NONE), + d2.Connection( + "d", + "e", + "styled", + style=d2.Style(stroke="red", stroke_dash=3, animated=True), + target_arrowhead=d2.Arrowhead(d2.ArrowheadShape.DIAMOND, filled=True), + ), + d2.Connection( + "e", + "a", + source_arrowhead=d2.Arrowhead(d2.ArrowheadShape.CF_ONE, label="1"), + target_arrowhead=d2.Arrowhead(d2.ArrowheadShape.CF_MANY, label="n"), + ), + ], + ) + Path("connections.svg").write_bytes(d2.render(diagram)) + + +if __name__ == "__main__": + main() diff --git a/examples/containers.py b/examples/containers.py new file mode 100644 index 0000000..bd24bcd --- /dev/null +++ b/examples/containers.py @@ -0,0 +1,29 @@ +from pathlib import Path + +import d2 + + +def main() -> None: + diagram = d2.Diagram( + shapes=[ + d2.Shape( + name="backend", + shapes=[ + d2.Shape(name="api"), + d2.Shape(name="worker"), + d2.Shape(name="cache", shapes=[d2.Shape(name="redis")]), + ], + connections=[d2.Connection("api", "worker", "enqueue")], + ), + d2.Shape(name="frontend"), + ], + connections=[ + d2.Connection("frontend", "backend.api", "HTTP"), + d2.Connection("backend.worker", "backend.cache.redis", "reads"), + ], + ) + Path("containers.svg").write_bytes(d2.render(diagram)) + + +if __name__ == "__main__": + main() diff --git a/examples/grids.py b/examples/grids.py new file mode 100644 index 0000000..ac4d442 --- /dev/null +++ b/examples/grids.py @@ -0,0 +1,21 @@ +from pathlib import Path + +import d2 + + +def main() -> None: + diagram = d2.Diagram( + shapes=[ + d2.Shape( + name="board", + grid_columns=3, + grid_gap=10, + shapes=[d2.Shape(name=f"cell{i}") for i in range(1, 7)], + ), + ], + ) + Path("grids.svg").write_bytes(d2.render(diagram)) + + +if __name__ == "__main__": + main() diff --git a/examples/shapes.py b/examples/shapes.py new file mode 100644 index 0000000..da8aeee --- /dev/null +++ b/examples/shapes.py @@ -0,0 +1,24 @@ +from pathlib import Path + +import d2 + +ICON = "https://icons.terrastruct.com/essentials%2F112-server.svg" + + +def main() -> None: + diagram = d2.Diagram( + direction="right", + shapes=[ + d2.Shape(name="db", label="Database", shape=d2.ShapeType.cylinder), + d2.Shape(name="jobs", shape=d2.ShapeType.queue), + d2.Shape(name="user", shape=d2.ShapeType.person), + d2.Shape(name="decision", shape=d2.ShapeType.diamond), + d2.Shape(name="internet", shape=d2.ShapeType.cloud), + d2.Shape(name="server", icon=ICON), + ], + ) + Path("shapes.svg").write_bytes(d2.render(diagram)) + + +if __name__ == "__main__": + main() diff --git a/examples/sql_tables.py b/examples/sql_tables.py new file mode 100644 index 0000000..2b3bdba --- /dev/null +++ b/examples/sql_tables.py @@ -0,0 +1,25 @@ +from pathlib import Path + +import d2 +from d2.sql_table import SQLConstraint, SQLTable, create_foreign_key_connection + + +def main() -> None: + users = SQLTable("users") + users.add_field("id", "int", SQLConstraint.PRIMARY_KEY) + users.add_field("email", "varchar(255)", SQLConstraint.UNIQUE) + + orders = SQLTable("orders") + orders.add_field("id", "int", SQLConstraint.PRIMARY_KEY) + orders.add_field("user_id", "int", SQLConstraint.FOREIGN_KEY) + orders.add_field("total", "decimal(10,2)") + + diagram = d2.Diagram( + shapes=[users, orders], + connections=[create_foreign_key_connection("orders", "user_id", "users", "id")], + ) + Path("sql_tables.svg").write_bytes(d2.render(diagram, layout="elk")) + + +if __name__ == "__main__": + main() diff --git a/examples/styles.py b/examples/styles.py new file mode 100644 index 0000000..54ef1a1 --- /dev/null +++ b/examples/styles.py @@ -0,0 +1,33 @@ +from pathlib import Path + +import d2 + + +def main() -> None: + diagram = d2.Diagram( + shapes=[ + d2.Shape( + name="colors", + style=d2.Style(fill="gold", stroke="brown", stroke_width=3), + ), + d2.Shape( + name="text", + style=d2.Style( + font="mono", + font_size=24, + font_color="white", + bold=True, + fill="navy", + ), + ), + d2.Shape(name="faded", style=d2.Style(fill="red", opacity=0.3)), + d2.Shape(name="block", style=d2.Style(three_d=True)), + d2.Shape(name="shadowed", style=d2.Style(shadow=True)), + d2.Shape(name="rounded", style=d2.Style(border_radius="12")), + ], + ) + Path("styles.svg").write_bytes(d2.render(diagram)) + + +if __name__ == "__main__": + main() diff --git a/examples/text_blocks.py b/examples/text_blocks.py new file mode 100644 index 0000000..920ed19 --- /dev/null +++ b/examples/text_blocks.py @@ -0,0 +1,24 @@ +from pathlib import Path + +import d2 + + +def main() -> None: + diagram = d2.Diagram( + shapes=[ + d2.Shape( + name="notes", + explanation=d2.Text("# Notes\n- first\n- **second**", "md"), + ), + d2.Shape( + name="snippet", + code=d2.Text('print("hello")', "python"), + ), + ], + connections=[d2.Connection("notes", "snippet")], + ) + Path("text_blocks.svg").write_bytes(d2.render(diagram)) + + +if __name__ == "__main__": + main() diff --git a/examples/tooltips_and_links.py b/examples/tooltips_and_links.py new file mode 100644 index 0000000..6dffd03 --- /dev/null +++ b/examples/tooltips_and_links.py @@ -0,0 +1,21 @@ +from pathlib import Path + +import d2 + + +def main() -> None: + diagram = d2.Diagram( + shapes=[ + d2.Shape(name="api", tooltip="Public REST API"), + d2.Shape(name="docs", link="https://d2lang.com"), + d2.Shape(name="db", shape=d2.ShapeType.cylinder), + ], + connections=[ + d2.Connection("api", "db", tooltip="Reads and writes over TCP"), + ], + ) + Path("tooltips_and_links.svg").write_bytes(d2.render(diagram)) + + +if __name__ == "__main__": + main() diff --git a/examples/uml_classes.py b/examples/uml_classes.py new file mode 100644 index 0000000..62867d9 --- /dev/null +++ b/examples/uml_classes.py @@ -0,0 +1,31 @@ +from pathlib import Path + +import d2 + + +def main() -> None: + animal = d2.ClassShape( + "Animal", + fields=[d2.ClassField("name", "string", "+")], + methods=[d2.ClassMethod("eat", "food string", "bool", "+")], + ) + dog = d2.ClassShape( + "Dog", + fields=[d2.ClassField("breed", "string", "-")], + methods=[d2.ClassMethod("bark", visibility="+")], + ) + diagram = d2.Diagram( + shapes=[animal, dog], + connections=[ + d2.Connection( + "Dog", + "Animal", + target_arrowhead=d2.Arrowhead(d2.ArrowheadShape.TRIANGLE), + ) + ], + ) + Path("uml_classes.svg").write_bytes(d2.render(diagram)) + + +if __name__ == "__main__": + main() From 5d42f1d83612a415e5a03db60273ef3aba90377b Mon Sep 17 00:00:00 2001 From: robby Date: Fri, 23 Oct 2026 10:48:23 -0400 Subject: [PATCH 3/4] Examples: use tala, a hex color, and render the boards example Co-Authored-By: Claude Opus 5.5 --- README.md | 4 ++-- examples/README.md | 1 - examples/boards.py | 3 ++- examples/config.py | 2 +- examples/styles.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c1d86ef..28449ab 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ with open("graph.svg", "wb") as f: print(d2.render(diagram, "txt").decode()) ``` -The format can be `"svg"` (the default), `"png"`, `"pdf"` or `"txt"`, which is ASCII art. You can also pass `layout="dagre" | "elk" | "tala"` and `theme=`. A source string works in place of a diagram. If d2 fails, `d2.D2Error` is raised with the message from d2. PNG and PDF make d2 download a headless browser the first time you use them. +The format can be `"svg"` (the default), `"png"`, `"pdf"` or `"txt"`, which is ASCII art. You can also pass `layout="dagre" | "elk" | "tala"` and `theme=`. For a diagram with layers, scenarios or steps, `target=` picks the board: the root by default, or a path such as `"layers.detail"`. A source string works in place of a diagram. If d2 fails, `d2.D2Error` is raised with the message from d2. PNG and PDF make d2 download a headless browser the first time you use them. See the [tests](/tests/test_d2) for more detailed usage examples. @@ -128,7 +128,7 @@ import d2 diagram = d2.Diagram( comments=["Generated by d2-python"], direction="right", - vars=d2.Config(layout_engine="elk", theme_id=4, sketch=True), + vars=d2.Config(layout_engine="tala", theme_id=4, sketch=True), classes={"hot": d2.Style(fill="orange", bold=True)}, shapes=[ d2.Shape(name="api", tooltip="Public API", width=200, classes=["hot"]), diff --git a/examples/README.md b/examples/README.md index 86fd226..939930c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -24,4 +24,3 @@ uv run python examples/shapes.py | [config.py](config.py) | Layout engine, theme, direction and sketch mode through `vars` | | [ascii.py](ascii.py) | ASCII output printed with `d2.render(diagram, "txt")` | -The `tala` layout engine is a separate install from d2, so the examples use `elk`. diff --git a/examples/boards.py b/examples/boards.py index d76d390..e7f3492 100644 --- a/examples/boards.py +++ b/examples/boards.py @@ -24,7 +24,8 @@ def main() -> None: d2.Layer(name="two", diagram=d2.Diagram(shapes=[d2.Shape(name="finish")])), ], ) - Path("boards.d2").write_text(str(diagram), encoding="utf-8") + Path("boards.svg").write_bytes(d2.render(diagram)) + Path("boards-detail.svg").write_bytes(d2.render(diagram, target="layers.detail")) if __name__ == "__main__": diff --git a/examples/config.py b/examples/config.py index 257bb4d..4877ed1 100644 --- a/examples/config.py +++ b/examples/config.py @@ -6,7 +6,7 @@ def main() -> None: diagram = d2.Diagram( direction="right", - vars=d2.Config(layout_engine="elk", theme_id=4, sketch=True, pad=40), + vars=d2.Config(layout_engine="tala", theme_id=4, sketch=True, pad=40), shapes=[d2.Shape(name="a"), d2.Shape(name="b"), d2.Shape(name="c")], connections=[d2.Connection("a", "b"), d2.Connection("b", "c")], ) diff --git a/examples/styles.py b/examples/styles.py index 54ef1a1..faffe2f 100644 --- a/examples/styles.py +++ b/examples/styles.py @@ -8,7 +8,7 @@ def main() -> None: shapes=[ d2.Shape( name="colors", - style=d2.Style(fill="gold", stroke="brown", stroke_width=3), + style=d2.Style(fill="#fde68a", stroke="#92400e", stroke_width=3), ), d2.Shape( name="text", From a7240b79977604e51605986ddc019a5e20ad7ed9 Mon Sep 17 00:00:00 2001 From: robby Date: Fri, 23 Oct 2026 10:48:36 -0400 Subject: [PATCH 4/4] examples README: boards renders now Co-Authored-By: Claude Opus 5.5 --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index 939930c..0d813a6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -20,7 +20,7 @@ uv run python examples/shapes.py | [classes.py](classes.py) | Reusable style classes | | [sql_tables.py](sql_tables.py) | SQL tables with constraints and a foreign key | | [uml_classes.py](uml_classes.py) | UML class shapes with fields, methods and inheritance | -| [boards.py](boards.py) | Layers, scenarios and steps, written to a `.d2` file | +| [boards.py](boards.py) | Layers, scenarios and steps, rendering the root board and one layer | | [config.py](config.py) | Layout engine, theme, direction and sketch mode through `vars` | | [ascii.py](ascii.py) | ASCII output printed with `d2.render(diagram, "txt")` |