flowfig 0.9.0 → 0.10.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.
- package/README.md +305 -59
- package/dist/atlas.d.ts +6 -0
- package/dist/atlas.js +122 -0
- package/dist/auto.d.ts +3 -0
- package/dist/auto.js +302 -0
- package/dist/browser.js +3 -1
- package/dist/check.js +54 -17
- package/dist/cli.js +22 -5
- package/dist/coverage.js +10 -7
- package/dist/diff.d.ts +10 -0
- package/dist/diff.js +137 -7
- package/dist/geometry.d.ts +4 -0
- package/dist/geometry.js +45 -17
- package/dist/index.d.ts +1 -1
- package/dist/index.js +149 -79
- package/dist/load.js +4 -3
- package/dist/mermaid.d.ts +19 -0
- package/dist/mermaid.js +617 -0
- package/dist/model.d.ts +11 -3
- package/dist/model.js +25 -1
- package/dist/open.d.ts +1 -0
- package/dist/open.js +11 -8
- package/dist/page.d.ts +2 -1
- package/dist/page.js +5 -4
- package/dist/svg.d.ts +7 -2
- package/dist/svg.js +66 -20
- package/dist/trace.js +10 -3
- package/dist/verify.d.ts +2 -0
- package/dist/verify.js +36 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/diff.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { edgeId, isGroup, str, toBeat } from './model.js';
|
|
1
|
+
import { edgeId, isGroup, laneColumns, nodes, str, toBeat } from './model.js';
|
|
2
2
|
const show = (v) => (v == null ? '(none)' : `"${str(v)}"`);
|
|
3
3
|
const fields = (a, b, names) => names.filter((k) => str(a[k]) !== str(b[k])).map((k) => `${String(k)} ${show(a[k])} -> ${show(b[k])}`);
|
|
4
4
|
function compare(kind, before, after, fieldsOf) {
|
|
@@ -29,13 +29,13 @@ const messages = (fig) => {
|
|
|
29
29
|
})));
|
|
30
30
|
return new Map(ids.map((id) => [id, id]));
|
|
31
31
|
};
|
|
32
|
+
const boxes = (f) => {
|
|
33
|
+
const m = new Map();
|
|
34
|
+
const walk = (g, parent) => g.children.forEach((c) => (isGroup(c) ? walk(c, c.label != null ? str(c.label) : c.id) : m.set(c.id, { ...c, group: parent })));
|
|
35
|
+
walk(f.layout);
|
|
36
|
+
return m;
|
|
37
|
+
};
|
|
32
38
|
export function diff(before, after) {
|
|
33
|
-
const boxes = (f) => {
|
|
34
|
-
const m = new Map();
|
|
35
|
-
const walk = (g, parent) => g.children.forEach((c) => (isGroup(c) ? walk(c, c.label != null ? str(c.label) : c.id) : m.set(c.id, { ...c, group: parent })));
|
|
36
|
-
walk(f.layout);
|
|
37
|
-
return m;
|
|
38
|
-
};
|
|
39
39
|
const edges = (f) => new Map(f.edges.map((e) => [edgeId(e), e]));
|
|
40
40
|
const steps = (f) => new Map((f.steps ?? []).map((s) => [str(s.label), s]));
|
|
41
41
|
const out = [
|
|
@@ -60,3 +60,133 @@ export function formatDiff(changes, as) {
|
|
|
60
60
|
})
|
|
61
61
|
.join('\n');
|
|
62
62
|
}
|
|
63
|
+
const key = (g) => g.id ?? (g.label != null ? str(g.label) : undefined);
|
|
64
|
+
const cloneGroup = (g) => ({ ...g, children: g.children.map((c) => (isGroup(c) ? cloneGroup(c) : c)) });
|
|
65
|
+
const ids = (g) => (isGroup(g) ? nodes(g).map((n) => n.id) : [g.id]);
|
|
66
|
+
function find(root, hit) {
|
|
67
|
+
for (const [i, c] of root.children.entries()) {
|
|
68
|
+
if (hit(c))
|
|
69
|
+
return { parent: root, i };
|
|
70
|
+
const inner = isGroup(c) ? find(c, hit) : undefined;
|
|
71
|
+
if (inner)
|
|
72
|
+
return inner;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function mergeFigures(before, after) {
|
|
76
|
+
const was = boxes(before);
|
|
77
|
+
const now = boxes(after);
|
|
78
|
+
const gone = (c) => ids(c).every((id) => !now.has(id));
|
|
79
|
+
const layout = cloneGroup(after.layout);
|
|
80
|
+
const placed = new Set();
|
|
81
|
+
const locate = (c) => find(layout, (x) => x === c || (isGroup(c) ? isGroup(x) && key(c) != null && key(x) === key(c) : !isGroup(x) && x.id === c.id));
|
|
82
|
+
const put = (item, frames, sibs, at) => {
|
|
83
|
+
const sides = [
|
|
84
|
+
...sibs
|
|
85
|
+
.slice(0, at)
|
|
86
|
+
.reverse()
|
|
87
|
+
.map((s) => [s, 1]),
|
|
88
|
+
...sibs.slice(at + 1).map((s) => [s, 0]),
|
|
89
|
+
];
|
|
90
|
+
const next = after.layout.auto
|
|
91
|
+
? undefined
|
|
92
|
+
: sides
|
|
93
|
+
.filter(([s]) => placed.has(s) || !gone(s))
|
|
94
|
+
.map(([s, off]) => ({ spot: locate(s), off }))
|
|
95
|
+
.find((x) => x.spot);
|
|
96
|
+
if (next)
|
|
97
|
+
next.spot.parent.children.splice(next.spot.i + next.off, 0, item);
|
|
98
|
+
else {
|
|
99
|
+
const frame = frames.map(locate).find(Boolean);
|
|
100
|
+
(frame ? frame.parent.children[frame.i] : layout).children.push(item);
|
|
101
|
+
}
|
|
102
|
+
placed.add(item);
|
|
103
|
+
};
|
|
104
|
+
const walk = (g, frames) => g.children.forEach((c, i, sibs) => {
|
|
105
|
+
if (isGroup(c) && !(c.label != null && gone(c) && !locate(c)))
|
|
106
|
+
walk(c, [c, ...frames]);
|
|
107
|
+
else if (gone(c))
|
|
108
|
+
put(c, frames, sibs, i);
|
|
109
|
+
});
|
|
110
|
+
walk(before.layout, [before.layout]);
|
|
111
|
+
const marks = { boxes: {}, edges: {}, steps: diff(before, after).filter((c) => c.kind !== 'box' && c.kind !== 'edge').length };
|
|
112
|
+
for (const [id, n] of now) {
|
|
113
|
+
const old = was.get(id);
|
|
114
|
+
if (!old)
|
|
115
|
+
marks.boxes[id] = 'added';
|
|
116
|
+
else if (fields(old, n, ['label', 'sub', 'shape', 'source', 'group']).length)
|
|
117
|
+
marks.boxes[id] = 'changed';
|
|
118
|
+
}
|
|
119
|
+
for (const id of was.keys())
|
|
120
|
+
if (!now.has(id))
|
|
121
|
+
marks.boxes[id] = 'removed';
|
|
122
|
+
const colAt = new Map();
|
|
123
|
+
if (after.lanes) {
|
|
124
|
+
const colsAfter = laneColumns(after);
|
|
125
|
+
const colsBefore = laneColumns(before);
|
|
126
|
+
const kept = [...now.keys()].filter((id) => was.has(id));
|
|
127
|
+
const slots = new Map();
|
|
128
|
+
for (const id of was.keys()) {
|
|
129
|
+
if (now.has(id))
|
|
130
|
+
continue;
|
|
131
|
+
const old = colsBefore.get(id);
|
|
132
|
+
const prev = kept
|
|
133
|
+
.filter((k) => colsBefore.get(k) < old)
|
|
134
|
+
.sort((a, b) => colsBefore.get(b) - colsBefore.get(a) || colsAfter.get(b) - colsAfter.get(a));
|
|
135
|
+
slots.set(id, { p: prev.length ? colsAfter.get(prev[0]) : -1, old });
|
|
136
|
+
}
|
|
137
|
+
const groups = [...new Map([...slots.values()].map((v) => [`${v.p}:${v.old}`, v])).values()].sort((a, b) => a.p - b.p || a.old - b.old);
|
|
138
|
+
for (const id of now.keys())
|
|
139
|
+
colAt.set(id, colsAfter.get(id) + groups.filter((g) => g.p < colsAfter.get(id)).length);
|
|
140
|
+
for (const [id, v] of slots) {
|
|
141
|
+
const i = groups.findIndex((g) => g.p === v.p && g.old === v.old);
|
|
142
|
+
colAt.set(id, v.p + 1 + groups.filter((g) => g.p < v.p).length + groups.slice(0, i).filter((g) => g.p === v.p).length);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const paint = (g) => ({
|
|
146
|
+
...g,
|
|
147
|
+
children: g.children.map((c) => {
|
|
148
|
+
if (isGroup(c))
|
|
149
|
+
return paint(c);
|
|
150
|
+
const n = { ...c };
|
|
151
|
+
delete n.tone;
|
|
152
|
+
if (after.lanes)
|
|
153
|
+
n.at = colAt.get(c.id);
|
|
154
|
+
const old = was.get(c.id);
|
|
155
|
+
if (marks.boxes[c.id] === 'changed' && old) {
|
|
156
|
+
if (str(old.label) !== str(c.label))
|
|
157
|
+
n.sub = `was "${str(old.label)}"`;
|
|
158
|
+
else if (old.group !== now.get(c.id).group)
|
|
159
|
+
n.sub = old.group != null ? `was in "${old.group}"` : 'moved';
|
|
160
|
+
}
|
|
161
|
+
return n;
|
|
162
|
+
}),
|
|
163
|
+
});
|
|
164
|
+
const oldEdges = new Map(before.edges.map((e) => [edgeId(e), e]));
|
|
165
|
+
const newEdges = new Map(after.edges.map((e) => [edgeId(e), e]));
|
|
166
|
+
const same = (a, b) => b != null && a.from === b.from && a.to === b.to;
|
|
167
|
+
const edges = after.edges.map((e) => {
|
|
168
|
+
const id = edgeId(e);
|
|
169
|
+
const old = oldEdges.get(id);
|
|
170
|
+
if (!same(e, old))
|
|
171
|
+
marks.edges[id] = 'added';
|
|
172
|
+
else if (fields(old, e, ['label', 'source']).length) {
|
|
173
|
+
marks.edges[id] = 'changed';
|
|
174
|
+
if (str(old.label) !== str(e.label) && old.label != null)
|
|
175
|
+
return { ...e, label: e.label != null ? `${str(e.label)} (was ${str(old.label)})` : `(was ${str(old.label)})` };
|
|
176
|
+
}
|
|
177
|
+
return e;
|
|
178
|
+
});
|
|
179
|
+
for (const [id, e] of oldEdges) {
|
|
180
|
+
if (same(e, newEdges.get(id)))
|
|
181
|
+
continue;
|
|
182
|
+
const mid = newEdges.has(id) ? `${id} (old)` : id;
|
|
183
|
+
marks.edges[mid] = 'removed';
|
|
184
|
+
const gone = { ...e, id: mid };
|
|
185
|
+
delete gone.around;
|
|
186
|
+
edges.push(gone);
|
|
187
|
+
}
|
|
188
|
+
const figure = { ...after, layout: paint(layout), edges };
|
|
189
|
+
delete figure.steps;
|
|
190
|
+
delete figure.rail;
|
|
191
|
+
return { figure, marks };
|
|
192
|
+
}
|
package/dist/geometry.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export type Routed = {
|
|
|
30
30
|
room?: number;
|
|
31
31
|
/** The px an elbow detour goes outside the boxes, for each side. */
|
|
32
32
|
bleed?: Partial<Record<Around, number>>;
|
|
33
|
+
ends?: [string, string];
|
|
33
34
|
};
|
|
34
35
|
/** A rect that a route keeps clear of. `box` marks a box. */
|
|
35
36
|
export type Avoid = Rect & {
|
|
@@ -37,6 +38,7 @@ export type Avoid = Rect & {
|
|
|
37
38
|
};
|
|
38
39
|
export declare const avoidOf: (boxes: Rect[], extra: Rect[], lanes: boolean) => Avoid[];
|
|
39
40
|
type Around = 'above' | 'below' | 'left' | 'right';
|
|
41
|
+
export declare const samples: (curve: [Pt, Pt, Pt, Pt]) => Pt[];
|
|
40
42
|
export declare const hit: (curve: [Pt, Pt, Pt, Pt], boxes: Rect[], m: number) => Rect[];
|
|
41
43
|
/** Routes edges between measured boxes as curved SVG paths. `elbow` draws right-angle paths; `stub` splits a cross-lane edge in two. */
|
|
42
44
|
export declare function route(edges: {
|
|
@@ -46,6 +48,7 @@ export declare function route(edges: {
|
|
|
46
48
|
around?: Around;
|
|
47
49
|
sides?: [Side, Side];
|
|
48
50
|
elbow?: boolean;
|
|
51
|
+
drop?: boolean;
|
|
49
52
|
stub?: number[];
|
|
50
53
|
bands?: [Rect | undefined, Rect | undefined];
|
|
51
54
|
labelW?: number;
|
|
@@ -53,6 +56,7 @@ export declare function route(edges: {
|
|
|
53
56
|
bands: Rect[];
|
|
54
57
|
boxes: Rect[];
|
|
55
58
|
}, pad?: number): Routed[];
|
|
59
|
+
export declare const markSide: (id: string, mark: 'start' | 'end', routed: Routed[]) => Side;
|
|
56
60
|
export declare const arcRoom: (routed: Routed[], way: Around) => number;
|
|
57
61
|
export declare function labelAlong(r: Routed, w: number, boxes: Rect[], placed: Rect[], fig?: Rect, lines?: Pt[]): Pt;
|
|
58
62
|
/** True if segment a-b crosses c-d. The ends of a-b are trimmed by 1.5 px. */
|
package/dist/geometry.js
CHANGED
|
@@ -52,9 +52,18 @@ const spanOf = (a, b, around) => {
|
|
|
52
52
|
return [Math.min(p, q), Math.max(p, q)];
|
|
53
53
|
};
|
|
54
54
|
const SIDES = { above: ['t', 't'], below: ['b', 'b'], left: ['l', 'l'], right: ['r', 'r'] };
|
|
55
|
+
export const samples = (curve) => {
|
|
56
|
+
const hull = curve.slice(1).reduce((s, q, k) => s + Math.hypot(q.x - curve[k].x, q.y - curve[k].y), 0);
|
|
57
|
+
const n = Math.max(32, Math.ceil(hull / 4));
|
|
58
|
+
return Array.from({ length: n + 1 }, (_, i) => bezier(curve, i / n));
|
|
59
|
+
};
|
|
55
60
|
export const hit = (curve, boxes, m) => {
|
|
56
|
-
const
|
|
57
|
-
|
|
61
|
+
const xs = curve.map((q) => q.x), ys = curve.map((q) => q.y);
|
|
62
|
+
const near = boxes.filter((r) => r.x + r.w + m > Math.min(...xs) && r.x - m < Math.max(...xs) && r.y + r.h + m > Math.min(...ys) && r.y - m < Math.max(...ys));
|
|
63
|
+
if (!near.length)
|
|
64
|
+
return [];
|
|
65
|
+
const pts = samples(curve);
|
|
66
|
+
return near.filter((r) => pts.some((q) => q.x > r.x - m && q.x < r.x + r.w + m && q.y > r.y - m && q.y < r.y + r.h + m));
|
|
58
67
|
};
|
|
59
68
|
/** Routes edges between measured boxes as curved SVG paths. `elbow` draws right-angle paths; `stub` splits a cross-lane edge in two. */
|
|
60
69
|
export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, pad = 24) {
|
|
@@ -68,7 +77,7 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
|
|
|
68
77
|
const others = avoid.filter((r) => r.box && !same(r, a) && !same(r, b));
|
|
69
78
|
const plain = stacked ? (a.y < b.y ? ['b', 't'] : ['t', 'b']) : a.x < b.x ? ['r', 'l'] : ['l', 'r'];
|
|
70
79
|
const past = hit(bend(sideMid(a, plain[0]), sideMid(b, plain[1]), plain[0]), others, -2);
|
|
71
|
-
const misses = (way, at) => [0.2, 0.5, 0.8].filter((f) => hit(arcLine(sideAt(a, SIDES[way][0], f), sideAt(b, SIDES[way][1], f), way, at), others, -
|
|
80
|
+
const misses = (way, at) => [0.2, 0.5, 0.8].filter((f) => hit(arcLine(sideAt(a, SIDES[way][0], f), sideAt(b, SIDES[way][1], f), way, at), others, -6).length > 0)
|
|
72
81
|
.length;
|
|
73
82
|
const blocked = (way, at) => misses(way, at) === 3;
|
|
74
83
|
let around = e.around;
|
|
@@ -81,7 +90,10 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
|
|
|
81
90
|
if (blocked(around, arc))
|
|
82
91
|
around = undefined;
|
|
83
92
|
}
|
|
84
|
-
|
|
93
|
+
const down = e.drop && !hit(bend(sideMid(a, 'b'), sideMid(b, 't'), 'b'), others, -2).length;
|
|
94
|
+
if (e.drop && !around)
|
|
95
|
+
elbow = !down;
|
|
96
|
+
else if (!around && !e.sides && !e.stub && !e.elbow && past.length) {
|
|
85
97
|
const ways = stacked ? ['left', 'right'] : ['above', 'below'];
|
|
86
98
|
const tries = [];
|
|
87
99
|
for (const share of [false, true])
|
|
@@ -107,7 +119,7 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
|
|
|
107
119
|
const [lo, hi] = spanOf(a, b, around);
|
|
108
120
|
arcs.push({ way: around, lo, hi, at: arc });
|
|
109
121
|
}
|
|
110
|
-
const [sa, sb] = e.sides ?? (e.stub ? ['r', 'l'] : around ? SIDES[around] : plain);
|
|
122
|
+
const [sa, sb] = e.sides ?? (e.stub ? ['r', 'l'] : down ? ['b', 't'] : around ? SIDES[around] : plain);
|
|
111
123
|
picks.push({ ...e, plain, a, b, sa, sb, around, arc, elbow });
|
|
112
124
|
}
|
|
113
125
|
let anchor = new Map();
|
|
@@ -152,10 +164,10 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
|
|
|
152
164
|
if (!p.around || !s || !e)
|
|
153
165
|
continue;
|
|
154
166
|
const others = avoid.filter((r) => r.box && !same(r, p.a) && !same(r, p.b));
|
|
155
|
-
if (!hit(arcLine(s, e, p.around, p.arc), others, -
|
|
167
|
+
if (!hit(arcLine(s, e, p.around, p.arc), others, -6).length)
|
|
156
168
|
continue;
|
|
157
169
|
const [pa, pb] = p.plain;
|
|
158
|
-
const straight = hit(bend(sideMid(p.a, pa), sideMid(p.b, pb), pa), others, -
|
|
170
|
+
const straight = hit(bend(sideMid(p.a, pa), sideMid(p.b, pb), pa), others, -6).length > 0;
|
|
159
171
|
[p.sa, p.sb, p.around, p.arc, p.elbow, moved] = [pa, pb, undefined, undefined, p.elbow || straight, true];
|
|
160
172
|
}
|
|
161
173
|
if (moved)
|
|
@@ -183,7 +195,15 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
|
|
|
183
195
|
const outside = (way, { x, y }) => (way === 'above' ? y0 - y : way === 'below' ? y - y1 : way === 'left' ? x0 - x : x - x1);
|
|
184
196
|
const roomOf = (way, at, curve, labelW) => Math.max(ARC - 6, lanes || !labelW ? outside(way, { x: at, y: at }) - 6 : outside(way, bezier(curve, 0.5)) + (upright(way) ? labelW / 2 : 9));
|
|
185
197
|
const one = (p) => {
|
|
186
|
-
const
|
|
198
|
+
const turn = !p.stub && p.elbow && p.sa === 'l';
|
|
199
|
+
const [s, e] = [anchor.get(p.id + (turn ? ':e' : ':s')), anchor.get(p.id + (turn ? ':s' : ':e'))];
|
|
200
|
+
const flip = (r) => {
|
|
201
|
+
if (!turn)
|
|
202
|
+
return r;
|
|
203
|
+
const pts = [...r.elbow].reverse();
|
|
204
|
+
const d = pts.slice(1).reduce((d, q, k) => d + (q.y === pts[k].y ? ` H ${q.x}` : ` V ${q.y}`), `M ${pts[0].x} ${pts[0].y}`);
|
|
205
|
+
return { ...r, d, curve: [...r.curve].reverse(), elbow: pts };
|
|
206
|
+
};
|
|
187
207
|
if (p.stub) {
|
|
188
208
|
const [ow, iw, sw] = p.stub;
|
|
189
209
|
const [sb, tb] = p.bands ?? [];
|
|
@@ -336,7 +356,7 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
|
|
|
336
356
|
}
|
|
337
357
|
if (p.elbow) {
|
|
338
358
|
if (e.x - s.x < 16 && s.y === e.y) {
|
|
339
|
-
return { id: p.id, d: `M ${s.x} ${s.y} H ${e.x}`, mid: { x: (s.x + e.x) / 2, y: s.y }, curve: [s, s, e, e], elbow: [s, e] };
|
|
359
|
+
return flip({ id: p.id, d: `M ${s.x} ${s.y} H ${e.x}`, mid: { x: (s.x + e.x) / 2, y: s.y }, curve: [s, s, e, e], elbow: [s, e] });
|
|
340
360
|
}
|
|
341
361
|
const own = (r) => [p.a, p.b].some((q) => same(q, r));
|
|
342
362
|
const all = avoid.filter((r) => !own(r));
|
|
@@ -347,14 +367,14 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
|
|
|
347
367
|
const mx = s.x + 8, ex = e.x - 8, mid = (s.y + e.y) / 2;
|
|
348
368
|
const ys = Array.from({ length: Math.floor((y1 - y0 + 24) / 4) + 1 }, (_, i) => y0 - 12 + i * 4).sort((u, w) => Math.abs(u - mid) - Math.abs(w - mid));
|
|
349
369
|
const my = ys.find((y) => vClear(mx, s.y, y) && hClear(y, ex, mx, all) && vClear(ex, y, e.y)) ?? mid;
|
|
350
|
-
return {
|
|
370
|
+
return flip({
|
|
351
371
|
id: p.id,
|
|
352
372
|
d: `M ${s.x} ${s.y} H ${mx} V ${my} H ${ex} V ${e.y} H ${e.x}`,
|
|
353
373
|
mid: { x: (mx + ex) / 2, y: my },
|
|
354
374
|
curve: [s, { x: mx, y: my }, { x: ex, y: my }, e],
|
|
355
375
|
elbow: [s, { x: mx, y: s.y }, { x: mx, y: my }, { x: ex, y: my }, { x: ex, y: e.y }, e],
|
|
356
376
|
bleed: { above: room(y0 - my), below: room(my - y1) },
|
|
357
|
-
};
|
|
377
|
+
});
|
|
358
378
|
}
|
|
359
379
|
const mid = (s.x + e.x) / 2;
|
|
360
380
|
const gap = Array.from({ length: Math.floor((e.x - s.x - 16) / 4) + 1 }, (_, i) => s.x + 8 + i * 4).sort((u, w) => Math.abs(u - mid) - Math.abs(w - mid));
|
|
@@ -383,7 +403,7 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
|
|
|
383
403
|
}
|
|
384
404
|
if (best) {
|
|
385
405
|
const { x, y, ex } = best;
|
|
386
|
-
return {
|
|
406
|
+
return flip({
|
|
387
407
|
id: p.id,
|
|
388
408
|
d: `M ${s.x} ${s.y} H ${x} V ${y} H ${ex} V ${e.y} H ${e.x}`,
|
|
389
409
|
mid: { x: (x + ex) / 2, y },
|
|
@@ -395,18 +415,18 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
|
|
|
395
415
|
above: room(y0 - y),
|
|
396
416
|
below: room(y - y1),
|
|
397
417
|
},
|
|
398
|
-
};
|
|
418
|
+
});
|
|
399
419
|
}
|
|
400
420
|
}
|
|
401
421
|
const x = mx ?? mid;
|
|
402
422
|
const c1 = { x, y: s.y }, c2 = { x, y: e.y };
|
|
403
|
-
return {
|
|
423
|
+
return flip({
|
|
404
424
|
id: p.id,
|
|
405
425
|
d: `M ${s.x} ${s.y} H ${x} V ${e.y} H ${e.x}`,
|
|
406
426
|
mid: { x, y: (s.y + e.y) / 2 },
|
|
407
427
|
curve: [s, c1, c2, e],
|
|
408
428
|
elbow: [s, c1, c2, e],
|
|
409
|
-
};
|
|
429
|
+
});
|
|
410
430
|
}
|
|
411
431
|
if (p.around) {
|
|
412
432
|
const curve = arcLine(s, e, p.around, p.arc);
|
|
@@ -456,15 +476,23 @@ export function route(edges, rects, tips = new Set(), avoid = [], area, lanes, p
|
|
|
456
476
|
pills.push(...use.out);
|
|
457
477
|
let k = 0;
|
|
458
478
|
for (const [p, r] of done) {
|
|
459
|
-
if (p.labelW)
|
|
479
|
+
if (p.labelW) {
|
|
460
480
|
r.mid = use.mids[k++];
|
|
481
|
+
const [l, rt] = [x0 - r.mid.x + p.labelW / 2, r.mid.x + p.labelW / 2 - x1];
|
|
482
|
+
r.bleed = { ...r.bleed, left: Math.max(r.bleed?.left ?? 0, l, 0), right: Math.max(r.bleed?.right ?? 0, rt, 0) };
|
|
483
|
+
}
|
|
461
484
|
track(Array.from({ length: 33 }, (_, i) => bezier(r.curve, i / 32)));
|
|
462
485
|
}
|
|
463
486
|
for (const p of picks)
|
|
464
487
|
if (p.stub)
|
|
465
488
|
done.set(p, one(p));
|
|
466
|
-
return picks.map((p) => done.get(p));
|
|
489
|
+
return picks.map((p) => ({ ...done.get(p), ends: [`${p.from}:${p.sa}`, `${p.to}:${p.sb}`] }));
|
|
467
490
|
}
|
|
491
|
+
export const markSide = (id, mark, routed) => {
|
|
492
|
+
const used = new Set(routed.flatMap((r) => r.ends ?? []));
|
|
493
|
+
const order = mark === 'start' ? ['l', 't', 'b', 'r'] : ['r', 'b', 't', 'l'];
|
|
494
|
+
return order.find((s) => !used.has(`${id}:${s}`)) ?? order[0];
|
|
495
|
+
};
|
|
468
496
|
export const arcRoom = (routed, way) => Math.max(0, ...routed.map((r) => (r.around === way ? r.room : (r.bleed?.[way] ?? 0))));
|
|
469
497
|
// The player pads its root by 4 px on a side with no detour.
|
|
470
498
|
const PLAYER_PAD = 4;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,4 +5,4 @@ export { LIGHT, DARK } from './model.ts';
|
|
|
5
5
|
* The interactive player for one figure. Give it a `FlowProps` spec.
|
|
6
6
|
* Use `toSvg` from `flowfig/svg` for a static animated SVG.
|
|
7
7
|
*/
|
|
8
|
-
export declare function Flow(
|
|
8
|
+
export declare function Flow(props: FlowProps): import("react").JSX.Element;
|