@zombie-mermaid/svg-renderer 4.2.3 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +15 -1
  2. package/dist/index.cjs +52 -52
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +23 -4
  5. package/dist/index.d.ts +23 -4
  6. package/dist/index.js +890 -850
  7. package/dist/index.js.map +1 -1
  8. package/package.json +14 -6
  9. package/src/c4/arial-widths.ts +0 -59
  10. package/src/c4/layout.ts +0 -680
  11. package/src/c4/metrics.ts +0 -261
  12. package/src/c4/renderer.ts +0 -366
  13. package/src/class/layout.ts +0 -525
  14. package/src/class/renderer.ts +0 -669
  15. package/src/edge-curves.ts +0 -205
  16. package/src/elk-instance.ts +0 -292
  17. package/src/er/layout.ts +0 -216
  18. package/src/er/renderer.ts +0 -516
  19. package/src/generated/mono-font-subset.ts +0 -32
  20. package/src/index.ts +0 -310
  21. package/src/layout-engine/back-edges.ts +0 -73
  22. package/src/layout-engine/compound-flat.ts +0 -916
  23. package/src/layout-engine/constants.ts +0 -19
  24. package/src/layout-engine/edge-bundling.ts +0 -458
  25. package/src/layout-engine/elk-adapter-utils.ts +0 -81
  26. package/src/layout-engine/elk-graph-builder.ts +0 -245
  27. package/src/layout-engine/from-elk.ts +0 -767
  28. package/src/layout-engine/inner-edges.ts +0 -329
  29. package/src/layout-engine/jog-straightening.ts +0 -52
  30. package/src/layout-engine/layer-alignment.ts +0 -174
  31. package/src/layout-engine/layout-hints.ts +0 -27
  32. package/src/layout-engine/to-elk.ts +0 -855
  33. package/src/layout-engine.ts +0 -115
  34. package/src/pie/layout.ts +0 -234
  35. package/src/pie/renderer.ts +0 -161
  36. package/src/registry.ts +0 -441
  37. package/src/renderer.ts +0 -1510
  38. package/src/resolve-colors.ts +0 -339
  39. package/src/sequence/layout.ts +0 -766
  40. package/src/sequence/renderer.ts +0 -687
  41. package/src/shape-clipping.ts +0 -197
  42. package/src/styles.ts +0 -145
  43. package/src/title-gaps.ts +0 -161
  44. package/src/xychart/layout.ts +0 -727
  45. package/src/xychart/renderer.ts +0 -676
