@jbrowse/bandage-core 11.7.3 → 11.8.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-ZZZ6NVLW.js → chunk-22ZMVREZ.js} +1 -1
- package/dist/chunks/{chunk-VXKL2ZLR.js → chunk-2H3ALI6L.js} +223 -45
- package/dist/chunks/{chunk-F4CDC72I.js → chunk-2RCKHCF7.js} +1 -1
- package/dist/chunks/{chunk-P276TWPM.js → chunk-3CXZUC2Y.js} +1 -1
- package/dist/chunks/{chunk-VNF236GG.js → chunk-3XYLEK4H.js} +2 -2
- package/dist/chunks/{chunk-PKGPSTRW.js → chunk-4OVIUCSB.js} +2 -2
- package/dist/chunks/{chunk-BLDUBB7X.js → chunk-4YPCUVMC.js} +2 -2
- package/dist/chunks/{chunk-N73RA4DR.js → chunk-6CXLLJ2C.js} +3 -3
- package/dist/chunks/{chunk-4SN7R4UF.js → chunk-6MD4VS2F.js} +1 -1
- package/dist/chunks/{chunk-VACK24HF.js → chunk-735MPLUL.js} +35 -9
- package/dist/chunks/chunk-7GDNDQD7.js +504 -0
- package/dist/chunks/{chunk-2US43KNG.js → chunk-7PEYA3NA.js} +2 -2
- package/dist/chunks/{chunk-XTCIKBWM.js → chunk-AXE3RR7J.js} +1 -1
- package/dist/chunks/{chunk-BXE3SBBN.js → chunk-C3KAPREU.js} +1 -1
- package/dist/chunks/{chunk-QJC63NFR.js → chunk-G3GLGDKV.js} +2 -2
- package/dist/chunks/{chunk-63XWYNID.js → chunk-G4WQBRDS.js} +4 -4
- package/dist/chunks/{chunk-SDI6BVG7.js → chunk-J4PDHWP2.js} +1 -1
- package/dist/chunks/{chunk-GD6RUZU5.js → chunk-JKIAHYAW.js} +14 -8
- package/dist/chunks/{chunk-AJQRKN3X.js → chunk-L2RQV23D.js} +1 -1
- package/dist/chunks/{chunk-EPOPGV5G.js → chunk-LA6JHEVV.js} +1 -1
- package/dist/chunks/{chunk-G7SG4XMK.js → chunk-LODKC7PJ.js} +7 -0
- package/dist/chunks/{chunk-QXBQDHKL.js → chunk-MO4DIRHD.js} +80 -38
- package/dist/chunks/{chunk-IHM43Y7B.js → chunk-N2GAA7RB.js} +1 -1
- package/dist/chunks/{chunk-6RB5ZQ34.js → chunk-N4AJV4GH.js} +25 -14
- package/dist/chunks/{chunk-LH436HS2.js → chunk-NHOXLUIL.js} +2 -2
- package/dist/chunks/{chunk-QLLKALSH.js → chunk-NQWEYJDD.js} +1 -1
- package/dist/chunks/{chunk-AWJJSA7D.js → chunk-OIOAK7BS.js} +1 -1
- package/dist/chunks/{chunk-UTDMFVC7.js → chunk-R3NPBUKT.js} +8 -8
- package/dist/chunks/{chunk-4KOYS5WR.js → chunk-R4XCTRIU.js} +1 -1
- package/dist/chunks/{chunk-XKPXOIWD.js → chunk-S3ICPPXC.js} +3 -3
- package/dist/chunks/{chunk-K4KVAMEF.js → chunk-S4CH434Z.js} +1 -1
- package/dist/chunks/{chunk-HMEAGAGC.js → chunk-S7BCZVRK.js} +2 -2
- package/dist/chunks/{chunk-SG7VQB4F.js → chunk-SDDMV2MM.js} +1 -1
- package/dist/chunks/{chunk-KMLATBNT.js → chunk-SIXFXXRI.js} +3 -3
- package/dist/chunks/{chunk-T3MEG5D3.js → chunk-U6JPSXJ3.js} +90 -21
- package/dist/chunks/{chunk-SENPGW4O.js → chunk-VCUOYVLC.js} +1 -1
- package/dist/chunks/{chunk-33KUO34R.js → chunk-VSRRIFRF.js} +1 -1
- package/dist/chunks/{chunk-BFUS3ENH.js → chunk-X5ULFAMM.js} +2 -2
- package/dist/chunks/{chunk-W5YRBEWQ.js → chunk-XOIYBEU7.js} +1 -1
- package/dist/chunks/{chunk-CL6ELLVF.js → chunk-YEDZ37PH.js} +1 -1
- package/dist/chunks/{chunk-Z4HXW6ZH.js → chunk-YF6T33PU.js} +10 -2
- package/dist/chunks/{chunk-2VMKQKVO.js → chunk-YIWN32ZX.js} +1 -1
- package/dist/chunks/{chunk-2T3V6Z3M.js → chunk-ZKIH2USC.js} +1 -1
- 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 +44 -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 +60 -0
- package/dist/types/util/geometry.d.ts +13 -1
- package/dist/types/version.d.ts +1 -1
- package/dist/types/walkEncoding.d.ts +7 -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 +9 -8
- package/dist/walkHighlight.js +10 -9
- package/dist/walkKey.js +5 -5
- package/package.json +1 -1
|
@@ -97,7 +97,8 @@ function visibleStretches(curves, lengths, bounds, yToX) {
|
|
|
97
97
|
});
|
|
98
98
|
return stretches;
|
|
99
99
|
}
|
|
100
|
-
|
|
100
|
+
var dashCurves = (curves, dashWorld, yToX = 1, bounds) => dashSpans(curves, dashWorld, yToX, bounds).map((d) => d.curves);
|
|
101
|
+
function dashSpans(curves, dashWorld, yToX = 1, bounds) {
|
|
101
102
|
const lengths = curves.map(
|
|
102
103
|
(c) => Math.hypot(c.cx0 - c.x0, (c.cy0 - c.y0) * yToX) + Math.hypot(c.cx1 - c.cx0, (c.cy1 - c.cy0) * yToX) + Math.hypot(c.x1 - c.cx1, (c.y1 - c.cy1) * yToX)
|
|
103
104
|
);
|
|
@@ -122,27 +123,49 @@ function dashCurves(curves, dashWorld, yToX = 1, bounds) {
|
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
const dash = [];
|
|
126
|
+
const along = [];
|
|
125
127
|
let start = 0;
|
|
126
128
|
curves.forEach((c, k) => {
|
|
127
129
|
const length = lengths[k];
|
|
128
130
|
const end = start + length;
|
|
129
131
|
if (length > 0 && end > from && start < to) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
Math.min(1, (to - start) / length)
|
|
135
|
-
)
|
|
136
|
-
);
|
|
132
|
+
const t0 = Math.max(0, (from - start) / length);
|
|
133
|
+
const t1 = Math.min(1, (to - start) / length);
|
|
134
|
+
dash.push(subCurve(c, t0, t1));
|
|
135
|
+
along.push((k + t0) / curves.length, (k + t1) / curves.length);
|
|
137
136
|
}
|
|
138
137
|
start = end;
|
|
139
138
|
});
|
|
140
139
|
if (dash.length) {
|
|
141
|
-
out.push(
|
|
140
|
+
out.push({
|
|
141
|
+
curves: dash,
|
|
142
|
+
startAlong: along[0],
|
|
143
|
+
endAlong: along.at(-1)
|
|
144
|
+
});
|
|
142
145
|
}
|
|
143
146
|
}
|
|
144
147
|
return out;
|
|
145
148
|
}
|
|
149
|
+
var slideAt = (from, to, along) => ({
|
|
150
|
+
dx: from.dx + (to.dx - from.dx) * along,
|
|
151
|
+
dy: from.dy + (to.dy - from.dy) * along
|
|
152
|
+
});
|
|
153
|
+
function slideCurves(curves, from, to) {
|
|
154
|
+
return curves.map((c, i) => {
|
|
155
|
+
const a = slideAt(from, to, i / curves.length);
|
|
156
|
+
const b = slideAt(from, to, (i + 1) / curves.length);
|
|
157
|
+
return {
|
|
158
|
+
x0: c.x0 + a.dx,
|
|
159
|
+
y0: c.y0 + a.dy,
|
|
160
|
+
cx0: c.cx0 + a.dx,
|
|
161
|
+
cy0: c.cy0 + a.dy,
|
|
162
|
+
cx1: c.cx1 + b.dx,
|
|
163
|
+
cy1: c.cy1 + b.dy,
|
|
164
|
+
x1: c.x1 + b.dx,
|
|
165
|
+
y1: c.y1 + b.dy
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
}
|
|
146
169
|
function translateCurves(curves, dx, dy) {
|
|
147
170
|
const out = new Array(curves.length);
|
|
148
171
|
for (let i = 0; i < curves.length; i++) {
|
|
@@ -555,6 +578,9 @@ export {
|
|
|
555
578
|
curveMidpoint,
|
|
556
579
|
curveBounds,
|
|
557
580
|
dashCurves,
|
|
581
|
+
dashSpans,
|
|
582
|
+
slideAt,
|
|
583
|
+
slideCurves,
|
|
558
584
|
translateCurves,
|
|
559
585
|
ownStrand,
|
|
560
586
|
strandSides,
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import {
|
|
2
|
+
slideAt
|
|
3
|
+
} from "./chunk-735MPLUL.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 = [0];
|
|
57
|
+
for (let i = 1; i < run.length; i++) {
|
|
58
|
+
s.push(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
|
+
const first = !!nodePositions[id]?.length && !seen.has(id);
|
|
67
|
+
seen.add(id);
|
|
68
|
+
return first;
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
const gap = (a, b) => Math.hypot(b.x - a.x, b.y * yToX - a.y);
|
|
72
|
+
const runs = [];
|
|
73
|
+
const owned = [];
|
|
74
|
+
let run = [];
|
|
75
|
+
let mine = [];
|
|
76
|
+
const breakRun = () => {
|
|
77
|
+
if (run.length) {
|
|
78
|
+
runs.push(run);
|
|
79
|
+
owned.push(mine);
|
|
80
|
+
}
|
|
81
|
+
run = [];
|
|
82
|
+
mine = [];
|
|
83
|
+
};
|
|
84
|
+
routes.forEach((route, r) => {
|
|
85
|
+
if (!owns[r].includes(true)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
route.forEach((id, step) => {
|
|
89
|
+
const segments = nodePositions[id];
|
|
90
|
+
if (!segments?.length) {
|
|
91
|
+
breakRun();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const first = segments[0];
|
|
95
|
+
const last = segments.at(-1);
|
|
96
|
+
const prev = run.at(-1);
|
|
97
|
+
const next = nodePositions[route[step + 1] ?? ""];
|
|
98
|
+
const toNext = (p) => {
|
|
99
|
+
const at = { x: p.x, y: p.y * yToX };
|
|
100
|
+
return next?.length ? Math.min(gap(at, next[0]), gap(at, next.at(-1))) : 0;
|
|
101
|
+
};
|
|
102
|
+
const forward = prev ? gap(prev, first) <= gap(prev, last) : toNext(last) <= toNext(first);
|
|
103
|
+
if (prev && gap(prev, forward ? first : last) > jump) {
|
|
104
|
+
breakRun();
|
|
105
|
+
}
|
|
106
|
+
const middle = Math.floor(segments.length / 2);
|
|
107
|
+
for (let k = 0; k < segments.length; k++) {
|
|
108
|
+
const index = forward ? k : segments.length - 1 - k;
|
|
109
|
+
const p = segments[index];
|
|
110
|
+
run.push({
|
|
111
|
+
x: p.x,
|
|
112
|
+
y: p.y * yToX,
|
|
113
|
+
id,
|
|
114
|
+
index,
|
|
115
|
+
middle: index === middle,
|
|
116
|
+
forward
|
|
117
|
+
});
|
|
118
|
+
mine.push(owns[r][step]);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
breakRun();
|
|
122
|
+
});
|
|
123
|
+
return { runs, owned };
|
|
124
|
+
}
|
|
125
|
+
function ownedWindows(s, owned, margin) {
|
|
126
|
+
const windows = [];
|
|
127
|
+
for (let i = 0; i < owned.length; i++) {
|
|
128
|
+
if (!owned[i]) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
let j = i;
|
|
132
|
+
while (j + 1 < owned.length && owned[j + 1]) {
|
|
133
|
+
j++;
|
|
134
|
+
}
|
|
135
|
+
const last = windows.at(-1);
|
|
136
|
+
const floor = last ? last[1] : 0;
|
|
137
|
+
let lo = i;
|
|
138
|
+
while (lo > floor && s[i] - s[lo - 1] <= margin) {
|
|
139
|
+
lo--;
|
|
140
|
+
}
|
|
141
|
+
let hi = j;
|
|
142
|
+
while (hi + 1 < s.length && s[hi + 1] - s[j] <= margin) {
|
|
143
|
+
hi++;
|
|
144
|
+
}
|
|
145
|
+
if (last && lo <= last[1] + 1) {
|
|
146
|
+
last[1] = hi;
|
|
147
|
+
} else {
|
|
148
|
+
windows.push([lo, hi]);
|
|
149
|
+
}
|
|
150
|
+
i = j;
|
|
151
|
+
}
|
|
152
|
+
return windows;
|
|
153
|
+
}
|
|
154
|
+
function smoothedNormals(run, reach) {
|
|
155
|
+
const s = arcOf(run);
|
|
156
|
+
const end = s.at(-1);
|
|
157
|
+
const pointAt = (at, j) => {
|
|
158
|
+
const a = run[j];
|
|
159
|
+
const b = run[Math.min(j + 1, run.length - 1)];
|
|
160
|
+
const span = s[Math.min(j + 1, run.length - 1)] - s[j];
|
|
161
|
+
const f = span > 0 ? (at - s[j]) / span : 0;
|
|
162
|
+
return { x: a.x + (b.x - a.x) * f, y: a.y + (b.y - a.y) * f };
|
|
163
|
+
};
|
|
164
|
+
let lo = 0;
|
|
165
|
+
let hi = 0;
|
|
166
|
+
return run.map((_, i) => {
|
|
167
|
+
const back = Math.max(0, s[i] - reach / 2);
|
|
168
|
+
const ahead = Math.min(end, s[i] + reach / 2);
|
|
169
|
+
while (lo < run.length - 1 && s[lo + 1] <= back) {
|
|
170
|
+
lo++;
|
|
171
|
+
}
|
|
172
|
+
while (hi < run.length - 1 && s[hi + 1] <= ahead) {
|
|
173
|
+
hi++;
|
|
174
|
+
}
|
|
175
|
+
const a = pointAt(back, lo);
|
|
176
|
+
const b = pointAt(ahead, hi);
|
|
177
|
+
const len = gapOf(a, b);
|
|
178
|
+
return len > 0 ? { nx: -(b.y - a.y) / len, ny: (b.x - a.x) / len } : void 0;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function cutCurls(run, close, length) {
|
|
182
|
+
const s = arcOf(run);
|
|
183
|
+
const out = run.slice();
|
|
184
|
+
const picks = [];
|
|
185
|
+
s.forEach((at, k) => {
|
|
186
|
+
const last = picks.at(-1);
|
|
187
|
+
if (last === void 0 || at - s[last] >= close / 4) {
|
|
188
|
+
picks.push(k);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
if (picks.at(-1) !== run.length - 1) {
|
|
192
|
+
picks.push(run.length - 1);
|
|
193
|
+
}
|
|
194
|
+
let a = 0;
|
|
195
|
+
while (a < picks.length) {
|
|
196
|
+
const i = picks[a];
|
|
197
|
+
let to = -1;
|
|
198
|
+
for (let b = a + 1; b < picks.length && s[picks[b]] - s[i] <= length; b++) {
|
|
199
|
+
const j2 = picks[b];
|
|
200
|
+
if (s[j2] - s[i] > Math.PI * close && gapOf(run[i], run[j2]) <= close) {
|
|
201
|
+
to = b;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (to < 0) {
|
|
205
|
+
a++;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const j = picks[to];
|
|
209
|
+
const p = run[i];
|
|
210
|
+
const q = run[j];
|
|
211
|
+
for (let k = i + 1; k < j; k++) {
|
|
212
|
+
const f = (s[k] - s[i]) / (s[j] - s[i]);
|
|
213
|
+
out[k] = {
|
|
214
|
+
...run[k],
|
|
215
|
+
x: p.x + (q.x - p.x) * f,
|
|
216
|
+
y: p.y + (q.y - p.y) * f
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
a = to;
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
function rounded(run, half) {
|
|
224
|
+
const s = arcOf(run);
|
|
225
|
+
const end = s.at(-1);
|
|
226
|
+
const sums = [{ w: 0, x: 0, y: 0 }];
|
|
227
|
+
run.forEach((p, i) => {
|
|
228
|
+
const w = (s[Math.min(i + 1, run.length - 1)] - s[Math.max(i - 1, 0)]) / 2;
|
|
229
|
+
const last = sums[i];
|
|
230
|
+
sums.push({ w: last.w + w, x: last.x + w * p.x, y: last.y + w * p.y });
|
|
231
|
+
});
|
|
232
|
+
const firstAtOrAfter = (at) => {
|
|
233
|
+
let lo = 0;
|
|
234
|
+
let hi = s.length;
|
|
235
|
+
while (lo < hi) {
|
|
236
|
+
const mid = lo + hi >> 1;
|
|
237
|
+
if (s[mid] < at) {
|
|
238
|
+
lo = mid + 1;
|
|
239
|
+
} else {
|
|
240
|
+
hi = mid;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return lo;
|
|
244
|
+
};
|
|
245
|
+
return run.map((p, i) => {
|
|
246
|
+
const reach = Math.min(half, s[i], end - s[i]);
|
|
247
|
+
const lo = firstAtOrAfter(s[i] - reach);
|
|
248
|
+
const hi = firstAtOrAfter(s[i] + reach + 1e-9 * (1 + end));
|
|
249
|
+
const w = sums[hi].w - sums[lo].w;
|
|
250
|
+
return w > 0 ? {
|
|
251
|
+
x: (sums[hi].x - sums[lo].x) / w,
|
|
252
|
+
y: (sums[hi].y - sums[lo].y) / w
|
|
253
|
+
} : { x: p.x, y: p.y };
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
function fieldSetter(field, nodePositions) {
|
|
257
|
+
return ({ id, index }, value) => {
|
|
258
|
+
const own = field.get(id) ?? new Array(nodePositions[id].length);
|
|
259
|
+
own[index] = value;
|
|
260
|
+
field.set(id, own);
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function laneLines(lanes, nodePositions, yToX, reach, band, smooth = true) {
|
|
264
|
+
const turn = Math.max(reach, 2 * band);
|
|
265
|
+
const curl = Math.PI * band;
|
|
266
|
+
const margin = smooth ? curl + band / 2 + turn / 2 : turn / 2;
|
|
267
|
+
const normals = /* @__PURE__ */ new Map();
|
|
268
|
+
const shifts = /* @__PURE__ */ new Map();
|
|
269
|
+
const setNormal = fieldSetter(normals, nodePositions);
|
|
270
|
+
const setShift = fieldSetter(shifts, nodePositions);
|
|
271
|
+
lanes.runs.forEach((run, r) => {
|
|
272
|
+
const owned = lanes.owned[r];
|
|
273
|
+
const pieces = ownedWindows(arcOf(run), owned, margin).map(([lo, hi]) => {
|
|
274
|
+
const points = run.slice(lo, hi + 1);
|
|
275
|
+
const line = smooth ? rounded(cutCurls(points, band / 2, curl), band / 2) : points;
|
|
276
|
+
return { lo, points, line, normals: smoothedNormals(line, turn) };
|
|
277
|
+
});
|
|
278
|
+
let agree = 0;
|
|
279
|
+
let along = 0;
|
|
280
|
+
for (const piece of pieces) {
|
|
281
|
+
piece.points.forEach((p, i) => {
|
|
282
|
+
const theirs = normals.get(p.id)?.[p.index];
|
|
283
|
+
const mine = piece.normals[i];
|
|
284
|
+
if (theirs && mine) {
|
|
285
|
+
agree += theirs.nx * mine.nx + theirs.ny * mine.ny;
|
|
286
|
+
}
|
|
287
|
+
along += p.forward ? 1 : -1;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
const sign = (agree === 0 ? along : agree) < 0 ? -1 : 1;
|
|
291
|
+
for (const { lo, points, line, normals: own } of pieces) {
|
|
292
|
+
points.forEach((p, i) => {
|
|
293
|
+
if (!owned[lo + i]) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const n = own[i];
|
|
297
|
+
if (n) {
|
|
298
|
+
setNormal(p, sign > 0 ? n : { nx: -n.nx, ny: -n.ny });
|
|
299
|
+
}
|
|
300
|
+
if (smooth) {
|
|
301
|
+
setShift(p, {
|
|
302
|
+
dx: line[i].x - p.x,
|
|
303
|
+
dy: (line[i].y - p.y) / yToX
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
return { normals, shifts };
|
|
310
|
+
}
|
|
311
|
+
function windowed(s, v, half, sign) {
|
|
312
|
+
const out = new Array(v.length);
|
|
313
|
+
const deque = [];
|
|
314
|
+
let head = 0;
|
|
315
|
+
let next = 0;
|
|
316
|
+
for (let i = 0; i < v.length; i++) {
|
|
317
|
+
while (next < v.length && s[next] - s[i] <= half) {
|
|
318
|
+
while (deque.length > head && sign * v[deque.at(-1)] <= sign * v[next]) {
|
|
319
|
+
deque.pop();
|
|
320
|
+
}
|
|
321
|
+
deque.push(next++);
|
|
322
|
+
}
|
|
323
|
+
while (s[i] - s[deque[head]] > half) {
|
|
324
|
+
head++;
|
|
325
|
+
}
|
|
326
|
+
out[i] = v[deque[head]];
|
|
327
|
+
}
|
|
328
|
+
return out;
|
|
329
|
+
}
|
|
330
|
+
function windowMean(s, v, half, counted = () => true) {
|
|
331
|
+
const n = v.length;
|
|
332
|
+
const weight = s.map(
|
|
333
|
+
(_, i) => !counted(v[i]) ? 0 : n > 1 ? (s[Math.min(i + 1, n - 1)] - s[Math.max(i - 1, 0)]) / 2 : 1
|
|
334
|
+
);
|
|
335
|
+
const sumW = [0];
|
|
336
|
+
const sumWV = [0];
|
|
337
|
+
for (let i = 0; i < n; i++) {
|
|
338
|
+
sumW.push(sumW[i] + weight[i]);
|
|
339
|
+
sumWV.push(sumWV[i] + weight[i] * v[i]);
|
|
340
|
+
}
|
|
341
|
+
let lo = 0;
|
|
342
|
+
let hi = 0;
|
|
343
|
+
return v.map((value, i) => {
|
|
344
|
+
while (s[i] - s[lo] > half) {
|
|
345
|
+
lo++;
|
|
346
|
+
}
|
|
347
|
+
while (hi < n && s[hi] - s[i] <= half) {
|
|
348
|
+
hi++;
|
|
349
|
+
}
|
|
350
|
+
const w = sumW[hi] - sumW[lo];
|
|
351
|
+
return w > 0 ? (sumWV[hi] - sumWV[lo]) / w : value;
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
function walkThreads(route, passes, nodePositions, yToX, reach) {
|
|
355
|
+
if (![...passes.values()].some((p) => p > 1)) {
|
|
356
|
+
return { span: passes, nodeThreads: /* @__PURE__ */ new Map(), linkThreads: /* @__PURE__ */ new Map() };
|
|
357
|
+
}
|
|
358
|
+
const span = /* @__PURE__ */ new Map();
|
|
359
|
+
const visits = /* @__PURE__ */ new Map();
|
|
360
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
361
|
+
const steps = [];
|
|
362
|
+
for (const piece of route) {
|
|
363
|
+
const points = [];
|
|
364
|
+
for (const id of piece) {
|
|
365
|
+
const segments = nodePositions[id];
|
|
366
|
+
const mid = segments?.[Math.floor(segments.length / 2)];
|
|
367
|
+
points.push(
|
|
368
|
+
mid ? { x: mid.x, y: mid.y * yToX } : points.at(-1) ?? { x: 0, y: 0 }
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
const s = arcOf(points);
|
|
372
|
+
const p = piece.map((id) => passes.get(id) ?? 1);
|
|
373
|
+
const most = windowed(s, p, reach / 2, 1);
|
|
374
|
+
const copy = piece.map((id) => {
|
|
375
|
+
const visit = (visits.get(id) ?? 0) + 1;
|
|
376
|
+
visits.set(id, visit);
|
|
377
|
+
return visit;
|
|
378
|
+
});
|
|
379
|
+
const full = p.map((v, i) => v === most[i]);
|
|
380
|
+
const before = [];
|
|
381
|
+
let last = -1;
|
|
382
|
+
for (let i = 0; i < piece.length; i++) {
|
|
383
|
+
last = full[i] ? i : last;
|
|
384
|
+
before.push(last);
|
|
385
|
+
}
|
|
386
|
+
const after = new Array(piece.length);
|
|
387
|
+
last = -1;
|
|
388
|
+
for (let i = piece.length - 1; i >= 0; i--) {
|
|
389
|
+
last = full[i] ? i : last;
|
|
390
|
+
after[i] = last;
|
|
391
|
+
}
|
|
392
|
+
const own = [];
|
|
393
|
+
piece.forEach((id, i) => {
|
|
394
|
+
const b = before[i];
|
|
395
|
+
const a = after[i];
|
|
396
|
+
const nearest = b < 0 ? a : a < 0 ? b : s[i] - s[b] <= s[a] - s[i] ? b : a;
|
|
397
|
+
const thread = Math.min(copy[nearest], most[i]) - 1;
|
|
398
|
+
span.set(id, Math.max(span.get(id) ?? 0, most[i]));
|
|
399
|
+
const list = wanted.get(id) ?? [];
|
|
400
|
+
list.push({ visit: copy[i], thread });
|
|
401
|
+
wanted.set(id, list);
|
|
402
|
+
own.push({ id, visit: copy[i] });
|
|
403
|
+
});
|
|
404
|
+
steps.push(own);
|
|
405
|
+
}
|
|
406
|
+
const threadOf = /* @__PURE__ */ new Map();
|
|
407
|
+
const nodeThreads = /* @__PURE__ */ new Map();
|
|
408
|
+
for (const [id, list] of wanted) {
|
|
409
|
+
const width = span.get(id);
|
|
410
|
+
const taken = /* @__PURE__ */ new Set();
|
|
411
|
+
const free = (want) => {
|
|
412
|
+
for (let d = 0; d < width; d++) {
|
|
413
|
+
for (const c of [want - d, want + d]) {
|
|
414
|
+
if (c >= 0 && c < width && !taken.has(c)) {
|
|
415
|
+
return c;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return want;
|
|
420
|
+
};
|
|
421
|
+
const byVisit = [];
|
|
422
|
+
for (const { visit, thread } of list) {
|
|
423
|
+
const t = free(thread);
|
|
424
|
+
taken.add(t);
|
|
425
|
+
byVisit[visit] = t;
|
|
426
|
+
}
|
|
427
|
+
threadOf.set(id, byVisit);
|
|
428
|
+
nodeThreads.set(
|
|
429
|
+
id,
|
|
430
|
+
[...taken].sort((a, b) => a - b)
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
const linkThreads = /* @__PURE__ */ new Map();
|
|
434
|
+
for (const own of steps) {
|
|
435
|
+
own.forEach((cur, i) => {
|
|
436
|
+
const prev = own[i - 1];
|
|
437
|
+
if (!prev) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const key = `${prev.id}>${cur.id}`;
|
|
441
|
+
const pair = [
|
|
442
|
+
threadOf.get(prev.id)[prev.visit],
|
|
443
|
+
threadOf.get(cur.id)[cur.visit]
|
|
444
|
+
];
|
|
445
|
+
const list = linkThreads.get(key) ?? [];
|
|
446
|
+
if (!list.some(([a, b]) => a === pair[0] && b === pair[1])) {
|
|
447
|
+
list.push(pair);
|
|
448
|
+
}
|
|
449
|
+
linkThreads.set(key, list);
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
return { span, nodeThreads, linkThreads };
|
|
453
|
+
}
|
|
454
|
+
function bandSpans(lanes, spans, { reach, taper }) {
|
|
455
|
+
const out = /* @__PURE__ */ new Map();
|
|
456
|
+
lanes.runs.forEach((run, r) => {
|
|
457
|
+
const s = arcOf(run);
|
|
458
|
+
const mids = run.flatMap((p, i) => p.middle ? [i] : []);
|
|
459
|
+
const along = mids.map((i) => s[i]);
|
|
460
|
+
const owned = mids.map((i) => lanes.owned[r][i]);
|
|
461
|
+
for (const [lo, hi] of ownedWindows(along, owned, reach / 2 + taper / 2)) {
|
|
462
|
+
const at = along.slice(lo, hi + 1);
|
|
463
|
+
const ids = mids.slice(lo, hi + 1).map((i) => run[i].id);
|
|
464
|
+
const eased = spans.map((own) => {
|
|
465
|
+
const raw = ids.map((id) => own.get(id) ?? 0);
|
|
466
|
+
return raw.every((v) => v === raw[0]) ? { slot: raw, held: raw } : {
|
|
467
|
+
slot: windowMean(at, windowed(at, raw, reach / 2, 1), taper / 2),
|
|
468
|
+
held: windowMean(at, raw, taper / 2, (v) => v > 0)
|
|
469
|
+
};
|
|
470
|
+
});
|
|
471
|
+
ids.forEach((id, i) => {
|
|
472
|
+
if (owned[lo + i]) {
|
|
473
|
+
out.set(id, {
|
|
474
|
+
slot: eased.map((e) => e.slot[i]),
|
|
475
|
+
held: eased.map((e) => e.held[i])
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
return out;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export {
|
|
485
|
+
THREAD_PX,
|
|
486
|
+
THREAD_GAP_PX,
|
|
487
|
+
SLOT_GAP_PX,
|
|
488
|
+
LANE_SMOOTHING_PX,
|
|
489
|
+
THREAD_REACH_PX,
|
|
490
|
+
THREAD_TAPER_PX,
|
|
491
|
+
MAX_BAND_PX,
|
|
492
|
+
PASSES_KEY,
|
|
493
|
+
threadsPasses,
|
|
494
|
+
THREAD_PITCH_PX,
|
|
495
|
+
slotCentres,
|
|
496
|
+
bandWidthPx,
|
|
497
|
+
bandScale,
|
|
498
|
+
passesKey,
|
|
499
|
+
laneSlideAt,
|
|
500
|
+
laneRuns,
|
|
501
|
+
laneLines,
|
|
502
|
+
walkThreads,
|
|
503
|
+
bandSpans
|
|
504
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
baseEdgeCurves
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-VSRRIFRF.js";
|
|
4
4
|
import {
|
|
5
5
|
curveBounds,
|
|
6
6
|
pathRibbonOffsets
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-735MPLUL.js";
|
|
8
8
|
|
|
9
9
|
// src/util/SpatialIndex.ts
|
|
10
10
|
var FALLBACK_CELL_SIZE = 50;
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
stepName,
|
|
3
3
|
tubeMapReads
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-J4PDHWP2.js";
|
|
5
5
|
import {
|
|
6
6
|
referenceWalkName
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-LODKC7PJ.js";
|
|
8
8
|
import {
|
|
9
9
|
pathCssColor,
|
|
10
10
|
pathGreyCssColor
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-JKIAHYAW.js";
|
|
12
12
|
import {
|
|
13
13
|
pathOrigin
|
|
14
14
|
} from "./chunk-3YBP46PB.js";
|
|
15
15
|
import {
|
|
16
16
|
ownStrand,
|
|
17
17
|
strandSides
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-735MPLUL.js";
|
|
19
19
|
import {
|
|
20
20
|
groupBy
|
|
21
21
|
} from "./chunk-BSOZW3FC.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
groupDigits
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-6CXLLJ2C.js";
|
|
4
4
|
import {
|
|
5
5
|
pathOrigin
|
|
6
6
|
} from "./chunk-3YBP46PB.js";
|
|
@@ -52,18 +52,24 @@ function pathLegend(paths, colors) {
|
|
|
52
52
|
firstOf.set(path.name, i);
|
|
53
53
|
}
|
|
54
54
|
});
|
|
55
|
-
const entries = [...firstOf].map(([
|
|
55
|
+
const entries = [...firstOf].map(([name2, i]) => ({ name: name2, i }));
|
|
56
56
|
const named = entries.map((e) => labelTiers(e.name));
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
const name = (t) => entries.map((_, k) => named[k][t]);
|
|
58
|
+
const offset = (t) => entries.map((e, k) => offsetTiers(e.name, named[k])[t]);
|
|
59
|
+
const candidates = [
|
|
60
|
+
name(0),
|
|
61
|
+
name(1),
|
|
62
|
+
offset(0),
|
|
63
|
+
name(2),
|
|
64
|
+
offset(1),
|
|
65
|
+
offset(2)
|
|
66
|
+
];
|
|
61
67
|
const distinct = candidates.find(
|
|
62
68
|
(labels2) => new Set(labels2).size === entries.length
|
|
63
69
|
);
|
|
64
70
|
const labels = distinct ?? candidates.at(-1);
|
|
65
|
-
return entries.map(({ name, i }, k) => ({
|
|
66
|
-
name,
|
|
71
|
+
return entries.map(({ name: name2, i }, k) => ({
|
|
72
|
+
name: name2,
|
|
67
73
|
label: labels[k],
|
|
68
74
|
color: colors?.[i] ?? pathCssColor(i, paths.length)
|
|
69
75
|
}));
|