Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
5cb5450
feat(grid): add internal placement model
timmy-wright Oct 1, 2026
682eb09
feat(grid): compute nested grid geometry
timmy-wright Oct 1, 2026
7c4bdf5
test(grid): cover placement and geometry engine
timmy-wright Oct 1, 2026
ccdcd75
[autofix.ci] apply automated fixes
autofix-ci[bot] Oct 1, 2026
d086fdf
docs(grid): explain placement engine invariants
timmy-wright Oct 1, 2026
5b455de
docs: clarify sparse grid track behavior
timmy-wright Oct 7, 2026
f03a523
refactor(grid): resolve placements once per container
timmy-wright Oct 7, 2026
d734de5
fix(grid): center content under wide titles
timmy-wright Oct 7, 2026
944d0a3
fix(grid): reject duplicate node ids
timmy-wright Oct 7, 2026
de0dbbb
fix(grid): report authored placement ids
timmy-wright Oct 7, 2026
3bef12b
perf(grid): advance automatic placement cursor
timmy-wright Oct 7, 2026
5a6fd51
perf(grid): advance partial placement cursors
timmy-wright Oct 7, 2026
55a0cf0
fix(grid): reject array configuration records
timmy-wright Oct 7, 2026
fe24c33
feat(grid): add sparse deterministic edge routing
timmy-wright Oct 1, 2026
dd9b210
test(grid): cover routing correctness and performance
timmy-wright Oct 1, 2026
b429402
docs(grid): explain routing algorithms
timmy-wright Oct 1, 2026
b9a901e
fix(grid): preserve routes when search caps trip
timmy-wright Oct 7, 2026
951e8b0
fix(grid): make routing demand keys delimiter-safe
timmy-wright Oct 7, 2026
26cc08f
fix(grid): keep endpoint ports within side bounds
timmy-wright Oct 7, 2026
bcd3a81
fix(grid): enforce interval memory caps during build
timmy-wright Oct 7, 2026
263b83c
perf(grid): avoid full search workspace resets
timmy-wright Oct 7, 2026
1d4ba86
docs(grid): clarify zero-area routing guard
timmy-wright Oct 7, 2026
af351c8
fix(grid): canonicalize route validation geometry
timmy-wright Oct 7, 2026
f5b19d7
test(grid): assert routing failure transactions
timmy-wright Oct 7, 2026
da5e3d4
test(grid): cover compatibility routing helpers
timmy-wright Oct 8, 2026
ab473f1
refactor(grid): remove ineffective bundle retry
timmy-wright Oct 8, 2026
5aaeb76
perf(grid): stop after bounded route candidates
timmy-wright Oct 8, 2026
4a73eac
fix(grid): correct coincident endpoint bend bound
timmy-wright Oct 8, 2026
ab57706
fix(grid): align portal planning clearance
timmy-wright Oct 8, 2026
e5f9d83
perf(grid): scope bundle checkpoint state
timmy-wright Oct 8, 2026
4bf6691
test(grid): allow coverage overhead for search case
timmy-wright Oct 8, 2026
2ada852
test(grid): consolidate route validity assertions
timmy-wright Oct 8, 2026
7c94598
refactor(grid): remove routing duplication
timmy-wright Oct 8, 2026
9d56b36
refactor(grid): type fallback container reasons
timmy-wright Oct 8, 2026
ef4211e
refactor(grid): consolidate sparse routing paths
timmy-wright Oct 8, 2026
fcd6cac
refactor(grid): share compatibility attachments
timmy-wright Oct 8, 2026
b8334b4
refactor(grid): internalize routing test options
timmy-wright Oct 8, 2026
500ec1a
docs(grid): clarify cumulative search budget
timmy-wright Oct 8, 2026
0357bbb
refactor(grid): derive adjacency cap
timmy-wright Oct 8, 2026
18cb0b1
fix(grid): keep unrelated edges off each other's corridors
timmy-wright Oct 8, 2026
45bd908
fix(grid): route unrelated edges to cross at a point, not run in para…
timmy-wright Oct 8, 2026
bad6fff
fix(grid): type search orientation ordinal to include no-orientation
timmy-wright Oct 8, 2026
c08ce9c
feat(grid): place labels and render routed edges
timmy-wright Oct 1, 2026
31572a9
test(grid): cover labels clipping and curves
timmy-wright Oct 1, 2026
ef59bc2
docs(grid): explain label and rendering pipeline
timmy-wright Oct 1, 2026
768a67f
fix(grid): clip ports along terminal axes
timmy-wright Oct 8, 2026
402839c
fix(grid): prevent self-overlapping label detours
timmy-wright Oct 8, 2026
d0a911c
refactor(grid): share label geometry policy
timmy-wright Oct 8, 2026
f7aa0a5
feat(grid): expand gaps for measured labels
timmy-wright Oct 8, 2026
a4dc5cb
feat(grid): size self-loops for labels
timmy-wright Oct 8, 2026
913a425
test(grid): instrument label-aware geometry
timmy-wright Oct 8, 2026
a4107e1
test(grid): cover label-aware routing fallbacks
timmy-wright Oct 8, 2026
950c6fb
docs(grid): document label-aware spacing
timmy-wright Oct 8, 2026
7463b80
remove
timmy-wright Oct 8, 2026
fba0081
fix(grid): scope label reroute lanes
timmy-wright Oct 8, 2026
6141846
fix(grid): tolerate degenerate label routes
timmy-wright Oct 8, 2026
e1dc879
fix(grid): clear stale label positions
timmy-wright Oct 8, 2026
19d030f
fix(grid): isolate label placement passes
timmy-wright Oct 8, 2026
8dc0fb1
test(grid): assert measured label routing work
timmy-wright Oct 8, 2026
719038d
refactor: remove edge corner radius re-export
timmy-wright Oct 8, 2026
3d18a0a
docs: explain edge corner radius clamping
timmy-wright Oct 8, 2026
2bc8df1
fix(grid): require label center anchors
timmy-wright Oct 8, 2026
af6c725
fix(grid): align label marker reservations
timmy-wright Oct 9, 2026
e2a0272
refactor(grid): share label detour axis logic
timmy-wright Oct 9, 2026
f6034f5
fix(grid): bound label index work
timmy-wright Oct 9, 2026
0dadff1
fix(grid): scope label center validation
timmy-wright Oct 9, 2026
b7f317d
lint:fix
timmy-wright Oct 9, 2026
9bb3223
fix(grid): reserve the direct endpoint side for aligned edges
timmy-wright Oct 8, 2026
b15d503
fix(grid): share an endpoint side with a straight edge when ports fit
timmy-wright Oct 8, 2026
8d77162
fix(grid): keep aligned edges on the fast path at shared endpoints
timmy-wright Oct 8, 2026
112201c
fix(grid): keep hierarchy edges off each other's corridors
timmy-wright Oct 8, 2026
f9e206f
fix(grid): line group portals up with the item ports they feed
timmy-wright Oct 8, 2026
f065ce8
fix(grid): avoid hierarchy routing doglegs
timmy-wright Oct 9, 2026
3beffae
fix(grid): reserve preferred hierarchy sides
timmy-wright Oct 9, 2026
9a68c26
fix edge
timmy-wright Oct 9, 2026
9391861
fix(grid): distribute ambiguous hierarchy routes
timmy-wright Oct 9, 2026
232f834
fix(grid): align ports with hierarchy routes
timmy-wright Oct 9, 2026
ff12e67
test(grid): cover labels clipping and curves
timmy-wright Oct 1, 2026
540adf1
feat(grid): expose configuration and placement metadata
timmy-wright Oct 1, 2026
bd4053c
test(grid): register layout and add browser coverage
timmy-wright Oct 1, 2026
d96dced
docs(grid): document and demonstrate grid layout
timmy-wright Oct 1, 2026
4c00ae3
docs(grid): explain public integration boundaries
timmy-wright Oct 1, 2026
637af3b
chore(grid): remove reconstruction artifacts
timmy-wright Oct 1, 2026
a65f1c2
update doc links
timmy-wright Oct 1, 2026
2b2b25b
test(grid): update rebased routing metrics
timmy-wright Oct 8, 2026
c931f75
test(grid): adapt rebased layout options
timmy-wright Oct 8, 2026
5af69fb
add labels to routes
timmy-wright Oct 8, 2026
5d5ad5c
simplify diagram
timmy-wright Oct 8, 2026
1804959
test(grid): add ordered Argos fixtures
timmy-wright Oct 8, 2026
454c114
fix(grid): remove stale label-instrumentation test duplicate
timmy-wright Oct 8, 2026
94b2fc2
fix(grid): break routerConstraint/routerPlanning/routerOccupancy impo…
timmy-wright Oct 8, 2026
68fc11d
[autofix.ci] apply automated fixes
autofix-ci[bot] Oct 8, 2026
69424b9
fix(rendering): restore swimlane endpoint clipping
timmy-wright Oct 9, 2026
7cc5e46
increase test timeout
timmy-wright Oct 9, 2026
cc183de
avoid symbol in layout
timmy-wright Oct 9, 2026
2ecdeff
fix(grid): validate placement coordinates during parse
timmy-wright Oct 9, 2026
785b4b0
add more diagrams
timmy-wright Oct 9, 2026
56242f5
more samples
timmy-wright Oct 9, 2026
d2f6c46
rename files
timmy-wright Oct 9, 2026
0fb2831
diagrams
timmy-wright Oct 9, 2026
ba16ddb
rename groups
timmy-wright Oct 9, 2026
d99598a
simplify diagram
timmy-wright Oct 9, 2026
3b9384d
test(grid): cover shape ports and edge curves
timmy-wright Oct 9, 2026
4ed13be
fix(grid): address review nits
timmy-wright Oct 9, 2026
38fcc40
docs(grid): add release version marker
timmy-wright Oct 9, 2026
289a7f2
docs(grid): clarify double-underscore placement ids
timmy-wright Oct 9, 2026
3ee2934
remove duplicate diagrams
timmy-wright Oct 9, 2026
e9474c5
feat(grid): support occurrence-indexed placements
timmy-wright Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/grid-layout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mermaid': minor
---

