Measuring complexity¶
A diagram stops helping once a reader cannot hold it in mind. The complexity rules put a number on that point, so a review can say "split this" without an argument about taste.
Two measures¶
Node count is the number of nodes mermaid's parser reads from the diagram. What counts as a node depends on the diagram type, as What counts as a node lists.
Visual complexity score adds what nodes alone miss:
Edges add clutter, each subgraph adds a boundary to track, and each level of nesting makes all of it harder to follow.
A row is a line of text inside a node, such as an entity's attribute, and only an erDiagram or a classDiagram has rows.
For a mindmap, treemap, treeView or ishikawa, the nesting depth in the score is the tree's depth below its root.
The four weights are options: edgeWeight, rowWeight, subgraphWeight and depthWeight.
The score, one idea at a time¶
The steps below build the score from a single idea upwards. Each step shows a small diagram's source, then the structure mermaid's parser returned for it. That graph is not drawn from the source text. The site build runs the linter's own parse engine over each example, and draws the nodes, edges and subgraphs it read.
A subgraph is drawn as a dashed box around its members. The worked score under each graph is also computed in that build, with the default weights.
Nodes¶
Start with nodes and nothing else. Each node is one thing the reader must find and name, so each adds one point. Six unconnected nodes score 6.
The diagram as drawn
flowchart LR
A[Order]
B[Payment]
C[Stock]
D[Packing]
E[Shipping]
F[Receipt]
What the parser returned: 6 nodes, 0 edges, 0 subgraphs
depth = max(subgraph nesting 0, tree depth 0) = 0
score = (nodes + 0.5 × edges + 3 × subgraphs) × (1 + 0.1 × depth)
= (6 + 0.5 × 0 + 3 × 0) × (1 + 0.1 × 0)
= (6 + 0 + 0) × 1
= 6 × 1
= 6
Edges¶
Join the same six nodes with five edges. An edge is a line to trace, but it is lighter than a node, so each adds half a point. The diagram now scores 8.5.
Mermaid source
The diagram as drawn
flowchart LR
A[Order]
B[Payment]
C[Stock]
D[Packing]
E[Shipping]
F[Receipt]
A --> B
A --> C
B --> F
C --> D
D --> E
What the parser returned: 6 nodes, 5 edges, 0 subgraphs
depth = max(subgraph nesting 0, tree depth 0) = 0
score = (nodes + 0.5 × edges + 3 × subgraphs) × (1 + 0.1 × depth)
= (6 + 0.5 × 5 + 3 × 0) × (1 + 0.1 × 0)
= (6 + 2.5 + 0) × 1
= 8.5 × 1
= 8.5
Subgraphs¶
Group five of the nodes into two subgraphs, Billing and Warehouse. A subgraph is a boundary the reader must track, so each adds three points. Grouping also creates one level of nesting, which scales the whole sum by 1.1. The diagram now scores 15.95.
Mermaid source
The diagram as drawn
flowchart LR
A[Order]
subgraph billing [Billing]
B[Payment]
F[Receipt]
end
subgraph warehouse [Warehouse]
C[Stock]
D[Packing]
E[Shipping]
end
A --> B
A --> C
B --> F
C --> D
D --> E
What the parser returned: 6 nodes, 5 edges, 2 subgraphs
depth = max(subgraph nesting 1, tree depth 0) = 1
score = (nodes + 0.5 × edges + 3 × subgraphs) × (1 + 0.1 × depth)
= (6 + 0.5 × 5 + 3 × 2) × (1 + 0.1 × 1)
= (6 + 2.5 + 6) × 1.1
= 14.5 × 1.1
= 15.95
Nesting depth¶
Move Packing and Shipping into a Dispatch subgraph inside Warehouse. The extra subgraph adds three points, and the second level of nesting raises the scale to 1.2. Nesting multiplies the score rather than adding to it, because a deeper diagram makes every element harder to place. The diagram now scores 21.
Mermaid source
The diagram as drawn
flowchart LR
A[Order]
subgraph billing [Billing]
B[Payment]
F[Receipt]
end
subgraph warehouse [Warehouse]
C[Stock]
subgraph dispatch [Dispatch]
D[Packing]
E[Shipping]
end
end
A --> B
A --> C
B --> F
C --> D
D --> E
What the parser returned: 6 nodes, 5 edges, 3 subgraphs
depth = max(subgraph nesting 2, tree depth 0) = 2
score = (nodes + 0.5 × edges + 3 × subgraphs) × (1 + 0.1 × depth)
= (6 + 0.5 × 5 + 3 × 3) × (1 + 0.1 × 2)
= (6 + 2.5 + 9) × 1.2
= 17.5 × 1.2
= 21
At three levels, nesting also fires its own rule, as Each measure is a ladder explains.
Tree depth¶
A mindmap draws no subgraphs, but a deep tree is as hard to follow as deep nesting.
For a tree-shaped type, the depth in the score is how far the tree reaches below its root.
Every entry is a node, and every link from parent to child is an edge.
This mindmap reaches three levels below its root, so it scores 13.
The diagram as drawn
mindmap
root((Release))
Build
Compile
Test
Ship
Package
Sign
What the parser returned: 7 nodes, 6 edges, 0 subgraphs
depth = max(subgraph nesting 0, tree depth 3) = 3
score = (nodes + 0.5 × edges + 3 × subgraphs) × (1 + 0.1 × depth)
= (7 + 0.5 × 6 + 3 × 0) × (1 + 0.1 × 3)
= (7 + 3 + 0) × 1.3
= 10 × 1.3
= 13
The full score¶
A real diagram combines all of these. This shop architecture has more nodes, more edges and five subgraphs, nested two levels deep. It scores 45.
Mermaid source
flowchart TD
CUST[Customer]
subgraph web [Web]
UI[Storefront]
API[API gateway]
end
subgraph services [Services]
subgraph orders [Orders]
OS[Order service]
OQ[Order queue]
end
subgraph billing [Billing]
PS[Payment service]
INV[Invoices]
end
subgraph stock [Stock]
SS[Stock service]
WH[Warehouse feed]
end
end
DB[(Orders DB)]
PDB[(Payments DB)]
SDB[(Stock DB)]
MAIL[Email]
PSP[Card processor]
LOG[Audit log]
CUST --> UI
UI --> API
API --> OS
OS --> OQ
OQ --> PS
OQ --> SS
OS --> DB
PS --> PDB
PS --> PSP
PS --> INV
INV --> MAIL
SS --> SDB
WH --> SS
OS --> LOG
PS --> LOG
The diagram as drawn
flowchart TD
CUST[Customer]
subgraph web [Web]
UI[Storefront]
API[API gateway]
end
subgraph services [Services]
subgraph orders [Orders]
OS[Order service]
OQ[Order queue]
end
subgraph billing [Billing]
PS[Payment service]
INV[Invoices]
end
subgraph stock [Stock]
SS[Stock service]
WH[Warehouse feed]
end
end
DB[(Orders DB)]
PDB[(Payments DB)]
SDB[(Stock DB)]
MAIL[Email]
PSP[Card processor]
LOG[Audit log]
CUST --> UI
UI --> API
API --> OS
OS --> OQ
OQ --> PS
OQ --> SS
OS --> DB
PS --> PDB
PS --> PSP
PS --> INV
INV --> MAIL
SS --> SDB
WH --> SS
OS --> LOG
PS --> LOG
What the parser returned: 15 nodes, 15 edges, 5 subgraphs
depth = max(subgraph nesting 2, tree depth 0) = 2
score = (nodes + 0.5 × edges + 3 × subgraphs) × (1 + 0.1 × depth)
= (15 + 0.5 × 15 + 3 × 5) × (1 + 0.1 × 2)
= (15 + 7.5 + 15) × 1.2
= 37.5 × 1.2
= 45
The score on its own says nothing about whether 45 is too much. That depends on the audience, which is what a preset sets.
Presets set the thresholds¶
A preset is a named set of thresholds for one density of diagram.
The default is high-density, which follows Huang et al. 2020.
That study found that around 50 nodes is where a network diagram becomes hard to read, and that 100 is the limit.
| Option | low-density |
medium-density |
high-density |
|---|---|---|---|
nodeIdeal |
8 | 12 | 20 |
nodeAcceptable |
12 | 20 | 35 |
nodeComplex |
20 | 35 | 50 |
nodeHardLimit |
35 | 60 | 100 |
vcsIdeal |
15 | 25 | 35 |
vcsAcceptable |
25 | 40 | 60 |
vcsComplex |
40 | 70 | 100 |
vcsCritical |
60 | 100 | 150 |
nodeTarget |
10 | 15 | 25 |
vcsTarget |
20 | 30 | 40 |
Pick low-density or medium-density for audiences that read diagrams quickly, such as slides or onboarding docs.
Each measure is a ladder¶
The thresholds form bands, and a diagram lands in exactly one band per measure. Only the rule for that band fires, so one diagram never gets three findings for the same problem.
| Measure | Band | Rule |
|---|---|---|
| Nodes | above nodeAcceptable, up to nodeComplex |
complexity-node-count-acceptable-exceeded |
| Nodes | above nodeComplex, up to nodeHardLimit |
complexity-node-count-cognitive-limit |
| Nodes | above nodeHardLimit |
complexity-node-count-hard-limit |
| Score | above vcsAcceptable, up to vcsComplex |
complexity-visual-score-acceptable-exceeded |
| Score | above vcsComplex |
complexity-visual-score-critical |
nodeComplex, nodeHardLimit and vcsComplex each bound two neighbouring bands, so two rules read each of them. nodeAcceptable and vcsAcceptable bound only the lowest band, so one rule reads each.
The shop architecture from The full score lands in a different band under each preset. Each row below is a real run of the complexity rules over that diagram.
| Preset | Nodes | nodeAcceptable / nodeComplex / nodeHardLimit |
Score | vcsAcceptable / vcsComplex |
Rules that fire |
|---|---|---|---|---|---|
low-density |
15 | 12 / 20 / 35 | 45 | 25 / 40 | complexity-node-count-acceptable-exceededcomplexity-visual-score-critical |
medium-density |
15 | 20 / 35 / 60 | 45 | 40 / 70 | complexity-visual-score-acceptable-exceeded |
high-density |
15 | 35 / 50 / 100 | 45 | 60 / 100 | None |
Nesting has its own rule: subgraphs nested three or more levels deep fire complexity-subgraph-nesting-too-deep.
That limit is fixed at three, and no preset or option changes it.
The rule reads only real subgraphs, such as a flowchart subgraph or a composite state.
A deep mindmap, treemap, treeView or ishikawa draws no subgraphs, so the rule never fires on it.
Its depth counts only toward the score.
Some threshold options have no effect yet
The presets also define nodeIdeal, vcsIdeal and vcsCritical, and the config accepts them, but no rule reads them today.
complexity-visual-score-critical fires above vcsComplex, the complex threshold, and its message names that threshold.
Because each rule covers one band, set the preset for the whole ladder at once with --preset.
Setting preset or a threshold on one complexity rule in the config moves only that rule's band.
That can leave a gap between bands, or an overlap where two rules report the same diagram.
Rule settings shows a threshold set on both rules that read it.
What counts as a node¶
Each diagram type has its own unit, read from mermaid's parser result. src/parse/stats.ts holds one adapter per type, and it is the authority when this table and the code disagree.
| Diagram type | A node is | A subgraph is |
|---|---|---|
flowchart, flowchart-elk, graph |
A vertex, but not a subgraph's id used as an edge's end, since that edge runs to the subgraph | A subgraph |
classDiagram |
A class, whose attributes and methods are rows | A namespace, and one inside another adds nesting |
sequenceDiagram |
A participant or actor | A box, and a block such as loop, alt or par |
stateDiagram |
A state, including a composite state, a fork and a choice | A composite state, but not a concurrency region inside it |
erDiagram |
An entity, whose attributes are rows | None |
block |
A leaf block | A block that holds others |
C4 diagrams |
A shape, such as a person or system | A boundary |
requirementDiagram |
A requirement or an element | None |
gantt |
A task | A section |
journey |
A task | A section |
timeline |
A period, and each event under it | A section |
kanban |
A task | A column |
mindmap, ishikawa |
Every entry in the tree | None |
quadrantChart |
A plotted point, and a quadrant that has a label | None |
xychart |
An x position: a category, or a point of the longest series | None |
sankey |
A node | None |
venn |
A set | None |
pie |
A slice | None |
packet |
A block | None |
gitGraph |
A commit | A branch |
radar |
An axis or a curve | None |
treemap, treeView |
Every entry in the tree | None |
architecture-beta |
A service | A group |
wardley |
An anchor or a component | None |
info |
None | None |
A note is never counted, in any diagram type.
Sequence, entity and state diagrams¶
These three types draw something other than boxes joined by lines, so each counts its own parts. As in the steps above, each graph is what the parser returned, and each score is computed by the site build.
Sequence diagrams¶
A participant is a node, and each message from one participant to another is an edge.
A box around participants is a subgraph, and so is each block, such as loop, alt, opt or par.
A block inside another block adds a level of nesting.
An else divides its block rather than opening a new one.
A note, an activation and autonumber add nothing.
Neither does a rect, which tints the background behind its messages rather than drawing a boundary.
This checkout has three participants, six messages, a box and an alt block, so it scores 13.2.
Mermaid source
The diagram as drawn
sequenceDiagram
actor C as Customer
box Shop
participant W as Web
participant P as Payments
end
C->>W: Place order
activate W
W->>P: Charge card
alt approved
P-->>W: Receipt
W-->>C: Confirmation
else declined
P-->>W: Declined
W-->>C: Try again
end
deactivate W
Note over C,W: Notes are not counted
What the parser returned: 3 nodes, 6 edges, 2 subgraphs
depth = max(subgraph nesting 1, tree depth 0) = 1
score = (nodes + 0.5 × edges + 3 × subgraphs) × (1 + 0.1 × depth)
= (3 + 0.5 × 6 + 3 × 2) × (1 + 0.1 × 1)
= (3 + 3 + 6) × 1.1
= 12 × 1.1
= 13.2
A block frames messages rather than participants, so the graph draws it as an empty boundary.
Entity relationship diagrams¶
An entity is a node, and a relationship is an edge. Each attribute is a row, which adds a quarter of a point. A row is text to read, but it sits inside a box the reader has already found, so it weighs less than an edge. These four entities, three relationships and five attributes score 6.75.
Mermaid source
The diagram as drawn
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE_ITEM : contains
PRODUCT ||--o{ LINE_ITEM : "ordered in"
CUSTOMER {
string id PK
string name
string email
}
ORDER {
string id PK
date placed
}
What the parser returned: 4 nodes, 3 edges, 0 subgraphs, 5 rows
depth = max(subgraph nesting 0, tree depth 0) = 0
score = (nodes + 0.5 × edges + 0.25 × rows + 3 × subgraphs) × (1 + 0.1 × depth)
= (4 + 0.5 × 3 + 0.25 × 5 + 3 × 0) × (1 + 0.1 × 0)
= (4 + 1.5 + 1.25 + 0) × 1
= 6.75 × 1
= 6.75
State diagrams¶
A state is a node, and a transition between two states is an edge.
A composite state is a node and also a subgraph around the states inside it.
A -- line splits a composite state into concurrency regions, drawn as dashed-off areas of its box.
A region is not a state or a subgraph, and the states in it count inside the composite.
A start or end marker, [*], only marks where a path begins or ends.
It is not a state, and its transition is not an edge.
A fork and a choice are nodes, because each is a point where the path divides.
This order flow has five states, three transitions and one composite state, so it scores 10.45.
Mermaid source
The diagram as drawn
stateDiagram-v2
[*] --> Cart
Cart --> Checkout
state Checkout {
[*] --> Address
Address --> Payment
Payment --> [*]
}
Checkout --> Shipped
Shipped --> [*]
note right of Cart : Notes are not counted
What the parser returned: 5 nodes, 3 edges, 1 subgraphs
depth = max(subgraph nesting 1, tree depth 0) = 1
score = (nodes + 0.5 × edges + 3 × subgraphs) × (1 + 0.1 × depth)
= (5 + 0.5 × 3 + 3 × 1) × (1 + 0.1 × 1)
= (5 + 1.5 + 3) × 1.1
= 9.5 × 1.1
= 10.45
Why complexity is never fixed automatically¶
Splitting a diagram changes what it says. A finding names the existing subgraphs to split along and a target size, and a human decides where the seams go. When a split plan meets its contract, the finding also carries the plan: an overview and one detail diagram per group. The plan is still a recommendation, as Summarising a diagram explains.
Diagrams that cannot be measured¶
A diagram the parser cannot read has no trustworthy count.
It is reported as parse-diagram-unparseable, and no complexity rule is evaluated against it.