@@ -1,329 +0,0 @@
1
- /**
2
- * Routes an edge between two members of a subgraph through the inside of its
3
- * box.
4
- *
5
- * `compound-flat.ts` keeps outsiders out of a box with invisible spine nodes in
6
- * the layers where the subgraph has no member. An edge from one member to
7
- * another that spans such a layer has to get past the spine, and ELK sends it
8
- * round the outside, which drags its end node, and so the box, sideways. These
9
- * edges are left out of the layout and drawn afterwards by `routeInnerEdge`,
10
- * which runs them down a channel between the members.
11
- *
12
- * Everything is worked out as (a, c): `a` along the flow, increasing with it,
13
- * and `c` across it. For `TD` that is (y, x); `BT` negates `a`, `LR` is (x, y).
14
- */
15
-
16
- import type { Direction, Point, PositionedNode } from '@zombie-mermaid/core'
17
-
18
- /** Room kept between a route and the nodes it passes, in px. */
19
- const CLEARANCE = 10
20
-
21
- /** Least distance between two routes that run side by side, in px. */
22
- const PARALLEL_GAP = 14
23
-
24
- /** How far an edge runs out of a node before turning, in px. */
25
- const STUB = 14
26
-
27
- /** Room kept between a route and the box's border, in px. */
28
- const BORDER_INSET = 10
29
-
30
- /** Cost of each bend, in px of extra length, so a straighter route wins. */
31
- const BEND_COST = 24
32
-
33
- /** Extra cost of leaving a decision by its bottom vertex, in px. */
34
- const DECISION_STEM = 400
35
-
36
- /** Shapes whose whole edge is a straight side an edge can leave from anywhere. */
37
- const FLAT_SIDED = new Set(['rectangle', 'rounded', 'subroutine'])
38
-
39
- interface Span {
40
- a0: number
41
- a1: number
42
- c0: number
43
- c1: number
44
- }
45
-
46
- interface Pt {
47
- a: number
48
- c: number
49
- }
50
-
51
- interface Frame {
52
- span: (box: BoxLike) => Span
53
- toPoint: (p: Pt) => Point
54
- }
55
-
56
- type BoxLike = { x: number; y: number; width: number; height: number }
57
-
58
- function frameFor(direction: Direction): Frame {
59
- const vertical =
60
- direction === 'TD' || direction === 'TB' || direction === 'BT'
61
- const flip = direction === 'BT' || direction === 'RL'
62
- const sign = flip ? -1 : 1
63
- return {
64
- span: (b) => {
65
- const [p0, p1] = vertical ? [b.y, b.y + b.height] : [b.x, b.x + b.width]
66
- const a0 = sign * p0
67
- const a1 = sign * p1
68
- return {
69
- a0: Math.min(a0, a1),
70
- a1: Math.max(a0, a1),
71
- c0: vertical ? b.x : b.y,
72
- c1: vertical ? b.x + b.width : b.y + b.height,
73
- }
74
- },
75
- toPoint: ({ a, c }) =>
76
- vertical ? { x: c, y: sign * a } : { x: sign * a, y: c },
77
- }
78
- }
79
-
80
- /** Whether the segment `p`-`q` (one axis only) passes through the inside of `s`. */
81
- function crosses(p: Pt, q: Pt, s: Span): boolean {
82
- const aLo = Math.min(p.a, q.a)
83
- const aHi = Math.max(p.a, q.a)
84
- const cLo = Math.min(p.c, q.c)
85
- const cHi = Math.max(p.c, q.c)
86
- return aHi > s.a0 && aLo < s.a1 && cHi > s.c0 && cLo < s.c1
87
- }
88
-
89
- const grow = (s: Span, by: number): Span => ({
90
- a0: s.a0 - by,
91
- a1: s.a1 + by,
92
- c0: s.c0 - by,
93
- c1: s.c1 + by,
94
- })
95
-
96
- /** The (a, c) of a point in `frame`. */
97
- function toPt(frame: Frame, p: Point): Pt {
98
- const s = frame.span({ x: p.x, y: p.y, width: 0, height: 0 })
99
- return { a: s.a0, c: s.c0 }
100
- }
101
-
102
- /** Whether the segment `p`-`q` runs beside a segment of `path`, closer than `PARALLEL_GAP`. */
103
- function runsAlong(p: Pt, q: Pt, path: readonly Pt[]): boolean {
104
- const flowwise = p.c === q.c
105
- for (let i = 1; i < path.length; i++) {
106
- const r = path[i - 1]!
107
- const t = path[i]!
108
- if ((r.c === t.c) !== flowwise) continue
109
- if (flowwise) {
110
- const apart = Math.abs(p.c - r.c)
111
- const shared =
112
- Math.min(Math.max(p.a, q.a), Math.max(r.a, t.a)) -
113
- Math.max(Math.min(p.a, q.a), Math.min(r.a, t.a))
114
- if (apart < PARALLEL_GAP && shared > 0) return true
115
- } else {
116
- const apart = Math.abs(p.a - r.a)
117
- const shared =
118
- Math.min(Math.max(p.c, q.c), Math.max(r.c, t.c)) -
119
- Math.max(Math.min(p.c, q.c), Math.min(r.c, t.c))
120
- if (apart < PARALLEL_GAP && shared > 0) return true
121
- }
122
- }
123
- return false
124
- }
125
-
126
- /** The path with repeated points and points in the middle of a straight run removed. */
127
- function simplify(path: Pt[]): Pt[] {
128
- const out: Pt[] = []
129
- for (const p of path) {
130
- const last = out[out.length - 1]
131
- if (last && last.a === p.a && last.c === p.c) continue
132
- out.push(p)
133
- }
134
- for (let i = out.length - 2; i > 0; i--) {
135
- const prev = out[i - 1]!
136
- const here = out[i]!
137
- const next = out[i + 1]!
138
- const straight =
139
- (prev.a === here.a && here.a === next.a) ||
140
- (prev.c === here.c && here.c === next.c)
141
- if (straight) out.splice(i, 1)
142
- }
143
- return out
144
- }
145
-
146
- const length = (path: Pt[]): number => {
147
- let total = 0
148
- for (let i = 1; i < path.length; i++) {
149
- total +=
150
- Math.abs(path[i]!.a - path[i - 1]!.a) +
151
- Math.abs(path[i]!.c - path[i - 1]!.c)
152
- }
153
- return total
154
- }
155
-
156
- /**
157
- * A route for the edge from `source` to `target`, both inside `bounds` (their
158
- * subgraph's box), kept clear of `others`. The cheapest of a straight line, a
159
- * Z for an edge that runs with the flow, and a hook out of the side of the
160
- * source and up the side of the target for one that runs against it. `undefined`
161
- * if nothing fits.
162
- */
163
- export function routeInnerEdge(
164
- source: PositionedNode,
165
- target: PositionedNode,
166
- others: readonly PositionedNode[],
167
- bounds: BoxLike,
168
- direction: Direction,
169
- drawn: readonly (readonly Point[])[] = [],
170
- ): Point[] | undefined {
171
- const frame = frameFor(direction)
172
- const S = frame.span(source)
173
- const T = frame.span(target)
174
- const forward = S.a1 <= T.a0
175
- const backward = T.a1 <= S.a0
176
- if (!forward && !backward) return undefined
177
-
178
- const inner = frame.span(bounds)
179
- const room: Span = {
180
- a0: inner.a0 + BORDER_INSET,
181
- a1: inner.a1 - BORDER_INSET,
182
- c0: inner.c0 + BORDER_INSET,
183
- c1: inner.c1 - BORDER_INSET,
184
- }
185
- const before = drawn.map((path) => path.map((p) => toPt(frame, p)))
186
- const blocks = others.map((n) => grow(frame.span(n), CLEARANCE))
187
- const mid = (s: Span): number => (s.c0 + s.c1) / 2
188
- const midA = (s: Span): number => (s.a0 + s.a1) / 2
189
- const flat = (n: PositionedNode): boolean => FLAT_SIDED.has(n.shape)
190
-
191
- /** Whether every segment is clear of the nodes, the ends touch only their own node, and it stays in the box. */
192
- const valid = (path: Pt[]): boolean => {
193
- for (const p of path) {
194
- if (p.a < room.a0 || p.a > room.a1 || p.c < room.c0 || p.c > room.c1) {
195
- return false
196
- }
197
- }
198
- for (let i = 1; i < path.length; i++) {
199
- const p = path[i - 1]!
200
- const q = path[i]!
201
- if (p.a !== q.a && p.c !== q.c) return false
202
- if (blocks.some((b) => crosses(p, q, b))) return false
203
- if (crosses(p, q, S) || crosses(p, q, T)) return false
204
- if (before.some((path) => runsAlong(p, q, path))) return false
205
- }
206
- return true
207
- }
208
-
209
- // Cross-flow positions worth trying: where the two nodes are, and just clear
210
- // of every node and the box's border.
211
- const candidates = new Set<number>([mid(S), mid(T), room.c0, room.c1])
212
- for (const b of blocks) {
213
- candidates.add(b.c0 - 1)
214
- candidates.add(b.c1 + 1)
215
- }
216
- for (const s of [S, T]) {
217
- candidates.add(s.c0 - CLEARANCE)
218
- candidates.add(s.c1 + CLEARANCE)
219
- }
220
- const within = (n: PositionedNode, s: Span, c: number): boolean =>
221
- flat(n) ? c >= s.c0 + 4 && c <= s.c1 - 4 : Math.abs(c - mid(s)) < 0.5
222
-
223
- const routes: Pt[][] = []
224
- if (forward) {
225
- const gap = (T.a0 - S.a1) / 2
226
- const a1 = S.a1 + Math.min(STUB, gap)
227
- const a2 = T.a0 - Math.min(STUB, gap)
228
- for (const c of candidates) {
229
- if (within(source, S, c) && within(target, T, c)) {
230
- routes.push([
231
- { a: S.a1, c },
232
- { a: T.a0, c },
233
- ])
234
- }
235
- // A Z, or an L-shaped pair of turns when one end can take the edge off centre.
236
- routes.push([
237
- { a: S.a1, c: mid(S) },
238
- { a: a1, c: mid(S) },
239
- { a: a1, c },
240
- { a: a2, c },
241
- { a: a2, c: mid(T) },
242
- { a: T.a0, c: mid(T) },
243
- ])
244
- if (within(target, T, c)) {
245
- routes.push([
246
- { a: S.a1, c: mid(S) },
247
- { a: a1, c: mid(S) },
248
- { a: a1, c },
249
- { a: T.a0, c },
250
- ])
251
- }
252
- if (within(source, S, c)) {
253
- routes.push([
254
- { a: S.a1, c },
255
- { a: a2, c },
256
- { a: a2, c: mid(T) },
257
- { a: T.a0, c: mid(T) },
258
- ])
259
- }
260
- // Out of the source's side and down: for a decision, whose other
261
- // branches leave by the bottom.
262
- const side = c >= S.c1 ? S.c1 : c <= S.c0 ? S.c0 : undefined
263
- if (side !== undefined) {
264
- const down = within(target, T, c)
265
- ? [{ a: T.a0, c }]
266
- : [
267
- { a: a2, c },
268
- { a: a2, c: mid(T) },
269
- { a: T.a0, c: mid(T) },
270
- ]
271
- routes.push([{ a: midA(S), c: side }, { a: midA(S), c }, ...down])
272
- }
273
- }
274
- } else {
275
- for (const c of candidates) {
276
- const side = c >= T.c1 ? T.c1 : c <= T.c0 ? T.c0 : undefined
277
- if (side === undefined) continue
278
- // Out of the source's back, up, and into the target's side.
279
- if (within(source, S, c)) {
280
- routes.push([
281
- { a: S.a0, c },
282
- { a: midA(T), c },
283
- { a: midA(T), c: side },
284
- ])
285
- }
286
- // Out of the source's side, along, up, and into the target's side.
287
- const out = side === T.c1 ? S.c1 : S.c0
288
- routes.push([
289
- { a: midA(S), c: out },
290
- { a: midA(S), c },
291
- { a: midA(T), c },
292
- { a: midA(T), c: side },
293
- ])
294
- }
295
- }
296
-
297
- let best: Pt[] | undefined
298
- let bestCost = Number.POSITIVE_INFINITY
299
- for (const route of routes) {
300
- const path = simplify(route)
301
- if (path.length < 2 || !valid(path)) continue
302
- // A decision's branches fan out of its sides; leaving by the bottom only
303
- // when nothing else fits.
304
- const stem =
305
- source.shape === 'diamond' && path[0]!.a === S.a1 ? DECISION_STEM : 0
306
- const cost = length(path) + BEND_COST * (path.length - 2) + stem
307
- if (cost < bestCost) {
308
- best = path
309
- bestCost = cost
310
- }
311
- }
312
- return best?.map(frame.toPoint)
313
- }
314
-
315
- /** The middle of a path's longest segment, where an edge label sits. */
316
- export function labelSpot(points: readonly Point[]): Point {
317
- let best = { x: points[0]!.x, y: points[0]!.y }
318
- let longest = -1
319
- for (let i = 1; i < points.length; i++) {
320
- const p = points[i - 1]!
321
- const q = points[i]!
322
- const len = Math.abs(q.x - p.x) + Math.abs(q.y - p.y)
323
- if (len > longest) {
324
- longest = len
325
- best = { x: (p.x + q.x) / 2, y: (p.y + q.y) / 2 }
326
- }
327
- }
328
- return best
329
- }
@@ -1,52 +0,0 @@
1
- /**
2
- * Straighten sub-pixel jogs in orthogonal edge routes.
3
- *
4
- * ELK spreads parallel edges across each node's side relative to that node's
5
- * own width, so two edges between differently sized, centered nodes leave and
6
- * arrive at slightly different offsets. The route then runs straight, steps
7
- * sideways by a fraction of a pixel, and runs straight again — a visible kink
8
- * that also tilts the arrowhead off the edge's axis (#1388).
9
- */
10
-
11
- import type { Point } from '@zombie-mermaid/core'
12
-
13
- /** Largest sideways step, in px, treated as an artifact rather than a bend. */
14
- const MAX_JOG = 1.5
15
-
16
- /** Slack for deciding two coordinates are "the same" run. */
17
- const EPS = 0.01
18
-
19
- /**
20
- * Collapse `run, tiny step, run` into one straight run along the shared axis.
21
- * Pattern (vertical form): A-B vertical, B-C horizontal with |dx| <= MAX_JOG,
22
- * C-D vertical. Returns a new array; the input is not mutated.
23
- */
24
- export function straightenTinyJogs(points: Point[]): Point[] {
25
- const out = points.map((p) => ({ ...p }))
26
- let i = 0
27
- while (i + 3 < out.length) {
28
- const [a, b, c, d] = [out[i]!, out[i + 1]!, out[i + 2]!, out[i + 3]!]
29
- const vertical =
30
- Math.abs(a.x - b.x) < EPS &&
31
- Math.abs(c.x - d.x) < EPS &&
32
- Math.abs(b.y - c.y) < EPS &&
33
- Math.abs(b.x - c.x) <= MAX_JOG &&
34
- Math.abs(b.x - c.x) >= EPS
35
- const horizontal =
36
- Math.abs(a.y - b.y) < EPS &&
37
- Math.abs(c.y - d.y) < EPS &&
38
- Math.abs(b.x - c.x) < EPS &&
39
- Math.abs(b.y - c.y) <= MAX_JOG &&
40
- Math.abs(b.y - c.y) >= EPS
41
- if (vertical) {
42
- const x = (a.x + d.x) / 2
43
- out.splice(i, 4, { x, y: a.y }, { x, y: d.y })
44
- } else if (horizontal) {
45
- const y = (a.y + d.y) / 2
46
- out.splice(i, 4, { x: a.x, y }, { x: d.x, y })
47
- } else {
48
- i++
49
- }
50
- }
51
- return out
52
- }
@@ -1,174 +0,0 @@
1
- /**
2
- * Layer alignment — snap same-layer nodes to a uniform position.
3
- * Split out of layout-engine.ts as a self-contained post-processing pass
4
- * over a PositionedGraph's nodes/edges.
5
- */
6
-
7
- import type {
8
- PositionedNode,
9
- PositionedEdge,
10
- Direction,
11
- } from '@zombie-mermaid/core'
12
- import { DEFAULTS } from './constants.ts'
13
-
14
- /**
15
- * ELK's orthogonal edge routing staggers nodes within the same layer to create
16
- * space for edge bends. This post-processing step groups nodes into layers and
17
- * snaps them to the same flow-axis coordinate (Y for TD/TB, X for LR/RL).
18
- *
19
- * Grouping uses proximity along the flow axis: within a layer, ELK's stagger
20
- * is always less than layerSpacing (bounded by edge routing channels), while
21
- * adjacent layers are separated by at least layerSpacing + nodeHeight.
22
- * A threshold of 0.75 * layerSpacing cleanly separates these cases.
23
- *
24
- * Directly connected nodes (sharing an edge) are never merged into the same
25
- * layer group as an additional safety check.
26
- *
27
- * Edge endpoints connected to shifted nodes are adjusted proportionally.
28
- * Intermediate bend points are left unchanged — edge bundling or clipping
29
- * will recalculate them afterwards.
30
- */
31
- // Audited for issue #100 (non-null assertions): every `!` in this file is
32
- // a bounds-checked array access, each backed by an explicit check earlier
33
- // in the same scope — `sorted[0]!` and the `sorted[i]!`/`sorted[i - 1]!`
34
- // loop-index accesses below are guaranteed by the `nodes.length === 0`
35
- // early return plus the `for (let i = 1; i < sorted.length; ...)` loop
36
- // bound; `edge.points[0]!`, `edge.points[1]!`, and the
37
- // `edge.points[edge.points.length - 1]!` / `[edge.points.length - 2]!`
38
- // pairs are guaranteed by the `edge.points.length < 2` early `continue`
39
- // (and, redundantly, the `edge.points.length > 1` checks right above each
40
- // access). This is the same idiom already accepted as justified elsewhere
41
- // in this codebase (see src/parser.ts, PR #158, and this directory's
42
- // edge-bundling.ts/from-elk.ts audit, PR #150, which didn't reach this
43
- // file). `noUncheckedIndexedAccess` can't see any of these guarantees,
44
- // but removing the `!` would only replace a proven-safe assertion with an
45
- // unreachable guard. Left as-is; no behavior change.
46
- export function alignLayerNodes(
47
- nodes: PositionedNode[],
48
- edges: PositionedEdge[],
49
- direction: Direction,
50
- ): void {
51
- if (nodes.length === 0) return
52
-
53
- const isHorizontal = direction === 'LR' || direction === 'RL'
54
-
55
- // Build set of directly-connected node pairs.
56
- // Nodes connected by an edge must not be merged into the same layer.
57
- const connectedPairs = new Set<string>()
58
- for (const edge of edges) {
59
- connectedPairs.add(`${edge.source}:${edge.target}`)
60
- connectedPairs.add(`${edge.target}:${edge.source}`)
61
- }
62
-
63
- // ELK's stagger creates small gaps between adjacent nodes in the same layer
64
- // (typically edgeEdge spacing = 12px per routing channel). Adjacent layers
65
- // are separated by at least layerSpacing (48px). We use single-linkage
66
- // clustering: a node joins the current layer if the gap from the previous
67
- // node (in sorted order) is within threshold, AND it has no direct edge to
68
- // any node already in the layer.
69
- const THRESHOLD = DEFAULTS.layerSpacing * 0.6
70
-
71
- // Sort nodes by flow-axis position
72
- const sorted = [...nodes].sort((a, b) =>
73
- isHorizontal ? a.x - b.x : a.y - b.y,
74
- )
75
-
76
- const layers: PositionedNode[][] = []
77
- let currentLayer: PositionedNode[] = [sorted[0]!]
78
-
79
- for (let i = 1; i < sorted.length; i++) {
80
- const pos = isHorizontal ? sorted[i]!.x : sorted[i]!.y
81
- const prevPos = isHorizontal ? sorted[i - 1]!.x : sorted[i - 1]!.y
82
- // Single-linkage: compare with previous node, not layer start
83
- const gap = pos - prevPos
84
- // Check if this node is connected to any node already in the current layer
85
- const hasEdgeToLayer = currentLayer.some((n) =>
86
- connectedPairs.has(`${n.id}:${sorted[i]!.id}`),
87
- )
88
- if (gap <= THRESHOLD && !hasEdgeToLayer) {
89
- currentLayer.push(sorted[i]!)
90
- } else {
91
- layers.push(currentLayer)
92
- currentLayer = [sorted[i]!]
93
- }
94
- }
95
- layers.push(currentLayer)
96
-
97
- // Snap each layer's nodes to the layer's center position
98
- const deltas = new Map<string, number>() // nodeId → shift amount
99
-
100
- for (const layer of layers) {
101
- if (layer.length <= 1) continue
102
-
103
- const positions = layer.map((n) => (isHorizontal ? n.x : n.y))
104
- const min = Math.min(...positions)
105
- const max = Math.max(...positions)
106
- if (max - min <= 1) continue // Already aligned
107
-
108
- // Use the center of the range as the snap target
109
- const target = (min + max) / 2
110
-
111
- for (const node of layer) {
112
- const oldPos = isHorizontal ? node.x : node.y
113
- const delta = target - oldPos
114
- if (Math.abs(delta) > 0.5) {
115
- if (isHorizontal) {
116
- node.x = target
117
- } else {
118
- node.y = target
119
- }
120
- deltas.set(node.id, delta)
121
- }
122
- }
123
- }
124
-
125
- if (deltas.size === 0) return
126
-
127
- // Adjust edge endpoints to match shifted node positions
128
- for (const edge of edges) {
129
- if (edge.points.length < 2) continue
130
-
131
- const srcDelta = deltas.get(edge.source)
132
- const tgtDelta = deltas.get(edge.target)
133
-
134
- if (srcDelta != null) {
135
- // Shift first point and any subsequent points in the initial vertical/horizontal run
136
- const first = edge.points[0]!
137
- if (isHorizontal) {
138
- first.x += srcDelta
139
- // Shift second point if it's part of a straight vertical exit
140
- if (
141
- edge.points.length > 1 &&
142
- edge.points[1]!.x === first.x - srcDelta
143
- ) {
144
- edge.points[1]!.x += srcDelta
145
- }
146
- } else {
147
- first.y += srcDelta
148
- if (
149
- edge.points.length > 1 &&
150
- edge.points[1]!.y === first.y - srcDelta
151
- ) {
152
- edge.points[1]!.y += srcDelta
153
- }
154
- }
155
- }
156
-
157
- if (tgtDelta != null) {
158
- const last = edge.points[edge.points.length - 1]!
159
- if (isHorizontal) {
160
- last.x += tgtDelta
161
- if (edge.points.length > 1) {
162
- const prev = edge.points[edge.points.length - 2]!
163
- if (prev.x === last.x - tgtDelta) prev.x += tgtDelta
164
- }
165
- } else {
166
- last.y += tgtDelta
167
- if (edge.points.length > 1) {
168
- const prev = edge.points[edge.points.length - 2]!
169
- if (prev.y === last.y - tgtDelta) prev.y += tgtDelta
170
- }
171
- }
172
- }
173
- }
174
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * Extra instructions a caller can give the layout pipeline for one run, for
3
- * things a `MermaidGraph` has no way to say. Used by the compound layout
4
- * (`compound-flat.ts`), which adds invisible helper nodes and edges to the
5
- * graph and has to size, order and weight them.
6
- */
7
- export interface LayoutHints {
8
- /** Node widths in px, used instead of the size worked out from the label. */
9
- fixedWidths?: ReadonlyMap<string, number>
10
- /**
11
- * Indices of edges that only shape the layout and are never drawn. They are
12
- * not straightened, so they can't drag the nodes they join into line.
13
- */
14
- looseEdges?: ReadonlySet<number>
15
- /**
16
- * Indices of real edges to leave out of the layout. The caller draws them
17
- * itself, so ELK doesn't route them round the helper nodes.
18
- */
19
- detachedEdges?: ReadonlySet<number>
20
- /**
21
- * The order a back-edge search starts from, when it must differ from the
22
- * graph's node order (which is also the order ELK is given the nodes in).
23
- */
24
- walkOrder?: readonly string[]
25
- /** Keep every layer's nodes in the order the graph lists them. */
26
- forceNodeOrder?: boolean
27
- }