feat: add a grid layout with configurable row and column placement, recursive subgraphs, alignment, and orthogonal edge routing
24 changes: 20 additions & 4 deletions .esbuild/dev-explorer/diagram-viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,11 @@
| 'redux-dark'
| 'redux-color'
| 'redux-dark-color';
type MermaidLayout = 'dagre' | 'elk' | 'domus' | 'hola' | 'swimlane';
type MermaidLayout = 'dagre' | 'elk' | 'domus' | 'hola' | 'swimlane' | 'grid';
type MermaidLook = 'classic' | 'handDrawn' | 'neo';
type MermaidLogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';

const ALL_LAYOUTS: MermaidLayout[] = ['dagre', 'elk', 'domus', 'hola', 'swimlane'];
const ALL_LAYOUTS: MermaidLayout[] = ['dagre', 'elk', 'domus', 'hola', 'swimlane', 'grid'];

// Phases emitted by the profiler tree, in display order. "total" is taken from
// the root `render` span. See packages/mermaid/src/profiler.ts.
Expand Down Expand Up @@ -324,7 +324,14 @@
}

function isLayout(v: unknown): v is MermaidLayout {
return v === 'dagre' || v === 'elk' || v === 'domus' || v === 'hola' || v === 'swimlane';
return (
v === 'dagre' ||
v === 'elk' ||
v === 'domus' ||
v === 'hola' ||
v === 'swimlane' ||
v === 'grid'
);
}

