Skip to content

Commit 49b8d57

Browse files
committed
feat(wardley): opt-in automatic label placement
Adds an opt-in `autoPlaceLabels` config flag to the wardley-beta diagram. When enabled, component/anchor/link/annotation labels are repositioned to avoid overlapping each other, node markers, pipeline boxes, the chart boundary and link lines, with leader lines for far-placed labels. A collision-free manual `label [x, y]` is kept as authored; pipeline child labels prefer placement underneath. Placement is a pure, deterministic, unit-tested module (wardleyLabelPlacement.ts); the renderer path is unchanged when the flag is off. Stacked on #7696 (wardley ecosystem/attitudes).
1 parent 5ed4ea6 commit 49b8d57

8 files changed

Lines changed: 1745 additions & 32 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'mermaid': minor
3+
---
4+
5+
feat(wardley): opt-in automatic label placement (`autoPlaceLabels`)
6+
7+
New opt-in `autoPlaceLabels` config flag for the `wardley-beta` diagram. When
8+
enabled, component, anchor, link and annotation labels are automatically
9+
repositioned to avoid overlapping each other, node markers, pipeline boxes,
10+
the chart boundary and link lines. Labels moved far from their node get a thin
11+
leader line; a collision-free manual `label [x, y]` is kept exactly as
12+
authored, and pipeline child labels prefer to sit underneath their node.
13+
14+
Off by default — existing maps render unchanged. Enable it via config:
15+
16+
```js
17+
mermaid.initialize({ 'wardley-beta': { autoPlaceLabels: true } });
18+
```

cypress/integration/rendering/wardley/wardley.spec.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,4 +304,84 @@ component Commodity Service [0.30, 0.85]
304304
{}
305305
);
306306
});
307+
308+
it('should render dense map labels without overlap when autoPlaceLabels is enabled', () => {
309+
// Seven long-named components packed into a tiny coordinate box. Without
310+
// auto-placement their default NE-offset labels collide into an unreadable
311+
// blob, so this map genuinely exercises the placement algorithm.
312+
imgSnapshotTest(
313+
`
314+
wardley-beta
315+
title Overlapping Label Stress Test
316+
size [1100, 800]
317+
318+
component Customer Service Portal [0.55, 0.62]
319+
component Customer Support Desk [0.58, 0.60]
320+
component Customer Records Store [0.52, 0.64]
321+
component Customer Data Platform [0.56, 0.585]
322+
component Account Management API [0.53, 0.59]
323+
component Billing And Invoicing [0.57, 0.635]
324+
component Identity Provider [0.545, 0.61]
325+
326+
Customer Service Portal -> Customer Data Platform
327+
Customer Support Desk -> Customer Records Store
328+
Account Management API -> Billing And Invoicing
329+
Identity Provider -> Customer Data Platform
330+
`,
331+
{ 'wardley-beta': { autoPlaceLabels: true } }
332+
);
333+
});
334+
335+
it('should keep collision-free manual labels when autoPlaceLabels is enabled', () => {
336+
// Three cases: `Kept Manual Label` is isolated with a manual label that
337+
// lands in clear space and has no link touching it -> kept untouched.
338+
// `Colliding Manual` has a manual label dropped onto a node cluster ->
339+
// re-placed. The remaining components are untuned -> auto-placed.
340+
imgSnapshotTest(
341+
`
342+
wardley-beta
343+
title Manual Label Mix
344+
size [1100, 800]
345+
346+
component Kept Manual Label [0.25, 0.30] label [20, -18]
347+
component Colliding Manual [0.55, 0.60] label [-90, 2]
348+
component Crowded Node A [0.52, 0.62]
349+
component Crowded Node B [0.56, 0.585]
350+
component Crowded Node C [0.53, 0.59]
351+
component Untuned Component [0.78, 0.40]
352+
353+
Colliding Manual -> Crowded Node B
354+
Crowded Node A -> Untuned Component
355+
`,
356+
{ 'wardley-beta': { autoPlaceLabels: true } }
357+
);
358+
});
359+
360+
it('should place pipeline child labels underneath when autoPlaceLabels is enabled', () => {
361+
// Pipeline child components have no manual `label [x,y]`, so they are
362+
// auto-placed; their preferred direction is straight down.
363+
imgSnapshotTest(
364+
`
365+
wardley-beta
366+
title Pipeline Autoplace
367+
size [1100, 800]
368+
369+
component Kettle [0.57, 0.45]
370+
component Power [0.10, 0.70]
371+
372+
Kettle -> Power
373+
374+
pipeline Kettle {
375+
component Campfire Kettle [0.30]
376+
component Electric Kettle [0.52]
377+
component Smart Kettle [0.74]
378+
}
379+
380+
Campfire Kettle -> Kettle
381+
Electric Kettle -> Kettle
382+
Smart Kettle -> Kettle
383+
`,
384+
{ 'wardley-beta': { autoPlaceLabels: true } }
385+
);
386+
});
307387
});

packages/mermaid/src/config.type.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1815,6 +1815,14 @@ export interface WardleyDiagramConfig extends BaseDiagramConfig {
18151815
* Whether to display a background grid.
18161816
*/
18171817
showGrid?: boolean;
1818+
/**
1819+
* When `true`, component, anchor, link, and annotation labels are
1820+
* automatically repositioned to avoid overlapping each other, node
1821+
* markers, the chart boundary, and link lines. Manual `label [x, y]`
1822+
* offsets are ignored while this is enabled.
1823+
*
1824+
*/
1825+
autoPlaceLabels?: boolean;
18181826
}
18191827
/**
18201828
* This interface was referenced by `MermaidConfig`'s JSON-Schema

packages/mermaid/src/diagrams/wardley/styles.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ export const styles: DiagramStylesProvider = ({
8484
.wardley-notes text {
8585
fill: ${w.axisTextColor};
8686
}
87+
.wardley-leader-line {
88+
stroke: ${w.annotationStroke};
89+
stroke-width: 1px;
90+
stroke-opacity: 0.6;
91+
fill: none;
92+
}
8793
`;
8894
};
8995

0 commit comments

Comments
 (0)