@jbrowse/bandage-core 6.1.0 → 6.2.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/dist/chunks/{chunk-SMQ545MU.js → chunk-3GMVZMKD.js} +1 -1
- package/dist/chunks/{chunk-KKU6JRJC.js → chunk-5HF753RL.js} +14 -14
- package/dist/chunks/{chunk-X4VVPLI5.js → chunk-6CO564AN.js} +1 -1
- package/dist/chunks/{chunk-V5BPIOLS.js → chunk-7R2UGY4I.js} +1 -1
- package/dist/chunks/{chunk-ZKGM7VLA.js → chunk-A7VOULBP.js} +1 -1
- package/dist/chunks/{chunk-ZHCDYSA5.js → chunk-GD7FYNGS.js} +1 -1
- package/dist/chunks/{chunk-LPFN4POU.js → chunk-HE3NRWH7.js} +1 -1
- package/dist/chunks/chunk-L53SXJZI.js +22 -0
- package/dist/chunks/{chunk-AMHZMY6E.js → chunk-M6SAUIYR.js} +1 -1
- package/dist/chunks/chunk-O4TGF4WS.js +503 -0
- package/dist/chunks/{chunk-ZZA2AFB4.js → chunk-P2KU2TXS.js} +1 -1
- package/dist/chunks/{chunk-C6USNTTP.js → chunk-STNG3Y5X.js} +25 -4
- package/dist/chunks/{chunk-6UACM7VU.js → chunk-YFRTTBCZ.js} +4 -3
- package/dist/chunks/{chunk-PJESIRDU.js → chunk-YQZWW2V6.js} +2 -2
- package/dist/cli/figure.js +22 -25
- package/dist/figure.js +14 -14
- package/dist/index.js +45 -35
- package/dist/layout/deletionRoutes.js +1 -1
- package/dist/layout/stressEngine.js +11 -0
- package/dist/layout/walkRowDraw.js +5 -5
- package/dist/layout/walkStrip.js +6 -6
- package/dist/layoutEngines.js +9 -0
- package/dist/layoutModes.js +2 -2
- package/dist/pipeline.js +3 -3
- package/dist/referenceRampCss.js +4 -4
- package/dist/referenceStrip.js +4 -4
- package/dist/renderer/GeometryBuilder.js +3 -3
- package/dist/tubeMap/nodeColors.js +4 -4
- package/dist/types/cli/figure.d.ts +2 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/layout/stressEngine.d.ts +19 -0
- package/dist/types/layoutEngines.d.ts +2 -0
- package/dist/types/pipeline.d.ts +2 -0
- package/dist/types/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/viewport.js +4 -4
- package/dist/walkEncoding.js +4 -4
- package/dist/walkHighlight.js +5 -5
- package/package.json +1 -1
- package/dist/chunks/{chunk-RX5CSWJW.js → chunk-VUBLSA6P.js} +4 -4
|
@@ -5,36 +5,36 @@ import {
|
|
|
5
5
|
stripGeneGaps,
|
|
6
6
|
walkStripFrame,
|
|
7
7
|
walkStripLabelsTree
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-7R2UGY4I.js";
|
|
9
9
|
import {
|
|
10
10
|
walkRowsKey,
|
|
11
11
|
walkRowsKeyTree,
|
|
12
12
|
walkRowsTree
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-YQZWW2V6.js";
|
|
14
14
|
import {
|
|
15
15
|
Canvas2DRenderer
|
|
16
16
|
} from "./chunk-CH3DLBUT.js";
|
|
17
|
-
import {
|
|
18
|
-
genePins
|
|
19
|
-
} from "./chunk-BUBZ32IC.js";
|
|
20
17
|
import {
|
|
21
18
|
EXON_COLOR,
|
|
22
19
|
exonOutlineTree,
|
|
23
20
|
exonStretches
|
|
24
21
|
} from "./chunk-3S3YVOJ7.js";
|
|
22
|
+
import {
|
|
23
|
+
genePins
|
|
24
|
+
} from "./chunk-BUBZ32IC.js";
|
|
25
25
|
import {
|
|
26
26
|
version
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-HE3NRWH7.js";
|
|
28
28
|
import {
|
|
29
29
|
axisScaleOf
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-M6SAUIYR.js";
|
|
31
31
|
import {
|
|
32
32
|
facetLifts,
|
|
33
33
|
walkLift
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-A7VOULBP.js";
|
|
35
35
|
import {
|
|
36
36
|
encodingStops
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-6CO564AN.js";
|
|
38
38
|
import {
|
|
39
39
|
rangeText,
|
|
40
40
|
walkKey
|
|
@@ -43,11 +43,14 @@ import {
|
|
|
43
43
|
FIT_PADDING,
|
|
44
44
|
drawingBounds,
|
|
45
45
|
fitTransform
|
|
46
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-YFRTTBCZ.js";
|
|
47
47
|
import {
|
|
48
48
|
buildGeometry,
|
|
49
49
|
computeReferenceRamp
|
|
50
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-VUBLSA6P.js";
|
|
51
|
+
import {
|
|
52
|
+
nodeInk
|
|
53
|
+
} from "./chunk-PBENCR5L.js";
|
|
51
54
|
import {
|
|
52
55
|
geneLabelCandidates
|
|
53
56
|
} from "./chunk-DKOB2OPC.js";
|
|
@@ -67,9 +70,6 @@ import {
|
|
|
67
70
|
occupancy,
|
|
68
71
|
placeLabels
|
|
69
72
|
} from "./chunk-AB35TJST.js";
|
|
70
|
-
import {
|
|
71
|
-
nodeInk
|
|
72
|
-
} from "./chunk-PBENCR5L.js";
|
|
73
73
|
import {
|
|
74
74
|
resolveColorScheme
|
|
75
75
|
} from "./chunk-JKJWSY4V.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
stressLayout
|
|
3
|
+
} from "./chunk-O4TGF4WS.js";
|
|
4
|
+
import {
|
|
5
|
+
loadBandage
|
|
6
|
+
} from "./chunk-7XHHVYG7.js";
|
|
7
|
+
|
|
8
|
+
// src/layoutEngines.ts
|
|
9
|
+
var layoutEngine = async (request) => {
|
|
10
|
+
const start = performance.now();
|
|
11
|
+
if (request.options.engine === "stress") {
|
|
12
|
+
const result2 = stressLayout(request);
|
|
13
|
+
return { result: result2, duration: performance.now() - start };
|
|
14
|
+
}
|
|
15
|
+
const bandage = await loadBandage();
|
|
16
|
+
const result = bandage.computeLayout(request.graph, request.options);
|
|
17
|
+
return { result, duration: performance.now() - start };
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
layoutEngine
|
|
22
|
+
};
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isBackbone
|
|
3
|
+
} from "./chunk-FYQWPEEY.js";
|
|
4
|
+
|
|
5
|
+
// src/layout/stressEngine.ts
|
|
6
|
+
var LAYOUT_ENGINES = [
|
|
7
|
+
{
|
|
8
|
+
value: "fmmm",
|
|
9
|
+
label: "Bandage (FMMM)",
|
|
10
|
+
description: "Bandage's force-directed layout: OGDF's fast multipole multilevel method, compiled to wasm."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
value: "stress",
|
|
14
|
+
label: "Stress (experimental)",
|
|
15
|
+
description: "Stress layout by stochastic gradient descent in plain JS: distances along the graph become distances on the page, so the reference reads straight and a repeat loop reads as a ring."
|
|
16
|
+
}
|
|
17
|
+
];
|
|
18
|
+
var LAYOUT_ENGINE_VALUES = LAYOUT_ENGINES.map((e) => e.value);
|
|
19
|
+
var QUALITY = [
|
|
20
|
+
{ walks: 2, iterations: 15 },
|
|
21
|
+
{ walks: 4, iterations: 30 },
|
|
22
|
+
{ walks: 8, iterations: 60 },
|
|
23
|
+
{ walks: 12, iterations: 90 },
|
|
24
|
+
{ walks: 16, iterations: 120 }
|
|
25
|
+
];
|
|
26
|
+
var UNTANGLE_PASSES = 20;
|
|
27
|
+
var UNTANGLE_RADIUS = 15;
|
|
28
|
+
var UNTANGLE_PUSH = 0.3;
|
|
29
|
+
var SMOOTH_PASSES = 2;
|
|
30
|
+
var MAX_HOPS = 200;
|
|
31
|
+
var EPS = 0.1;
|
|
32
|
+
function flipped(strand, name) {
|
|
33
|
+
const own = name.at(-1);
|
|
34
|
+
return (strand === "+" || strand === "-") && (own === "+" || own === "-") && strand !== own;
|
|
35
|
+
}
|
|
36
|
+
function xorshift(seed) {
|
|
37
|
+
let s = Math.imul(seed, 2654435761) >>> 0 || 1;
|
|
38
|
+
return () => {
|
|
39
|
+
s ^= s << 13;
|
|
40
|
+
s >>>= 0;
|
|
41
|
+
s ^= s >>> 17;
|
|
42
|
+
s ^= s << 5;
|
|
43
|
+
s >>>= 0;
|
|
44
|
+
return s / 4294967296;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function num(value, fallback) {
|
|
48
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
49
|
+
}
|
|
50
|
+
function chains(request) {
|
|
51
|
+
const { nodes, edges } = request.graph;
|
|
52
|
+
const o = request.options;
|
|
53
|
+
const perMegabase = num(o.nodeLengthPerMegabase, 1e3);
|
|
54
|
+
const minimum = num(o.minimumNodeLength, 1);
|
|
55
|
+
const segment = num(o.nodeSegmentLength, 1);
|
|
56
|
+
const link = num(o.edgeLength, 1);
|
|
57
|
+
const ids = nodes.map((n2) => n2.id);
|
|
58
|
+
const first = new Int32Array(nodes.length);
|
|
59
|
+
const count = new Int32Array(nodes.length);
|
|
60
|
+
const per = new Float64Array(nodes.length);
|
|
61
|
+
let n = 0;
|
|
62
|
+
nodes.forEach((node, k) => {
|
|
63
|
+
const drawn = Math.max(perMegabase * node.length / 1e6, minimum);
|
|
64
|
+
const steps = Math.max(1, Math.ceil(drawn / segment));
|
|
65
|
+
first[k] = n;
|
|
66
|
+
count[k] = steps + 1;
|
|
67
|
+
per[k] = drawn / steps;
|
|
68
|
+
n += steps + 1;
|
|
69
|
+
});
|
|
70
|
+
const chainOf = new Int32Array(n);
|
|
71
|
+
const indexOf = new Int32Array(n);
|
|
72
|
+
const seedX = new Float64Array(n).fill(NaN);
|
|
73
|
+
const seedY = new Float64Array(n).fill(NaN);
|
|
74
|
+
const backbone = new Uint8Array(n);
|
|
75
|
+
const adjacent = Array.from({ length: n }, () => []);
|
|
76
|
+
nodes.forEach((node, k) => {
|
|
77
|
+
const seeded = typeof node.x === "number" && typeof node.y === "number";
|
|
78
|
+
const reference = isBackbone(node) ? 1 : 0;
|
|
79
|
+
for (let i = 0; i < count[k]; i++) {
|
|
80
|
+
const v = first[k] + i;
|
|
81
|
+
chainOf[v] = k;
|
|
82
|
+
indexOf[v] = i;
|
|
83
|
+
backbone[v] = reference;
|
|
84
|
+
if (seeded) {
|
|
85
|
+
seedX[v] = node.x + i * per[k];
|
|
86
|
+
seedY[v] = node.y;
|
|
87
|
+
}
|
|
88
|
+
if (i > 0) {
|
|
89
|
+
adjacent[v - 1].push([v, per[k]]);
|
|
90
|
+
adjacent[v].push([v - 1, per[k]]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
const index = new Map(ids.map((id, k) => [id, k]));
|
|
95
|
+
const links = nodes.map(() => [[], []]);
|
|
96
|
+
for (const edge of edges) {
|
|
97
|
+
const a = index.get(edge.from);
|
|
98
|
+
const b = index.get(edge.to);
|
|
99
|
+
if (a === void 0 || b === void 0) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (a === b && count[a] <= 2) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const fromSide = flipped(edge.fromStrand, edge.from) ? 0 : 1;
|
|
106
|
+
const toSide = flipped(edge.toStrand, edge.to) ? 1 : 0;
|
|
107
|
+
const u = first[a] + (fromSide ? count[a] - 1 : 0);
|
|
108
|
+
const v = first[b] + (toSide ? count[b] - 1 : 0);
|
|
109
|
+
adjacent[u].push([v, link]);
|
|
110
|
+
adjacent[v].push([u, link]);
|
|
111
|
+
links[a][fromSide].push([b, toSide]);
|
|
112
|
+
links[b][toSide].push([a, fromSide]);
|
|
113
|
+
}
|
|
114
|
+
const off = new Int32Array(n + 1);
|
|
115
|
+
for (let i = 0; i < n; i++) {
|
|
116
|
+
off[i + 1] = off[i] + adjacent[i].length;
|
|
117
|
+
}
|
|
118
|
+
const nb = new Int32Array(off[n]);
|
|
119
|
+
const wt = new Float64Array(off[n]);
|
|
120
|
+
for (let i = 0; i < n; i++) {
|
|
121
|
+
adjacent[i].forEach(([v, w], k) => {
|
|
122
|
+
nb[off[i] + k] = v;
|
|
123
|
+
wt[off[i] + k] = w;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
ids,
|
|
128
|
+
first,
|
|
129
|
+
count,
|
|
130
|
+
per,
|
|
131
|
+
links,
|
|
132
|
+
chainOf,
|
|
133
|
+
indexOf,
|
|
134
|
+
seedX,
|
|
135
|
+
seedY,
|
|
136
|
+
backbone,
|
|
137
|
+
off,
|
|
138
|
+
nb,
|
|
139
|
+
wt,
|
|
140
|
+
n
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function stressLayout(request, opts = {}) {
|
|
144
|
+
const c = chains(request);
|
|
145
|
+
const { n, off, nb, wt, seedX, seedY, backbone, first, count, per } = c;
|
|
146
|
+
const o = request.options;
|
|
147
|
+
const quality = QUALITY[Math.max(0, Math.min(4, num(opts.quality ?? o.quality, 2)))];
|
|
148
|
+
const gap = num(opts.componentSeparation ?? o.componentSeparation, 15);
|
|
149
|
+
const rand = xorshift(num(opts.seed ?? o.seed, 1));
|
|
150
|
+
const X = new Float64Array(n);
|
|
151
|
+
const Y = new Float64Array(n);
|
|
152
|
+
const nodePositions = {};
|
|
153
|
+
if (n === 0) {
|
|
154
|
+
return { nodePositions };
|
|
155
|
+
}
|
|
156
|
+
let dmin = Infinity;
|
|
157
|
+
let total = 0;
|
|
158
|
+
for (let e = 0; e < nb.length; e++) {
|
|
159
|
+
dmin = Math.min(dmin, wt[e]);
|
|
160
|
+
total += wt[e];
|
|
161
|
+
}
|
|
162
|
+
total /= 2;
|
|
163
|
+
let anySeed = false;
|
|
164
|
+
for (let i = 0; i < n; i++) {
|
|
165
|
+
if (!Number.isNaN(seedX[i])) {
|
|
166
|
+
anySeed = true;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const side = Math.sqrt(n) * 20;
|
|
171
|
+
for (let i = 0; i < n; i++) {
|
|
172
|
+
if (anySeed && !Number.isNaN(seedX[i])) {
|
|
173
|
+
X[i] = seedX[i] + rand() - 0.5;
|
|
174
|
+
Y[i] = seedY[i] + rand() - 0.5;
|
|
175
|
+
} else {
|
|
176
|
+
X[i] = rand() * side;
|
|
177
|
+
Y[i] = rand() * side;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const refIdx = [];
|
|
181
|
+
for (let i = 0; i < n; i++) {
|
|
182
|
+
if (backbone[i] && !Number.isNaN(seedX[i])) {
|
|
183
|
+
refIdx.push(i);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
refIdx.sort((a, b) => seedX[a] - seedX[b]);
|
|
187
|
+
const refX = Float64Array.from(refIdx, (i) => seedX[i]);
|
|
188
|
+
const span = refIdx.length ? refX[refX.length - 1] - refX[0] : 0;
|
|
189
|
+
const dmax = Math.max(span, total / 4, dmin * 10);
|
|
190
|
+
const etaMax = dmax * dmax;
|
|
191
|
+
const etaMin = EPS * dmin * dmin;
|
|
192
|
+
const { walks, iterations } = quality;
|
|
193
|
+
const lambda = Math.log(etaMax / etaMin) / Math.max(1, iterations - 1);
|
|
194
|
+
const logMin = Math.log(dmin);
|
|
195
|
+
const logMax = Math.log(dmax);
|
|
196
|
+
const move = (i, j, d, eta) => {
|
|
197
|
+
let mu = eta / (d * d);
|
|
198
|
+
if (mu > 1) {
|
|
199
|
+
mu = 1;
|
|
200
|
+
}
|
|
201
|
+
const dx = X[i] - X[j];
|
|
202
|
+
const dy = Y[i] - Y[j];
|
|
203
|
+
const mag = Math.sqrt(dx * dx + dy * dy) || 1e-9;
|
|
204
|
+
const r = mu * (mag - d) / (2 * mag);
|
|
205
|
+
const rx = r * dx;
|
|
206
|
+
const ry = r * dy;
|
|
207
|
+
X[i] = X[i] - rx;
|
|
208
|
+
Y[i] = Y[i] - ry;
|
|
209
|
+
X[j] = X[j] + rx;
|
|
210
|
+
Y[j] = Y[j] + ry;
|
|
211
|
+
};
|
|
212
|
+
const walk = (i, length) => {
|
|
213
|
+
let chain = c.chainOf[i];
|
|
214
|
+
let idx = c.indexOf[i];
|
|
215
|
+
let dir = rand() < 0.5 ? -1 : 1;
|
|
216
|
+
let remaining = length;
|
|
217
|
+
for (let hops = 0; hops < MAX_HOPS; hops++) {
|
|
218
|
+
const points = count[chain];
|
|
219
|
+
const step = per[chain];
|
|
220
|
+
const toEnd = (dir > 0 ? points - 1 - idx : idx) * step;
|
|
221
|
+
if (remaining <= toEnd) {
|
|
222
|
+
return first[chain] + idx + dir * Math.round(remaining / step);
|
|
223
|
+
}
|
|
224
|
+
remaining -= toEnd;
|
|
225
|
+
const out = c.links[chain][dir > 0 ? 1 : 0];
|
|
226
|
+
if (out.length === 0) {
|
|
227
|
+
return first[chain] + (dir > 0 ? points - 1 : 0);
|
|
228
|
+
}
|
|
229
|
+
const [next, enterSide] = out[Math.floor(rand() * out.length)];
|
|
230
|
+
remaining -= num(o.edgeLength, 1);
|
|
231
|
+
chain = next;
|
|
232
|
+
dir = enterSide === 0 ? 1 : -1;
|
|
233
|
+
idx = enterSide === 0 ? 0 : count[next] - 1;
|
|
234
|
+
if (remaining <= 0) {
|
|
235
|
+
return first[chain] + idx;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return -1;
|
|
239
|
+
};
|
|
240
|
+
const refPartner = (i) => {
|
|
241
|
+
const length = Math.exp(logMin + rand() * (logMax - logMin)) * (rand() < 0.5 ? -1 : 1);
|
|
242
|
+
const target = seedX[i] + length;
|
|
243
|
+
let lo = 0;
|
|
244
|
+
let hi = refX.length;
|
|
245
|
+
while (lo < hi) {
|
|
246
|
+
const mid = lo + hi >> 1;
|
|
247
|
+
if (refX[mid] < target) {
|
|
248
|
+
lo = mid + 1;
|
|
249
|
+
} else {
|
|
250
|
+
hi = mid;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return refIdx[Math.min(lo, refIdx.length - 1)];
|
|
254
|
+
};
|
|
255
|
+
const referenceTerm = (i, eta) => {
|
|
256
|
+
if (!backbone[i] || refIdx.length < 2 || Number.isNaN(seedX[i])) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const j = refPartner(i);
|
|
260
|
+
const d = Math.abs(seedX[i] - seedX[j]);
|
|
261
|
+
if (j !== i && d > 0) {
|
|
262
|
+
move(i, j, d, eta);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
const adjacentTerms = (i, eta) => {
|
|
266
|
+
for (let e = off[i]; e < off[i + 1]; e++) {
|
|
267
|
+
const j = nb[e];
|
|
268
|
+
if (j > i) {
|
|
269
|
+
move(i, j, wt[e], eta);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
const order = new Int32Array(n);
|
|
274
|
+
for (let i = 0; i < n; i++) {
|
|
275
|
+
order[i] = i;
|
|
276
|
+
}
|
|
277
|
+
for (let t = 0; t < iterations; t++) {
|
|
278
|
+
const eta = etaMax * Math.exp(-lambda * t);
|
|
279
|
+
for (let k = n - 1; k > 0; k--) {
|
|
280
|
+
const r = Math.floor(rand() * (k + 1));
|
|
281
|
+
const tmp = order[k];
|
|
282
|
+
order[k] = order[r];
|
|
283
|
+
order[r] = tmp;
|
|
284
|
+
}
|
|
285
|
+
for (let q = 0; q < n; q++) {
|
|
286
|
+
const i = order[q];
|
|
287
|
+
adjacentTerms(i, eta);
|
|
288
|
+
for (let w = 0; w < walks; w++) {
|
|
289
|
+
const length = Math.exp(logMin + rand() * (logMax - logMin));
|
|
290
|
+
const j = walk(i, length);
|
|
291
|
+
if (j >= 0 && j !== i) {
|
|
292
|
+
move(i, j, length, eta);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
referenceTerm(i, eta);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const cell = UNTANGLE_RADIUS;
|
|
299
|
+
const key = (x, y) => Math.floor(x / cell) * 73856093 ^ Math.floor(y / cell) * 19349663;
|
|
300
|
+
for (let u = 0; u < UNTANGLE_PASSES; u++) {
|
|
301
|
+
const grid = /* @__PURE__ */ new Map();
|
|
302
|
+
for (let i = 0; i < n; i++) {
|
|
303
|
+
const k = key(X[i], Y[i]);
|
|
304
|
+
const list = grid.get(k);
|
|
305
|
+
if (list) {
|
|
306
|
+
list.push(i);
|
|
307
|
+
} else {
|
|
308
|
+
grid.set(k, [i]);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
for (let i = 0; i < n; i++) {
|
|
312
|
+
const cx = Math.floor(X[i] / cell);
|
|
313
|
+
const cy = Math.floor(Y[i] / cell);
|
|
314
|
+
for (let gx = cx - 1; gx <= cx + 1; gx++) {
|
|
315
|
+
for (let gy = cy - 1; gy <= cy + 1; gy++) {
|
|
316
|
+
const list = grid.get(gx * 73856093 ^ gy * 19349663);
|
|
317
|
+
if (!list) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
for (const j of list) {
|
|
321
|
+
if (j <= i) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
let joined = false;
|
|
325
|
+
for (let e = off[i]; e < off[i + 1]; e++) {
|
|
326
|
+
if (nb[e] === j) {
|
|
327
|
+
joined = true;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (joined) {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
const dx = X[i] - X[j];
|
|
335
|
+
const dy = Y[i] - Y[j];
|
|
336
|
+
const mag = Math.sqrt(dx * dx + dy * dy);
|
|
337
|
+
if (mag >= UNTANGLE_RADIUS) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
let rx;
|
|
341
|
+
let ry;
|
|
342
|
+
if (mag > 1e-6) {
|
|
343
|
+
const r = UNTANGLE_PUSH * (UNTANGLE_RADIUS - mag) / (2 * mag);
|
|
344
|
+
rx = r * dx;
|
|
345
|
+
ry = r * dy;
|
|
346
|
+
} else {
|
|
347
|
+
rx = UNTANGLE_PUSH * UNTANGLE_RADIUS * (rand() - 0.5);
|
|
348
|
+
ry = UNTANGLE_PUSH * UNTANGLE_RADIUS * (rand() - 0.5);
|
|
349
|
+
}
|
|
350
|
+
X[i] = X[i] + rx;
|
|
351
|
+
Y[i] = Y[i] + ry;
|
|
352
|
+
X[j] = X[j] - rx;
|
|
353
|
+
Y[j] = Y[j] - ry;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
for (let i = 0; i < n; i++) {
|
|
359
|
+
adjacentTerms(i, Infinity);
|
|
360
|
+
referenceTerm(i, etaMin * 4);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
for (let k = 0; k < count.length; k++) {
|
|
364
|
+
const a = first[k];
|
|
365
|
+
const m = count[k];
|
|
366
|
+
if (m < 3) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
for (let s = 0; s < SMOOTH_PASSES; s++) {
|
|
370
|
+
const xs = X.slice(a, a + m);
|
|
371
|
+
const ys = Y.slice(a, a + m);
|
|
372
|
+
for (let i = 1; i + 1 < m; i++) {
|
|
373
|
+
X[a + i] = 0.5 * xs[i] + 0.25 * (xs[i - 1] + xs[i + 1]);
|
|
374
|
+
Y[a + i] = 0.5 * ys[i] + 0.25 * (ys[i - 1] + ys[i + 1]);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
let length = 0;
|
|
378
|
+
let cx = 0;
|
|
379
|
+
let cy = 0;
|
|
380
|
+
for (let i = 0; i < m; i++) {
|
|
381
|
+
cx += X[a + i];
|
|
382
|
+
cy += Y[a + i];
|
|
383
|
+
if (i > 0) {
|
|
384
|
+
length += Math.hypot(
|
|
385
|
+
X[a + i] - X[a + i - 1],
|
|
386
|
+
Y[a + i] - Y[a + i - 1]
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
cx /= m;
|
|
391
|
+
cy /= m;
|
|
392
|
+
const scale = length > 0 ? per[k] * (m - 1) / length : 1;
|
|
393
|
+
for (let i = 0; i < m; i++) {
|
|
394
|
+
X[a + i] = cx + (X[a + i] - cx) * scale;
|
|
395
|
+
Y[a + i] = cy + (Y[a + i] - cy) * scale;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
packComponents(c, X, Y, anySeed, gap);
|
|
399
|
+
for (let k = 0; k < c.ids.length; k++) {
|
|
400
|
+
const points = [];
|
|
401
|
+
for (let i = 0; i < count[k]; i++) {
|
|
402
|
+
points.push({ x: X[first[k] + i], y: Y[first[k] + i] });
|
|
403
|
+
}
|
|
404
|
+
nodePositions[c.ids[k]] = points;
|
|
405
|
+
}
|
|
406
|
+
return { nodePositions };
|
|
407
|
+
}
|
|
408
|
+
function packComponents(c, X, Y, anySeed, gap) {
|
|
409
|
+
const { n, off, nb } = c;
|
|
410
|
+
const comp = new Int32Array(n).fill(-1);
|
|
411
|
+
let components = 0;
|
|
412
|
+
for (let s = 0; s < n; s++) {
|
|
413
|
+
if (comp[s] >= 0) {
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
const queue = [s];
|
|
417
|
+
comp[s] = components;
|
|
418
|
+
for (const u of queue) {
|
|
419
|
+
for (let e = off[u]; e < off[u + 1]; e++) {
|
|
420
|
+
const v = nb[e];
|
|
421
|
+
if (comp[v] < 0) {
|
|
422
|
+
comp[v] = components;
|
|
423
|
+
queue.push(v);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
components++;
|
|
428
|
+
}
|
|
429
|
+
if (!anySeed) {
|
|
430
|
+
for (let k = 0; k < components; k++) {
|
|
431
|
+
rotateToPrincipalAxis(X, Y, comp, k);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (components < 2) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const minX = new Float64Array(components).fill(Infinity);
|
|
438
|
+
const minY = new Float64Array(components).fill(Infinity);
|
|
439
|
+
const maxY = new Float64Array(components).fill(-Infinity);
|
|
440
|
+
for (let i = 0; i < n; i++) {
|
|
441
|
+
const k = comp[i];
|
|
442
|
+
minX[k] = Math.min(minX[k], X[i]);
|
|
443
|
+
minY[k] = Math.min(minY[k], Y[i]);
|
|
444
|
+
maxY[k] = Math.max(maxY[k], Y[i]);
|
|
445
|
+
}
|
|
446
|
+
const shift = new Float64Array(components);
|
|
447
|
+
let y = 0;
|
|
448
|
+
for (let k = 0; k < components; k++) {
|
|
449
|
+
shift[k] = y - minY[k];
|
|
450
|
+
y += maxY[k] - minY[k] + gap;
|
|
451
|
+
}
|
|
452
|
+
for (let i = 0; i < n; i++) {
|
|
453
|
+
const k = comp[i];
|
|
454
|
+
X[i] = X[i] - minX[k];
|
|
455
|
+
Y[i] = Y[i] + shift[k];
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
function rotateToPrincipalAxis(X, Y, comp, k) {
|
|
459
|
+
let m = 0;
|
|
460
|
+
let cx = 0;
|
|
461
|
+
let cy = 0;
|
|
462
|
+
for (let i = 0; i < X.length; i++) {
|
|
463
|
+
if (comp[i] === k) {
|
|
464
|
+
cx += X[i];
|
|
465
|
+
cy += Y[i];
|
|
466
|
+
m++;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (m < 3) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
cx /= m;
|
|
473
|
+
cy /= m;
|
|
474
|
+
let sxx = 0;
|
|
475
|
+
let syy = 0;
|
|
476
|
+
let sxy = 0;
|
|
477
|
+
for (let i = 0; i < X.length; i++) {
|
|
478
|
+
if (comp[i] === k) {
|
|
479
|
+
const dx = X[i] - cx;
|
|
480
|
+
const dy = Y[i] - cy;
|
|
481
|
+
sxx += dx * dx;
|
|
482
|
+
syy += dy * dy;
|
|
483
|
+
sxy += dx * dy;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const angle = -0.5 * Math.atan2(2 * sxy, sxx - syy);
|
|
487
|
+
const cos = Math.cos(angle);
|
|
488
|
+
const sin = Math.sin(angle);
|
|
489
|
+
for (let i = 0; i < X.length; i++) {
|
|
490
|
+
if (comp[i] === k) {
|
|
491
|
+
const dx = X[i] - cx;
|
|
492
|
+
const dy = Y[i] - cy;
|
|
493
|
+
X[i] = cx + cos * dx - sin * dy;
|
|
494
|
+
Y[i] = cy + sin * dx + cos * dy;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export {
|
|
500
|
+
LAYOUT_ENGINES,
|
|
501
|
+
LAYOUT_ENGINE_VALUES,
|
|
502
|
+
stressLayout
|
|
503
|
+
};
|
|
@@ -40,11 +40,32 @@ function withDeletionRoutes(graph, deletions, dropped = /* @__PURE__ */ new Set(
|
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
function closableDeletions(graph, deletions) {
|
|
43
|
-
const
|
|
43
|
+
const parent = /* @__PURE__ */ new Map();
|
|
44
|
+
const root = (id) => {
|
|
45
|
+
let r = id;
|
|
46
|
+
while (parent.has(r)) {
|
|
47
|
+
r = parent.get(r);
|
|
48
|
+
}
|
|
49
|
+
if (r !== id) {
|
|
50
|
+
parent.set(id, r);
|
|
51
|
+
}
|
|
52
|
+
return r;
|
|
53
|
+
};
|
|
54
|
+
const join = (a, b) => {
|
|
55
|
+
const [ra, rb] = [root(a), root(b)];
|
|
56
|
+
if (ra !== rb) {
|
|
57
|
+
parent.set(ra, rb);
|
|
58
|
+
}
|
|
59
|
+
return ra !== rb;
|
|
60
|
+
};
|
|
61
|
+
const candidates = new Set(deletions.map((d) => d.edgeIndex));
|
|
62
|
+
graph.edges.forEach((e, ei) => {
|
|
63
|
+
if (!candidates.has(ei)) {
|
|
64
|
+
join(e.from, e.to);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
44
67
|
return new Set(
|
|
45
|
-
deletions.filter(
|
|
46
|
-
(d) => d.bypassed.reduce((sum, id) => sum + (lengths.get(id) ?? 0), 0) >= d.bp
|
|
47
|
-
).map((d) => d.edgeIndex)
|
|
68
|
+
deletions.filter((d) => !join(d.from, d.to)).map((d) => d.edgeIndex)
|
|
48
69
|
);
|
|
49
70
|
}
|
|
50
71
|
function routeNodes(graph, deletions, routes, drawnOf, opts) {
|