function isLook(v: unknown): v is MermaidLook {
Expand All @@ -341,7 +348,16 @@
// Back-compat:
// - older UI used `renderer=dagre-d3|dagre-wrapper|elk`
// - new UI uses `layout=dagre|elk|domus`
if (v === 'dagre' || v === 'elk' || v === 'domus' || v === 'hola' || v === 'swimlane') return v;
if (
v === 'dagre' ||
v === 'elk' ||
v === 'domus' ||
v === 'hola' ||
v === 'swimlane' ||
v === 'grid'
) {
return v;
}
if (v === 'dagre-d3' || v === 'dagre-wrapper') return 'dagre';
return null;
}
Expand Down Expand Up @@ -722,14 +738,14 @@
this.#persistCompareState();
if (tab === 'code') {
void this.updateComplete.then(() => {
const editor = this.querySelector('dev-code-editor') as any;

Check warning on line 741 in .esbuild/dev-explorer/diagram-viewer.ts

View workflow job for this annotation

GitHub Actions / autofix

Unexpected any. Specify a different type
editor?.requestMeasure?.();
});
}
}

#syncConsolePanelFilters() {
const panel = this.querySelector('dev-console-panel') as any;

Check warning on line 748 in .esbuild/dev-explorer/diagram-viewer.ts

View workflow job for this annotation

GitHub Actions / autofix

Unexpected any. Specify a different type
if (!panel) return;
// This is intentionally opinionated: less noise by default as logLevel increases.
if (
Expand All @@ -755,7 +771,7 @@
}

#appendLog(entry: LogEntry) {
const panel = this.querySelector('dev-console-panel') as any;

