@jbrowse/bandage-core 11.7.3 → 11.9.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 +59 -19
- package/dist/bubbles/bubbleHalos.js +6 -6
- package/dist/bubbles/classifyBubble.js +5 -5
- package/dist/bubbles/superbubbles.js +7 -7
- package/dist/chunks/{chunk-K4KVAMEF.js → chunk-2A6Z2DHF.js} +1 -1
- package/dist/chunks/{chunk-4SN7R4UF.js → chunk-2MXFG3W7.js} +1 -1
- package/dist/chunks/chunk-3BDQQTXY.js +568 -0
- package/dist/chunks/{chunk-QXBQDHKL.js → chunk-3BOREHCJ.js} +80 -38
- package/dist/chunks/{chunk-F4CDC72I.js → chunk-3DHGSHC3.js} +1 -1
- package/dist/chunks/{chunk-P276TWPM.js → chunk-3GORESBG.js} +1 -1
- package/dist/chunks/{chunk-4KOYS5WR.js → chunk-4BYGFDQB.js} +1 -1
- package/dist/chunks/{chunk-LH436HS2.js → chunk-4JDO7ZLN.js} +2 -2
- package/dist/chunks/{chunk-2US43KNG.js → chunk-7YSIP3O2.js} +2 -2
- package/dist/chunks/{chunk-VNF236GG.js → chunk-ARXDNQLF.js} +2 -2
- package/dist/chunks/{chunk-N73RA4DR.js → chunk-AURNOR27.js} +3 -3
- package/dist/chunks/{chunk-63XWYNID.js → chunk-BDS5VZYZ.js} +4 -4
- package/dist/chunks/{chunk-BXE3SBBN.js → chunk-C3KAPREU.js} +1 -1
- package/dist/chunks/{chunk-VXKL2ZLR.js → chunk-GDZQFELP.js} +259 -60
- package/dist/chunks/{chunk-2T3V6Z3M.js → chunk-GYJMPVOU.js} +1 -1
- package/dist/chunks/{chunk-BFUS3ENH.js → chunk-HEYIAMTC.js} +2 -2
- package/dist/chunks/{chunk-HMEAGAGC.js → chunk-HIN56TLA.js} +2 -2
- package/dist/chunks/{chunk-2VMKQKVO.js → chunk-HNNP5QUN.js} +1 -1
- package/dist/chunks/{chunk-W5YRBEWQ.js → chunk-I53QYITQ.js} +1 -1
- package/dist/chunks/{chunk-UTDMFVC7.js → chunk-I5CHAYDZ.js} +8 -8
- package/dist/chunks/{chunk-EPOPGV5G.js → chunk-IXLT3JJX.js} +1 -1
- package/dist/chunks/{chunk-SG7VQB4F.js → chunk-IZKM4NY7.js} +1 -1
- package/dist/chunks/{chunk-KMLATBNT.js → chunk-J7RNXBXA.js} +3 -3
- package/dist/chunks/{chunk-VACK24HF.js → chunk-JJV2GT2Z.js} +43 -9
- package/dist/chunks/{chunk-PKGPSTRW.js → chunk-JO2T77NW.js} +2 -2
- package/dist/chunks/{chunk-XKPXOIWD.js → chunk-JVT3XTKN.js} +3 -3
- package/dist/chunks/{chunk-SDI6BVG7.js → chunk-KKHXMJEE.js} +1 -1
- package/dist/chunks/{chunk-G7SG4XMK.js → chunk-LODKC7PJ.js} +7 -0
- package/dist/chunks/{chunk-AJQRKN3X.js → chunk-N3DCLT7M.js} +1 -1
- package/dist/chunks/{chunk-ZZZ6NVLW.js → chunk-NAYS472K.js} +1 -1
- package/dist/chunks/{chunk-AWJJSA7D.js → chunk-NWEVR2OQ.js} +1 -1
- package/dist/chunks/{chunk-33KUO34R.js → chunk-P2GJBFNA.js} +1 -1
- package/dist/chunks/{chunk-IHM43Y7B.js → chunk-RT4U3DH3.js} +1 -1
- package/dist/chunks/{chunk-XTCIKBWM.js → chunk-S6E3PMH7.js} +1 -1
- package/dist/chunks/{chunk-BLDUBB7X.js → chunk-SZYFZNCO.js} +2 -2
- package/dist/chunks/{chunk-T3MEG5D3.js → chunk-T2UNTWU5.js} +90 -21
- package/dist/chunks/{chunk-QJC63NFR.js → chunk-TQ26XEFL.js} +2 -2
- package/dist/chunks/{chunk-6RB5ZQ34.js → chunk-UEWIGD52.js} +51 -14
- package/dist/chunks/{chunk-SENPGW4O.js → chunk-VCUOYVLC.js} +1 -1
- package/dist/chunks/{chunk-CL6ELLVF.js → chunk-VNCHWWLG.js} +1 -1
- package/dist/chunks/{chunk-GD6RUZU5.js → chunk-VQ353UDK.js} +14 -8
- package/dist/chunks/{chunk-QLLKALSH.js → chunk-VTGQCLGI.js} +1 -1
- package/dist/chunks/{chunk-Z4HXW6ZH.js → chunk-ZWBQ4RRK.js} +24 -10
- package/dist/cli/figure.js +31 -30
- package/dist/cli/fold.js +6 -6
- package/dist/deletionEdges.js +2 -2
- package/dist/figure.js +27 -26
- package/dist/figureData.js +25 -24
- package/dist/figureScene.js +18 -17
- package/dist/figureSettings.js +6 -6
- package/dist/foldVariants.js +2 -2
- package/dist/genes/genePins.js +3 -3
- package/dist/graphLabels.js +4 -4
- package/dist/index.js +45 -44
- package/dist/labelLayout.js +8 -8
- package/dist/layout/forceCache.js +6 -6
- package/dist/layout/mergeRuns.js +2 -2
- package/dist/layout/orientToReference.js +2 -2
- package/dist/layout/tubeMapLayout.js +9 -9
- package/dist/layout/walkRowDraw.js +9 -8
- package/dist/layout/walkRowLayout.js +3 -3
- package/dist/layout/walkRowRuns.js +1 -1
- package/dist/layout/walkRows.js +2 -2
- package/dist/layout/walkStrip.js +13 -12
- package/dist/layoutModes.js +11 -11
- package/dist/pathColors.js +5 -5
- package/dist/pipeline.js +5 -5
- package/dist/referenceStrip.js +9 -8
- package/dist/renderer/GeometryBuilder.js +8 -7
- package/dist/renderer/walkLanes.js +46 -0
- package/dist/tubeMap/axis.js +5 -5
- package/dist/tubeMap/bundle.js +10 -10
- package/dist/tubeMap/coarsen.js +10 -10
- package/dist/tubeMap/deviations.js +11 -11
- package/dist/tubeMap/draw.js +5 -5
- package/dist/tubeMap/genes.js +6 -6
- package/dist/tubeMap/nodeColors.js +9 -8
- package/dist/tubeMap/reads.js +2 -2
- package/dist/types/figureData.d.ts +22 -0
- package/dist/types/renderer/types.d.ts +20 -0
- package/dist/types/renderer/walkLanes.d.ts +76 -0
- package/dist/types/util/geometry.d.ts +13 -1
- package/dist/types/version.d.ts +1 -1
- package/dist/types/walkEncoding.d.ts +9 -5
- package/dist/types/walkHighlight.d.ts +3 -0
- package/dist/util/SpatialIndex.js +4 -4
- package/dist/util/edgeCurves.js +3 -3
- package/dist/util/geometry.js +7 -1
- package/dist/util/hitDetection.js +5 -5
- package/dist/version.js +1 -1
- package/dist/viewport.js +6 -6
- package/dist/walkEncoding.js +13 -10
- package/dist/walkHighlight.js +10 -9
- package/dist/walkKey.js +5 -5
- package/package.json +1 -1
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
import {
|
|
2
|
+
slideAt
|
|
3
|
+
} from "./chunk-JJV2GT2Z.js";
|
|
4
|
+
|
|
5
|
+
// src/renderer/walkLanes.ts
|
|
6
|
+
var THREAD_PX = 2;
|
|
7
|
+
var THREAD_GAP_PX = 1.5;
|
|
8
|
+
var SLOT_GAP_PX = 4;
|
|
9
|
+
var LANE_SMOOTHING_PX = 12;
|
|
10
|
+
var THREAD_REACH_PX = 64;
|
|
11
|
+
var THREAD_TAPER_PX = 24;
|
|
12
|
+
var MAX_BAND_PX = 48;
|
|
13
|
+
var PASSES_KEY = "a thread per pass through a node";
|
|
14
|
+
var threadsPasses = (walks) => walks.length > 1 && walks.some((w) => w.mostPasses > 1);
|
|
15
|
+
var threadsWidth = (threads) => threads * THREAD_PX + Math.max(0, threads - 1) * THREAD_GAP_PX;
|
|
16
|
+
var sum = (values) => values.reduce((a, b) => a + b, 0);
|
|
17
|
+
function slotLayout(spans, mostPasses, laneWidth) {
|
|
18
|
+
const widths = spans.map(
|
|
19
|
+
(m, i) => mostPasses[i] > 1 ? threadsWidth(m) : laneWidth * Math.min(1, m)
|
|
20
|
+
);
|
|
21
|
+
let seen = 0;
|
|
22
|
+
const gaps = spans.map((m) => {
|
|
23
|
+
const share = Math.min(1, m);
|
|
24
|
+
const gap = SLOT_GAP_PX * share * seen;
|
|
25
|
+
seen = Math.max(seen, share);
|
|
26
|
+
return gap;
|
|
27
|
+
});
|
|
28
|
+
return { widths, gaps, band: sum(widths) + sum(gaps) };
|
|
29
|
+
}
|
|
30
|
+
var THREAD_PITCH_PX = THREAD_PX + THREAD_GAP_PX;
|
|
31
|
+
function slotCentres(spans, mostPasses, held = spans) {
|
|
32
|
+
const { widths, gaps, band } = slotLayout(spans, mostPasses, THREAD_PX);
|
|
33
|
+
let at = -band / 2;
|
|
34
|
+
return widths.map((width, i) => {
|
|
35
|
+
at += gaps[i];
|
|
36
|
+
const slack = mostPasses[i] > 1 ? Math.max(0, width - threadsWidth(held[i])) : 0;
|
|
37
|
+
const centre = at + slack / 2 + THREAD_PX / 2;
|
|
38
|
+
at += width;
|
|
39
|
+
return centre;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
var bandWidthPx = (mostPasses) => slotLayout(mostPasses, mostPasses, THREAD_PX).band;
|
|
43
|
+
var bandScale = (mostPasses) => Math.min(1, MAX_BAND_PX / (bandWidthPx(mostPasses) || 1));
|
|
44
|
+
var passesKey = (walks) => bandScale(walks.map((w) => w.mostPasses)) < 1 ? `${PASSES_KEY}, thinned to fit` : PASSES_KEY;
|
|
45
|
+
function laneSlideAt(start, end, along) {
|
|
46
|
+
const vector = ({ normal, offsetX: offsetX2 }) => ({
|
|
47
|
+
dx: normal.nx * offsetX2,
|
|
48
|
+
dy: normal.ny * offsetX2
|
|
49
|
+
});
|
|
50
|
+
const { dx, dy } = slideAt(vector(start), vector(end), along);
|
|
51
|
+
const offsetX = Math.hypot(dx, dy);
|
|
52
|
+
return offsetX > 0 ? { normal: { nx: dx / offsetX, ny: dy / offsetX }, offsetX } : { normal: { nx: 0, ny: 0 }, offsetX: 0 };
|
|
53
|
+
}
|
|
54
|
+
var gapOf = (a, b) => Math.hypot(b.x - a.x, b.y - a.y);
|
|
55
|
+
function arcOf(run) {
|
|
56
|
+
const s = new Float64Array(run.length);
|
|
57
|
+
for (let i = 1; i < run.length; i++) {
|
|
58
|
+
s[i] = s[i - 1] + gapOf(run[i - 1], run[i]);
|
|
59
|
+
}
|
|
60
|
+
return s;
|
|
61
|
+
}
|
|
62
|
+
function laneRuns(routes, nodePositions, yToX, jump) {
|
|
63
|
+
const seen = /* @__PURE__ */ new Set();
|
|
64
|
+
const owns = routes.map(
|
|
65
|
+
(route) => route.map((id) => {
|
|
66
|
+
if (seen.has(id)) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
seen.add(id);
|
|
70
|
+
return !!nodePositions[id]?.length;
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
const gap = (a, b) => Math.hypot(b.x - a.x, b.y * yToX - a.y);
|
|
74
|
+
const runs = [];
|
|
75
|
+
const owned = [];
|
|
76
|
+
let run = [];
|
|
77
|
+
let mine = [];
|
|
78
|
+
const breakRun = () => {
|
|
79
|
+
if (run.length) {
|
|
80
|
+
runs.push(run);
|
|
81
|
+
owned.push(mine);
|
|
82
|
+
}
|
|
83
|
+
run = [];
|
|
84
|
+
mine = [];
|
|
85
|
+
};
|
|
86
|
+
routes.forEach((route, r) => {
|
|
87
|
+
if (!owns[r].includes(true)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
route.forEach((id, step) => {
|
|
91
|
+
const segments = nodePositions[id];
|
|
92
|
+
if (!segments?.length) {
|
|
93
|
+
breakRun();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const first = segments[0];
|
|
97
|
+
const last = segments.at(-1);
|
|
98
|
+
const prev = run.at(-1);
|
|
99
|
+
const next = nodePositions[route[step + 1] ?? ""];
|
|
100
|
+
const toNext = (p) => {
|
|
101
|
+
const at = { x: p.x, y: p.y * yToX };
|
|
102
|
+
return next?.length ? Math.min(gap(at, next[0]), gap(at, next.at(-1))) : 0;
|
|
103
|
+
};
|
|
104
|
+
const forward = prev ? gap(prev, first) <= gap(prev, last) : toNext(last) <= toNext(first);
|
|
105
|
+
if (prev && gap(prev, forward ? first : last) > jump) {
|
|
106
|
+
breakRun();
|
|
107
|
+
}
|
|
108
|
+
const middle = Math.floor(segments.length / 2);
|
|
109
|
+
for (let k = 0; k < segments.length; k++) {
|
|
110
|
+
const index = forward ? k : segments.length - 1 - k;
|
|
111
|
+
const p = segments[index];
|
|
112
|
+
run.push({
|
|
113
|
+
x: p.x,
|
|
114
|
+
y: p.y * yToX,
|
|
115
|
+
id,
|
|
116
|
+
index,
|
|
117
|
+
middle: index === middle,
|
|
118
|
+
forward
|
|
119
|
+
});
|
|
120
|
+
mine.push(owns[r][step]);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
breakRun();
|
|
124
|
+
});
|
|
125
|
+
return { runs, owned };
|
|
126
|
+
}
|
|
127
|
+
function ownedWindows(s, owned, margin) {
|
|
128
|
+
const windows = [];
|
|
129
|
+
for (let i = 0; i < owned.length; i++) {
|
|
130
|
+
if (!owned[i]) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
let j = i;
|
|
134
|
+
while (j + 1 < owned.length && owned[j + 1]) {
|
|
135
|
+
j++;
|
|
136
|
+
}
|
|
137
|
+
const last = windows.at(-1);
|
|
138
|
+
const floor = last ? last[1] : 0;
|
|
139
|
+
let lo = i;
|
|
140
|
+
while (lo > floor && s[i] - s[lo - 1] <= margin) {
|
|
141
|
+
lo--;
|
|
142
|
+
}
|
|
143
|
+
let hi = j;
|
|
144
|
+
while (hi + 1 < s.length && s[hi + 1] - s[j] <= margin) {
|
|
145
|
+
hi++;
|
|
146
|
+
}
|
|
147
|
+
if (last && lo <= last[1] + 1) {
|
|
148
|
+
last[1] = hi;
|
|
149
|
+
} else {
|
|
150
|
+
windows.push([lo, hi]);
|
|
151
|
+
}
|
|
152
|
+
i = j;
|
|
153
|
+
}
|
|
154
|
+
return windows;
|
|
155
|
+
}
|
|
156
|
+
function smoothedNormals(run, reach) {
|
|
157
|
+
const s = arcOf(run);
|
|
158
|
+
const end = s.at(-1);
|
|
159
|
+
const pointAt = (at, j) => {
|
|
160
|
+
const a = run[j];
|
|
161
|
+
const b = run[Math.min(j + 1, run.length - 1)];
|
|
162
|
+
const span = s[Math.min(j + 1, run.length - 1)] - s[j];
|
|
163
|
+
const f = span > 0 ? (at - s[j]) / span : 0;
|
|
164
|
+
return { x: a.x + (b.x - a.x) * f, y: a.y + (b.y - a.y) * f };
|
|
165
|
+
};
|
|
166
|
+
let lo = 0;
|
|
167
|
+
let hi = 0;
|
|
168
|
+
return run.map((_, i) => {
|
|
169
|
+
const back = Math.max(0, s[i] - reach / 2);
|
|
170
|
+
const ahead = Math.min(end, s[i] + reach / 2);
|
|
171
|
+
while (lo < run.length - 1 && s[lo + 1] <= back) {
|
|
172
|
+
lo++;
|
|
173
|
+
}
|
|
174
|
+
while (hi < run.length - 1 && s[hi + 1] <= ahead) {
|
|
175
|
+
hi++;
|
|
176
|
+
}
|
|
177
|
+
const a = pointAt(back, lo);
|
|
178
|
+
const b = pointAt(ahead, hi);
|
|
179
|
+
const len = gapOf(a, b);
|
|
180
|
+
return len > 0 ? { nx: -(b.y - a.y) / len, ny: (b.x - a.x) / len } : void 0;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
function cutCurls(run, close, length) {
|
|
184
|
+
const s = arcOf(run);
|
|
185
|
+
const out = run.slice();
|
|
186
|
+
const picks = [];
|
|
187
|
+
s.forEach((at, k) => {
|
|
188
|
+
const last = picks.at(-1);
|
|
189
|
+
if (last === void 0 || at - s[last] >= close / 4) {
|
|
190
|
+
picks.push(k);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
if (picks.at(-1) !== run.length - 1) {
|
|
194
|
+
picks.push(run.length - 1);
|
|
195
|
+
}
|
|
196
|
+
let a = 0;
|
|
197
|
+
while (a < picks.length) {
|
|
198
|
+
const i = picks[a];
|
|
199
|
+
let to = -1;
|
|
200
|
+
for (let b = a + 1; b < picks.length && s[picks[b]] - s[i] <= length; b++) {
|
|
201
|
+
const j2 = picks[b];
|
|
202
|
+
if (s[j2] - s[i] > Math.PI * close && gapOf(run[i], run[j2]) <= close) {
|
|
203
|
+
to = b;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (to < 0) {
|
|
207
|
+
a++;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const j = picks[to];
|
|
211
|
+
const p = run[i];
|
|
212
|
+
const q = run[j];
|
|
213
|
+
for (let k = i + 1; k < j; k++) {
|
|
214
|
+
const f = (s[k] - s[i]) / (s[j] - s[i]);
|
|
215
|
+
out[k] = {
|
|
216
|
+
...run[k],
|
|
217
|
+
x: p.x + (q.x - p.x) * f,
|
|
218
|
+
y: p.y + (q.y - p.y) * f
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
a = to;
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
function rounded(run, half) {
|
|
226
|
+
const n = run.length;
|
|
227
|
+
const s = arcOf(run);
|
|
228
|
+
const end = s[n - 1];
|
|
229
|
+
const sumW = new Float64Array(n + 1);
|
|
230
|
+
const sumX = new Float64Array(n + 1);
|
|
231
|
+
const sumY = new Float64Array(n + 1);
|
|
232
|
+
for (let i = 0; i < n; i++) {
|
|
233
|
+
const p = run[i];
|
|
234
|
+
const w = (s[Math.min(i + 1, n - 1)] - s[Math.max(i - 1, 0)]) / 2;
|
|
235
|
+
sumW[i + 1] = sumW[i] + w;
|
|
236
|
+
sumX[i + 1] = sumX[i] + w * p.x;
|
|
237
|
+
sumY[i + 1] = sumY[i] + w * p.y;
|
|
238
|
+
}
|
|
239
|
+
const out = new Array(n);
|
|
240
|
+
let lo = 0;
|
|
241
|
+
let hi = 0;
|
|
242
|
+
for (let i = 0; i < n; i++) {
|
|
243
|
+
const p = run[i];
|
|
244
|
+
const reach = Math.min(half, s[i], end - s[i]);
|
|
245
|
+
const from = s[i] - reach;
|
|
246
|
+
const to = s[i] + reach + 1e-9 * (1 + end);
|
|
247
|
+
while (lo < n && s[lo] < from) {
|
|
248
|
+
lo++;
|
|
249
|
+
}
|
|
250
|
+
while (hi < n && s[hi] < to) {
|
|
251
|
+
hi++;
|
|
252
|
+
}
|
|
253
|
+
const w = sumW[hi] - sumW[lo];
|
|
254
|
+
out[i] = w > 0 ? { x: (sumX[hi] - sumX[lo]) / w, y: (sumY[hi] - sumY[lo]) / w } : { x: p.x, y: p.y };
|
|
255
|
+
}
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
function fieldSetter(field, nodePositions) {
|
|
259
|
+
return ({ id, index }, value) => {
|
|
260
|
+
let own = field.get(id);
|
|
261
|
+
if (!own) {
|
|
262
|
+
own = new Array(nodePositions[id].length);
|
|
263
|
+
field.set(id, own);
|
|
264
|
+
}
|
|
265
|
+
own[index] = value;
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function laneLines(lanes, nodePositions, yToX, reach, band, smooth = true) {
|
|
269
|
+
const turn = Math.max(reach, 2 * band);
|
|
270
|
+
const curl = Math.PI * band;
|
|
271
|
+
const margin = smooth ? curl + band / 2 + turn / 2 : turn / 2;
|
|
272
|
+
const normals = /* @__PURE__ */ new Map();
|
|
273
|
+
const shifts = /* @__PURE__ */ new Map();
|
|
274
|
+
const setNormal = fieldSetter(normals, nodePositions);
|
|
275
|
+
const setShift = fieldSetter(shifts, nodePositions);
|
|
276
|
+
lanes.runs.forEach((run, r) => {
|
|
277
|
+
const owned = lanes.owned[r];
|
|
278
|
+
const pieces = ownedWindows(arcOf(run), owned, margin).map(([lo, hi]) => {
|
|
279
|
+
const points = run.slice(lo, hi + 1);
|
|
280
|
+
const line = smooth ? rounded(cutCurls(points, band / 2, curl), band / 2) : points;
|
|
281
|
+
return { lo, points, line, normals: smoothedNormals(line, turn) };
|
|
282
|
+
});
|
|
283
|
+
let agree = 0;
|
|
284
|
+
let along = 0;
|
|
285
|
+
for (const piece of pieces) {
|
|
286
|
+
piece.points.forEach((p, i) => {
|
|
287
|
+
const theirs = normals.get(p.id)?.[p.index];
|
|
288
|
+
const mine = piece.normals[i];
|
|
289
|
+
if (theirs && mine) {
|
|
290
|
+
agree += theirs.nx * mine.nx + theirs.ny * mine.ny;
|
|
291
|
+
}
|
|
292
|
+
along += p.forward ? 1 : -1;
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
const sign = (agree === 0 ? along : agree) < 0 ? -1 : 1;
|
|
296
|
+
for (const { lo, points, line, normals: own } of pieces) {
|
|
297
|
+
points.forEach((p, i) => {
|
|
298
|
+
if (!owned[lo + i]) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const n = own[i];
|
|
302
|
+
if (n) {
|
|
303
|
+
setNormal(p, sign > 0 ? n : { nx: -n.nx, ny: -n.ny });
|
|
304
|
+
}
|
|
305
|
+
if (smooth) {
|
|
306
|
+
setShift(p, {
|
|
307
|
+
dx: line[i].x - p.x,
|
|
308
|
+
dy: (line[i].y - p.y) / yToX
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
return { normals, shifts };
|
|
315
|
+
}
|
|
316
|
+
function windowed(s, v, half, sign) {
|
|
317
|
+
const out = new Array(v.length);
|
|
318
|
+
const deque = [];
|
|
319
|
+
let head = 0;
|
|
320
|
+
let next = 0;
|
|
321
|
+
for (let i = 0; i < v.length; i++) {
|
|
322
|
+
while (next < v.length && s[next] - s[i] <= half) {
|
|
323
|
+
while (deque.length > head && sign * v[deque.at(-1)] <= sign * v[next]) {
|
|
324
|
+
deque.pop();
|
|
325
|
+
}
|
|
326
|
+
deque.push(next++);
|
|
327
|
+
}
|
|
328
|
+
while (s[i] - s[deque[head]] > half) {
|
|
329
|
+
head++;
|
|
330
|
+
}
|
|
331
|
+
out[i] = v[deque[head]];
|
|
332
|
+
}
|
|
333
|
+
return out;
|
|
334
|
+
}
|
|
335
|
+
function windowMean(s, v, half, counted = () => true) {
|
|
336
|
+
const n = v.length;
|
|
337
|
+
const weight = s.map(
|
|
338
|
+
(_, i) => !counted(v[i]) ? 0 : n > 1 ? (s[Math.min(i + 1, n - 1)] - s[Math.max(i - 1, 0)]) / 2 : 1
|
|
339
|
+
);
|
|
340
|
+
const sumW = [0];
|
|
341
|
+
const sumWV = [0];
|
|
342
|
+
for (let i = 0; i < n; i++) {
|
|
343
|
+
sumW.push(sumW[i] + weight[i]);
|
|
344
|
+
sumWV.push(sumWV[i] + weight[i] * v[i]);
|
|
345
|
+
}
|
|
346
|
+
let lo = 0;
|
|
347
|
+
let hi = 0;
|
|
348
|
+
return v.map((value, i) => {
|
|
349
|
+
while (s[i] - s[lo] > half) {
|
|
350
|
+
lo++;
|
|
351
|
+
}
|
|
352
|
+
while (hi < n && s[hi] - s[i] <= half) {
|
|
353
|
+
hi++;
|
|
354
|
+
}
|
|
355
|
+
const w = sumW[hi] - sumW[lo];
|
|
356
|
+
return w > 0 ? (sumWV[hi] - sumWV[lo]) / w : value;
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
var ONE_THREAD = [0];
|
|
360
|
+
function repeats(passes) {
|
|
361
|
+
for (const p of passes.values()) {
|
|
362
|
+
if (p > 1) {
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
function walkThreads(route, passes, nodePositions, yToX, reach) {
|
|
369
|
+
if (!repeats(passes)) {
|
|
370
|
+
return { span: passes, nodeThreads: /* @__PURE__ */ new Map(), linkThreads: /* @__PURE__ */ new Map() };
|
|
371
|
+
}
|
|
372
|
+
const index = /* @__PURE__ */ new Map();
|
|
373
|
+
const ids = [];
|
|
374
|
+
const widths = [];
|
|
375
|
+
const visits = [];
|
|
376
|
+
const steps = [];
|
|
377
|
+
const stepVisits = [];
|
|
378
|
+
const stepWants = [];
|
|
379
|
+
for (const piece of route) {
|
|
380
|
+
const points = [];
|
|
381
|
+
const nodes = [];
|
|
382
|
+
for (const id of piece) {
|
|
383
|
+
let k = index.get(id);
|
|
384
|
+
if (k === void 0) {
|
|
385
|
+
k = ids.length;
|
|
386
|
+
index.set(id, k);
|
|
387
|
+
ids.push(id);
|
|
388
|
+
widths.push(0);
|
|
389
|
+
visits.push(0);
|
|
390
|
+
}
|
|
391
|
+
nodes.push(k);
|
|
392
|
+
const segments = nodePositions[id];
|
|
393
|
+
const mid = segments?.[Math.floor(segments.length / 2)];
|
|
394
|
+
points.push(
|
|
395
|
+
mid ? { x: mid.x, y: mid.y * yToX } : points.at(-1) ?? { x: 0, y: 0 }
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
const s = arcOf(points);
|
|
399
|
+
const p = piece.map((id) => passes.get(id) ?? 1);
|
|
400
|
+
const most = windowed(s, p, reach / 2, 1);
|
|
401
|
+
const copy = nodes.map((k) => ++visits[k]);
|
|
402
|
+
const n = piece.length;
|
|
403
|
+
const before = new Int32Array(n);
|
|
404
|
+
let last = -1;
|
|
405
|
+
for (let i = 0; i < n; i++) {
|
|
406
|
+
last = p[i] === most[i] ? i : last;
|
|
407
|
+
before[i] = last;
|
|
408
|
+
}
|
|
409
|
+
const after = new Int32Array(n);
|
|
410
|
+
last = -1;
|
|
411
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
412
|
+
last = p[i] === most[i] ? i : last;
|
|
413
|
+
after[i] = last;
|
|
414
|
+
}
|
|
415
|
+
const wants = new Array(n);
|
|
416
|
+
for (let i = 0; i < n; i++) {
|
|
417
|
+
const b = before[i];
|
|
418
|
+
const a = after[i];
|
|
419
|
+
const nearest = b < 0 ? a : a < 0 ? b : s[i] - s[b] <= s[a] - s[i] ? b : a;
|
|
420
|
+
wants[i] = Math.min(copy[nearest], most[i]) - 1;
|
|
421
|
+
widths[nodes[i]] = Math.max(widths[nodes[i]], most[i]);
|
|
422
|
+
}
|
|
423
|
+
steps.push(nodes);
|
|
424
|
+
stepVisits.push(copy);
|
|
425
|
+
stepWants.push(wants);
|
|
426
|
+
}
|
|
427
|
+
const taken = [];
|
|
428
|
+
const threadOf = [];
|
|
429
|
+
const free = (k, want) => {
|
|
430
|
+
const width = widths[k];
|
|
431
|
+
const own = taken[k] ??= /* @__PURE__ */ new Set();
|
|
432
|
+
for (let d = 0; d < width; d++) {
|
|
433
|
+
for (const c of [want - d, want + d]) {
|
|
434
|
+
if (c >= 0 && c < width && !own.has(c)) {
|
|
435
|
+
return c;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return want;
|
|
440
|
+
};
|
|
441
|
+
steps.forEach((nodes, piece) => {
|
|
442
|
+
nodes.forEach((k, i) => {
|
|
443
|
+
if (widths[k] > 1) {
|
|
444
|
+
const t = free(k, stepWants[piece][i]);
|
|
445
|
+
taken[k].add(t);
|
|
446
|
+
(threadOf[k] ??= [])[stepVisits[piece][i]] = t;
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
const span = /* @__PURE__ */ new Map();
|
|
451
|
+
const nodeThreads = /* @__PURE__ */ new Map();
|
|
452
|
+
ids.forEach((id, k) => {
|
|
453
|
+
span.set(id, widths[k]);
|
|
454
|
+
nodeThreads.set(
|
|
455
|
+
id,
|
|
456
|
+
taken[k] ? [...taken[k]].sort((a, b) => a - b) : ONE_THREAD
|
|
457
|
+
);
|
|
458
|
+
});
|
|
459
|
+
const linkThreads = /* @__PURE__ */ new Map();
|
|
460
|
+
steps.forEach((nodes, piece) => {
|
|
461
|
+
const visit = stepVisits[piece];
|
|
462
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
463
|
+
const from = nodes[i - 1];
|
|
464
|
+
const to = nodes[i];
|
|
465
|
+
const pair = [
|
|
466
|
+
threadOf[from]?.[visit[i - 1]] ?? 0,
|
|
467
|
+
threadOf[to]?.[visit[i]] ?? 0
|
|
468
|
+
];
|
|
469
|
+
let ahead = linkThreads.get(ids[from]);
|
|
470
|
+
if (!ahead) {
|
|
471
|
+
ahead = /* @__PURE__ */ new Map();
|
|
472
|
+
linkThreads.set(ids[from], ahead);
|
|
473
|
+
}
|
|
474
|
+
const list = ahead.get(ids[to]);
|
|
475
|
+
if (!list) {
|
|
476
|
+
ahead.set(ids[to], [pair]);
|
|
477
|
+
} else if (!list.some(([a, b]) => a === pair[0] && b === pair[1])) {
|
|
478
|
+
list.push(pair);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
return { span, nodeThreads, linkThreads };
|
|
483
|
+
}
|
|
484
|
+
function bandSpans(lanes, spans, { reach, taper }) {
|
|
485
|
+
const out = /* @__PURE__ */ new Map();
|
|
486
|
+
lanes.runs.forEach((run, r) => {
|
|
487
|
+
const s = arcOf(run);
|
|
488
|
+
const mids = run.flatMap((p, i) => p.middle ? [i] : []);
|
|
489
|
+
const along = mids.map((i) => s[i]);
|
|
490
|
+
const owned = mids.map((i) => lanes.owned[r][i]);
|
|
491
|
+
for (const [lo, hi] of ownedWindows(along, owned, reach / 2 + taper / 2)) {
|
|
492
|
+
const at = along.slice(lo, hi + 1);
|
|
493
|
+
const ids = mids.slice(lo, hi + 1).map((i) => run[i].id);
|
|
494
|
+
const eased = spans.map((own) => {
|
|
495
|
+
const raw = ids.map((id) => own.get(id) ?? 0);
|
|
496
|
+
return raw.every((v) => v === raw[0]) ? { slot: raw, held: raw } : {
|
|
497
|
+
slot: windowMean(at, windowed(at, raw, reach / 2, 1), taper / 2),
|
|
498
|
+
held: windowMean(at, raw, taper / 2, (v) => v > 0)
|
|
499
|
+
};
|
|
500
|
+
});
|
|
501
|
+
ids.forEach((id, i) => {
|
|
502
|
+
if (owned[lo + i]) {
|
|
503
|
+
out.set(id, {
|
|
504
|
+
slot: eased.map((e) => e.slot[i]),
|
|
505
|
+
held: eased.map((e) => e.held[i])
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
return out;
|
|
512
|
+
}
|
|
513
|
+
function liftLanes(walks, nodePositions, yToX, worldPerScreenPx) {
|
|
514
|
+
const threaded = threadsPasses(walks);
|
|
515
|
+
const passes = walks.map((w) => w.mostPasses);
|
|
516
|
+
const reach = THREAD_REACH_PX * worldPerScreenPx;
|
|
517
|
+
const runs = laneRuns(
|
|
518
|
+
walks.flatMap((w) => w.route),
|
|
519
|
+
nodePositions,
|
|
520
|
+
yToX,
|
|
521
|
+
LANE_SMOOTHING_PX * worldPerScreenPx
|
|
522
|
+
);
|
|
523
|
+
const lines = laneLines(
|
|
524
|
+
runs,
|
|
525
|
+
nodePositions,
|
|
526
|
+
yToX,
|
|
527
|
+
LANE_SMOOTHING_PX * worldPerScreenPx,
|
|
528
|
+
bandWidthPx(passes) * (threaded ? bandScale(passes) : 1) * worldPerScreenPx,
|
|
529
|
+
threaded
|
|
530
|
+
);
|
|
531
|
+
const threads = threaded ? walks.map((w) => walkThreads(w.route, w.passes, nodePositions, yToX, reach)) : void 0;
|
|
532
|
+
const spans = threads ? bandSpans(
|
|
533
|
+
runs,
|
|
534
|
+
threads.map((t) => t.span),
|
|
535
|
+
{ reach, taper: THREAD_TAPER_PX * worldPerScreenPx }
|
|
536
|
+
) : void 0;
|
|
537
|
+
const furthestShift = { dx: 0, dy: 0 };
|
|
538
|
+
for (const own of lines.shifts.values()) {
|
|
539
|
+
for (const shift of own) {
|
|
540
|
+
furthestShift.dx = Math.max(furthestShift.dx, Math.abs(shift?.dx ?? 0));
|
|
541
|
+
furthestShift.dy = Math.max(furthestShift.dy, Math.abs(shift?.dy ?? 0));
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return { lines, threads, spans, furthestShift };
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
export {
|
|
548
|
+
THREAD_PX,
|
|
549
|
+
THREAD_GAP_PX,
|
|
550
|
+
SLOT_GAP_PX,
|
|
551
|
+
LANE_SMOOTHING_PX,
|
|
552
|
+
THREAD_REACH_PX,
|
|
553
|
+
THREAD_TAPER_PX,
|
|
554
|
+
MAX_BAND_PX,
|
|
555
|
+
PASSES_KEY,
|
|
556
|
+
threadsPasses,
|
|
557
|
+
THREAD_PITCH_PX,
|
|
558
|
+
slotCentres,
|
|
559
|
+
bandWidthPx,
|
|
560
|
+
bandScale,
|
|
561
|
+
passesKey,
|
|
562
|
+
laneSlideAt,
|
|
563
|
+
laneRuns,
|
|
564
|
+
laneLines,
|
|
565
|
+
walkThreads,
|
|
566
|
+
bandSpans,
|
|
567
|
+
liftLanes
|
|
568
|
+
};
|