{"slug": "adding-diagrams-to-my-static-site-generator-with-d2", "title": "Adding diagrams to my static site generator with D2", "summary": "Simon Willison added D2 diagram support to his static site generator, allowing him to compile D2 files into SVG diagrams for blog posts. The feature, implemented via a Python function that invokes the D2 CLI with ELK layout options, addresses his difficulty producing diagrams manually or with AI assistance.", "body_md": "## Adding diagrams to my static site generator with D2\n\nA lot of the time when I've been writing posts for this blog, I've felt that a diagram would really help. But they're a pain to produce well, and I think I underuse them as a result. I wanted to fix that, and wound up adding D2 support to my static site generator. I think it works pretty well!\n\nIn the past, I've tried drawing my own diagrams in LibreOffice and exporting as SVG, but my complete lack of artistic skill doesn't help:\n\nAsking an AI to do it for me helped in simple cases:\n\n...but with something less standard (there must be a million neural network diagrams in their training sets) it can be really fiddly to get something right.\n\nI did some investigations into the various diagram-generating tools out there, and\ndecided to give [D2](https://d2lang.com/) a go. It has a simple\nlanguage for specifying what your diagram should show, and the output is pretty nice:\n\nHere's the source for that diagram:\n\n``` php\nstyle.fill: transparent\n\ntokens: Tokens\ntokens -> llm.token-embeddings\n\nllm: \"\" {\n  token-embeddings: Token embeddings\n  position-embeddings: Position embeddings\n\n  plus: \"+\" {\n    shape: circle\n    width: 36\n    height: 36\n    style.font-size: 24\n    style.fill: transparent\n  }\n\n  token-embeddings -> plus\n  position-embeddings -> plus\n\n  input-embeddings: Input embeddings\n  plus -> input-embeddings\n\n  transformers-layers: \"\" {\n    style.stroke-dash: 3\n    style.fill: transparent\n    transformers-1: Transformers layer 1\n    transformers-2: Transformers layer 2\n    dots: \"⋮\" {shape: text; style.font-size: 28}\n    transformers-n: Transformers layer n\n    transformers-1 -> transformers-2 -> dots -> transformers-n\n  }\n  input-embeddings -> transformers-layers.transformers-1\n\n  final-norm: LayerNorm\n  transformers-layers.transformers-n -> final-norm\n\n  output-head: Output head\n  final-norm -> output-head\n}\n\noutput-logits: Logits\nllm.output-head -> output-logits\n```\n\nThat looks pretty clear to me!\n\nSo now, in the source for my blog posts, I have a `diagrams`\n\ndirectory. That contains\nsubdirectories -- by convention, I create one for each post that needs diagrams --\nand D2 files. These can be generated automatically when I publish:\n\n```\n    compile_d2_diagrams(input_path=Path(INPUT_DIR) / \"diagrams\", output_path=Path(NEW_OUTPUT_DIR) / \"diagrams\")\n\n...\n\ndef compile_d2_diagrams(input_path, output_path):\n    if input_path.is_file():\n        if not input_path.name.endswith(\".d2\"):\n            raise Exception(f\"Unknown file type in D2 tree: {input_path}\")\n        output_path = output_path.with_suffix(\".svg\")\n        print(f\"Compiling D2 diagram in {input_path} to {output_path}\")\n        subprocess.check_call([\n            \"d2\",\n            \"--pad=0\",\n            \"--layout=elk\",\n            \"--elk-nodeNodeBetweenLayers=30\",\n            \"--elk-padding=[top=20,left=20,bottom=20,right=20]\",\n            input_path,\n            output_path\n        ])\n        return\n    if input_path.is_dir():\n        print(f\"Making diagram directory to match {input_path}: {output_path}\")\n        output_path.mkdir()\n        for child_path in input_path.iterdir():\n            compile_d2_diagrams(child_path, output_path / child_path.name)\n        return\n    raise Exception(f\"Don't know what {input_path} is!\")\n```\n\n(Hat tip to [Evan Hahn](https://evanhahn.com/change-pathlib-path-extension-python/) for the `with_suffix`\n\nmethod on `Path`\n\n, which I wasn't aware of.)\n\nThe flags on the command line took a little bit of fiddling; the `--pad=0`\n\njust gets\nrid of the large margins that D2 puts around the diagram by default, but the others\nare to tell it to use the ELK layout package with particular formatting. Its default\nlayout has curvy lines, and I prefer the closer-to-right-angle ones that ELK provides.\n\nAnother awkward bit was in scaling; the file that is generated by that `d2`\n\ncommand\ncomes out pretty large ([you can see it full-size here](/diagrams/adding-d2/llm-top-level.svg)).\nBy default, I allow images inlined into my posts to be as wide as the text, but that would still be\ntoo large here.\n\nI use [ markdown2](https://github.com/trentm/python-markdown2) to convert the markdown\nsource for my posts into HTML, and there isn't any way to tell it what size an\nimage should be using markdown-ish syntax. So for now, instead of embedding images the normal markdown way, like\nthis:\n\n```\n![A simple neural network](/post-assets/neural-networks-maths/network.svg \"A simple neural network\")\n```\n\n...for these D2-generated ones I'll just embed a normal `<img>`\n\ntag like this:\n\n```\n<img src=\"/diagrams/adding-d2/llm-top-level.svg\" alt=\"A GPT-2-style LLM at the top level\" title=\"A GPT-2-style LLM at the top level\" style=\"width: 50%\">\n```\n\n...so that I can control the size. Perhaps more work needed there.\n\nAt some point I may go back and update my old diagrams -- at least, the really ugly hand-drawn ones -- to use this.\n\nAnd a random thought: perhaps it might also make sense to include the D2 source somehow on the blog? I can imagine that it could help with accessibility in some situations, and perhaps also for any LLMs stopping by. Will have to ponder that a bit more.\n\nWhat do you think? Does the D2 diagram look good to you? Or is there a better diagramming package that might work better?\n\n## Cite this post\n\n```\n@misc{thomas2026aug-adding-d2,\n  author       = {Thomas, Giles},\n  title        = {{Adding diagrams to my static site generator with D2}},\n  year         = {2026},\n  month        = aug,\n  howpublished = {Blog post},\n  url          = {https://www.gilesthomas.com/2026/08/adding-d2},\n}\n```\n\n", "url": "https://wpnews.pro/news/adding-diagrams-to-my-static-site-generator-with-d2", "canonical_source": "https://www.gilesthomas.com/2026/08/adding-d2", "published_at": "2026-08-25 22:00:23+00:00", "updated_at": "2026-08-25 22:16:05.774272+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Simon Willison", "D2", "Evan Hahn"], "alternates": {"html": "https://wpnews.pro/news/adding-diagrams-to-my-static-site-generator-with-d2", "markdown": "https://wpnews.pro/news/adding-diagrams-to-my-static-site-generator-with-d2.md", "text": "https://wpnews.pro/news/adding-diagrams-to-my-static-site-generator-with-d2.txt", "jsonld": "https://wpnews.pro/news/adding-diagrams-to-my-static-site-generator-with-d2.jsonld"}}