Check warning on line 774 in .esbuild/dev-explorer/diagram-viewer.ts

View workflow job for this annotation

GitHub Actions / autofix

Unexpected any. Specify a different type
panel?.append?.(entry);
}

Expand Down Expand Up @@ -783,11 +799,11 @@
// Capture those too (map debug/info/log -> panel "info").
console.log = (...args) => {
capture('info', args);
this.#originalConsole!.log.apply(console, args as any);

Check warning on line 802 in .esbuild/dev-explorer/diagram-viewer.ts

View workflow job for this annotation

GitHub Actions / autofix

Unexpected any. Specify a different type
};
console.info = (...args) => {
capture('info', args);
this.#originalConsole!.info.apply(console, args as any);

Check warning on line 806 in .esbuild/dev-explorer/diagram-viewer.ts

View workflow job for this annotation

GitHub Actions / autofix

Unexpected any. Specify a different type
};
console.debug = (...args) => {
capture('info', args);
Expand Down
317 changes: 317 additions & 0 deletions demos/flowchart-grid.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Mermaid Flowchart GRID Test Page</title>
<style>
body {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
font-family: Arial, sans-serif;
}

.examples {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(520px, 1fr));
gap: 1.5rem;
}

section {
min-width: 0;
padding: 1rem;
border: 1px solid #ccc;
border-radius: 0.5rem;
}

section h2 {
margin-top: 0;
}

.mermaid {
overflow: auto;
}
</style>
</head>

<body>
<h1>Flowchart GRID</h1>
<p>
These examples exercise grid placement and the visible edge-routing behavior. Resize the
browser or edit the diagrams to inspect different route choices.
</p>

<main class="examples">
<section>
<h2>Explicit and automatic placement</h2>
<p>
Explicit cells are reserved first. Row-only, column-only, and unplaced nodes fill the
remaining cells deterministically.
</p>
<pre class="mermaid">
---
config:
layout: grid
grid:
columns: 3
rowGap: 45
columnGap: 55
---
flowchart TB
A["Explicit (1, 1)"]@{ row: 1, column: 1 }
B["Row 1"]@{ row: 1 }
C["Column 1"]@{ column: 1 }
D["Automatic"]
E["Explicit (3, 3)"]@{ row: 3, column: 3 }

A --> B
A --> C
B --> D
C --> D
D --> E
</pre>
</section>

<section>
<h2>Shared cells and alignment</h2>
<p>
Nodes assigned to the same explicit cell form a vertical stack. Horizontal alignment
applies to each item; vertical alignment applies to the whole stack.
</p>
<pre class="mermaid">
---
config:
layout: grid
grid:
cellGap: 18
rowGap: 55
columnGap: 80
---
flowchart TB
A["Wide top item"]@{ row: 1, column: 1, horizontalAlign: left, verticalAlign: center }
B["Middle"]@{ row: 1, column: 1, horizontalAlign: center, verticalAlign: center }
C["Right"]@{ row: 1, column: 1, horizontalAlign: right, verticalAlign: center }
D["Peer cell"]@{ row: 1, column: 2 }
E["Lower row"]@{ row: 2, column: 1 }

A --> B
B --> C
A --> D
C --> E
D --> E
</pre>
</section>

<section>
<h2>Nested groups and hierarchy routing</h2>
<p>
Child coordinates are local to their immediate group. Boundary-crossing edges avoid group
titles and use explicit entry and exit points.
</p>
<pre class="mermaid">
---
config:
layout: grid
grid:
placements:
Input: { row: 1, column: 1 }
Work: { row: 1, column: 2 }
Output: { row: 1, column: 3 }
A: { row: 1, column: 1 }
B: { row: 2, column: 1 }
C: { row: 1, column: 1 }
Nested: { row: 2, column: 1 }
D: { row: 1, column: 1 }
E: { row: 2, column: 1 }
F: { row: 1, column: 1 }
rowGap: 45
columnGap: 70
---
flowchart LR
subgraph Input["Input group"]
A["Receive"]
B["Validate"]
end

subgraph Work["Processing group"]
C["Dispatch"]
subgraph Nested["Nested group"]
D["Transform"]
E["Enrich"]
end
end

subgraph Output["Output group"]
F["Publish"]
end

A --> C
B --> D
C --> D
C --> E
D --> F
E --> F
</pre>
</section>

