Add D2 output format - #64
Open
KTSCode wants to merge 13 commits into
Open
Conversation
Render schemas, embedded schemas, and clusters to D2 (d2lang.com) via the sql_table shape. Like DOT but using D2's ER-native features where they add information: crow's-foot cardinality and native primary/foreign key constraints. The :columns option accepts only [] or the default; :fontname is unsupported.
d2 suppresses a sql_table row's value when it equals the row key, so a field whose rendered type matches its name (an :id field of Ecto type :id) lost its type cell. Pad the type with a trailing space, invisible in the rendered SVG. Regenerate the demo SVG, which shipped the bug.
The static cluster_ prefix only moved the container-key collision: a
global node literally named cluster_Accounts still collides, failing
compilation ("sql_table columns cannot have children") or silently
merging bare nodes under columns: []. Prepend cluster_ until no
downcased container key matches a downcased global node id — d2 keys
fold case even when quoted.
Owner
|
Hi Kyle! |
fuelen
requested changes
Jul 28, 2026
fuelen
left a comment
Owner
There was a problem hiding this comment.
Great work! Please remove the asset, update the README accordingly, and we're good to go.
P.S: don't worry about examples/d2/ being empty. I see that the examples generator is broken for every format at the moment, unrelated to your PR.
Owner
There was a problem hiding this comment.
Could you please remove this demo file? I understand the point, and nothing is wrong with the image. DOT is the de facto default here and the README carries a single demo. How to present several formats is a separate question I want to think through on its own rather than settle in this PR.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add D2 output format
Adds D2 as a rich output format for
mix ecto.gen.erd,alongside DOT, PlantUML, DBML, QuickDBD, and Mermaid.
D2 is ER-native and renders to SVG without external runtime dependencies, so it
fills the gap between DOT/PlantUML (rich, but need Graphviz/Java) and
Mermaid/DBML (portable, but table-only).
What it renders
Everything DOT and PlantUML can show, plus more:
sql_tableshape<->connection, the primary-key side iscf-one-required(exactly one), the foreign-key sidecf-many-required(orcf-onefor has-one) ER-native, like PlantUML/Mermaidconstraint:badges, FK marking exceeds DOT, which marks none:map_node+Ecto.ERD.Node.set_cluster/2)#Enum<[...]>,#Ecto.Embedded<...>,{:array, _}(shares the DOT renderer's type formatter, so:enum_values_limit/:enum_values_orderwork identically)Example diagram (source):
Usage
Design
The guiding principle: mirror DOT (the library's reference rich format), and
deviate toward another format's behavior only where it yields more or better
information.
ER-native; DOT's plainness is a Graphviz limitation, not a style to copy.
tables have no clean affordance; D2's
sql_tabledoes, so it should.:columnsaccepts[](bare nodes) or the default[:name, :type],raising otherwise. Type-only or reordered subsets are impossible on
sql_table(rows are fixed name/type pairs); name-only bare rows wouldcompile in d2 but stay unsupported for parity with Mermaid. With
[], edgescollapse to node level and are deduped per node pair.
cluster_<name>andescalate the prefix when that would collide with a global node. Because D2
folds keys case-insensitively, case-only node or cluster collisions receive
distinct internal keys while retaining their original displayed names via
label:.:fontnameignored; D2 has no in-source font-family control (as withDBML/QuickDBD/Mermaid).
Notes
headless Chromium; SVG is native and dependency-free.
source-arrowheadon a->connection but renders both ends on
<->(verified empirically), so edgesuse the bidirectional operator with both ends marked.
sql_tabletype cell whenits text equals the row key, such as an
:idfield of type:id. Therenderer adds an invisible trailing space to the type text so the value
remains visible without changing the field key used by edge ports.
\,",${(variablesubstitution), and newlines, so hostile schema/source names can't break the
document or trigger d2 variable interpolation.
Testing
constraints, both-end crow's-foot cardinality, clusters, cluster/node key
collisions (including case-only node and cluster identities), equal
field-name/type rendering, string escaping, node-pair edge dedup with
columns: [], the:columnsfallback, and embedded-schema edges.columns: []collision cases compile cleanly withd2v0.7.1while preserving distinct displayed nodes and containers.
relationships, 16 enums, 11 embeds) all rendered, zero schemas skipped, and
the output compiled to SVG.