<section>
<h2>Parallel, reverse, and self-loop edges</h2>
<p>
Related edges use deterministic ports and lanes. Self-loops use separate ports on the same
node.
</p>
<pre class="mermaid">
---
config:
layout: grid
grid:
rowGap: 85
columnGap: 130
---
flowchart LR
A["Producer"]@{ row: 1, column: 1 }
B["Broker"]@{ row: 1, column: 2 }
C["Consumer"]@{ row: 1, column: 3 }
D["Retry queue"]@{ row: 2, column: 2 }

A -->|normal| B
A -->|priority| B
B -->|deliver| C
C -->|reject| B
B -->|retry| D
D -->|ready| B
D -->|wait| D
</pre>
</section>

<section>
<h2>Label-aware routing</h2>
<p>
Labels are placed after the first routing pass. Affected edges are rerouted around the
frozen label positions where space permits.
</p>
<pre class="mermaid">
---
config:
layout: grid
grid:
rowGap: 70
columnGap: 90
---
flowchart TB
A["Request"]@{ row: 1, column: 1 }
B{"Authorized?"}@{ row: 2, column: 2 }
C["Process"]@{ row: 3, column: 1 }
D["Reject"]@{ row: 3, column: 3 }
E["Audit"]@{ row: 4, column: 2 }

A -->|validate credentials| B
B -->|yes: continue processing| C
B -->|no: return an error| D
C -->|record successful request| E
D -->|record rejected request| E
A -->|capture request metadata| E
</pre>
</section>

<section>
<h2>Rounded orthogonal routes</h2>
<p>
The default rounded curve preserves the calculated orthogonal corridor while rounding its
corners.
</p>
<pre class="mermaid">
---
config:
layout: grid
grid:
curve: rounded
edgeCornerRadius: 12
rowGap: 65
columnGap: 85
---
flowchart TB
TL["Top left"]@{ row: 1, column: 1 }
TR["Top right"]@{ row: 1, column: 3 }
C["Center obstacle"]@{ row: 2, column: 2 }
BL["Bottom left"]@{ row: 3, column: 1 }
BR["Bottom right"]@{ row: 3, column: 3 }

TL -->|TL-BR| BR
TR -->|TR-BL| BL
TL -->|TL-C| C
C -->|C-BR| BR
</pre>
</section>

<section>
<h2>Linear orthogonal routes</h2>
<p>
The linear curve draws the same calculated route with square corners, which makes the
individual routing segments easy to inspect.
</p>
<pre class="mermaid">
---
config:
layout: grid
grid:
curve: linear
rowGap: 65
columnGap: 85
---
flowchart TB
TL["Top left"]@{ row: 1, column: 1 }
TR["Top right"]@{ row: 1, column: 3 }
C["Center obstacle"]@{ row: 2, column: 2 }
BL["Bottom left"]@{ row: 3, column: 1 }
BR["Bottom right"]@{ row: 3, column: 3 }

TL -->|TL-BR| BR
TR -->|TR-BL| BL
TL -->|TL-C| C
C -->|C-BR| BR
</pre>
</section>

<section>
<h2>Interpolated routes</h2>
<p>
Interpolating curves use the calculated route points but can move outside the orthogonal
corridor. Compare this example with the rounded and linear versions.
</p>
<pre class="mermaid">
---
config:
layout: grid
grid:
curve: basis
rowGap: 65
columnGap: 85
---
flowchart TB
TL["Top left"]@{ row: 1, column: 1 }
TR["Top right"]@{ row: 1, column: 3 }
C["Center obstacle"]@{ row: 2, column: 2 }
BL["Bottom left"]@{ row: 3, column: 1 }
BR["Bottom right"]@{ row: 3, column: 3 }

TL -->|TL-BR| BR
TR -->|TR-BL| BL
TL -->|TL-C| C
C -->|C-BR| BR
</pre>
</section>
</main>

<script type="module">
import mermaid from './mermaid.esm.mjs';

mermaid.initialize({
startOnLoad: true,
logLevel: 3,
securityLevel: 'loose',
});
</script>
</body>
</html>
3 changes: 3 additions & 0 deletions demos/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ <h2><a href="./flowchart.html">Flow charts</a></h2>
<li>
<h2><a href="./flowchart-elk.html">Flow charts ELK</a></h2>
</li>
<li>
<h2><a href="./flowchart-grid.html">Flow charts GRID</a></h2>
</li>
<li>
<h2>
<a href="./flowchart_expanded_node_shapes.html">Flowchart - expanded node shapes</a>
Expand Down
Loading
Loading