@jbrowse/bandage-core 7.0.0 → 7.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.
Files changed (40) hide show
  1. package/dist/chunks/{chunk-EZPBJPK6.js → chunk-3JANT2SW.js} +6 -3
  2. package/dist/chunks/chunk-6EJIU6IA.js +42 -0
  3. package/dist/chunks/{chunk-6UFS3EWI.js → chunk-EPWYRSHH.js} +1 -1
  4. package/dist/chunks/{chunk-WEMKWLDF.js → chunk-JKXE6CIP.js} +1 -1
  5. package/dist/chunks/{chunk-YZ5CCEDH.js → chunk-JVLURPFW.js} +1 -0
  6. package/dist/chunks/{chunk-MZ7QQRUJ.js → chunk-KREASK3Z.js} +7 -13
  7. package/dist/chunks/chunk-LYJ4US66.js +664 -0
  8. package/dist/chunks/{chunk-7PWMTUAK.js → chunk-QMJ4J5PT.js} +14 -13
  9. package/dist/chunks/{chunk-EOSVRILX.js → chunk-S73EYTZA.js} +1 -1
  10. package/dist/chunks/{chunk-JFNPL4UT.js → chunk-UGD6ZMOF.js} +29 -4
  11. package/dist/chunks/{chunk-O4TGF4WS.js → chunk-UNOE2NDN.js} +155 -129
  12. package/dist/chunks/{chunk-AWCMVPVP.js → chunk-Y7UR3MDE.js} +1 -1
  13. package/dist/cli/figure.js +24 -13
  14. package/dist/figure.js +9 -9
  15. package/dist/index.js +28 -19
  16. package/dist/layout/deletionRoutes.js +3 -1
  17. package/dist/layout/stressEngine.js +5 -5
  18. package/dist/layout/stressPlacement.js +7 -0
  19. package/dist/layout/walkRowDraw.js +11 -1
  20. package/dist/layout/walkRowLayout.js +1 -1
  21. package/dist/layout/walkStrip.js +4 -3
  22. package/dist/layoutEngines.js +10 -2
  23. package/dist/layoutModes.js +2 -2
  24. package/dist/pipeline.js +2 -2
  25. package/dist/types/cli/figure.d.ts +1 -1
  26. package/dist/types/figure.d.ts +2 -1
  27. package/dist/types/index.d.ts +5 -5
  28. package/dist/types/layout/deletionRoutes.d.ts +1 -0
  29. package/dist/types/layout/stressEngine.d.ts +19 -11
  30. package/dist/types/layout/stressPlacement.d.ts +5 -0
  31. package/dist/types/layout/walkRowDraw.d.ts +11 -0
  32. package/dist/types/layout/walkRowLayout.d.ts +2 -1
  33. package/dist/types/layout/walkStrip.d.ts +1 -1
  34. package/dist/types/layoutEngines.d.ts +12 -0
  35. package/dist/types/pipeline.d.ts +1 -1
  36. package/dist/types/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/dist/viewport.js +3 -3
  39. package/package.json +1 -1
  40. package/dist/chunks/chunk-L53SXJZI.js +0 -22
@@ -1,3 +1,6 @@
1
+ import {
2
+ Canvas2DRenderer
3
+ } from "./chunk-CH3DLBUT.js";
1
4
  import {
2
5
  svgCanvas
3
6
  } from "./chunk-47DYCZOK.js";
@@ -5,15 +8,12 @@ import {
5
8
  stripGeneGaps,
6
9
  walkStripFrame,
7
10
  walkStripLabelsTree
8
- } from "./chunk-MZ7QQRUJ.js";
11
+ } from "./chunk-KREASK3Z.js";
9
12
  import {
10
13
  walkRowsKey,
11
14
  walkRowsKeyTree,
12
15
  walkRowsTree
13
- } from "./chunk-JFNPL4UT.js";
14
- import {
15
- Canvas2DRenderer
16
- } from "./chunk-CH3DLBUT.js";
16
+ } from "./chunk-UGD6ZMOF.js";
17
17
  import {
18
18
  EXON_COLOR,
19
19
  exonOutlineTree,
@@ -24,10 +24,10 @@ import {
24
24
  } from "./chunk-BUBZ32IC.js";
25
25
  import {
26
26
  version
27
- } from "./chunk-EOSVRILX.js";
27
+ } from "./chunk-S73EYTZA.js";
28
28
  import {
29
29
  axisScaleOf
30
- } from "./chunk-AWCMVPVP.js";
30
+ } from "./chunk-Y7UR3MDE.js";
31
31
  import {
32
32
  facetLifts,
33
33
  walkLift
@@ -43,7 +43,7 @@ import {
43
43
  FIT_PADDING,
44
44
  drawingBounds,
45
45
  fitTransform
46
- } from "./chunk-6UFS3EWI.js";
46
+ } from "./chunk-EPWYRSHH.js";
47
47
  import {
48
48
  buildGeometry,
49
49
  computeReferenceRamp
@@ -59,7 +59,7 @@ import {
59
59
  } from "./chunk-FP4H7SFT.js";
60
60
  import {
61
61
  walkRowsExtent
62
- } from "./chunk-EZPBJPK6.js";
62
+ } from "./chunk-3JANT2SW.js";
63
63
  import {
64
64
  pathLegend
65
65
  } from "./chunk-E7FWQQHP.js";
@@ -154,7 +154,7 @@ function figureSvg(graph, layout, o = {}) {
154
154
  const rows = o.walkRows;
155
155
  const bounds = drawingBounds(layout, {
156
156
  region: o.fitToDrawing ? void 0 : region,
157
- extent: rows && layout.extent ? walkRowsExtent(rows) : void 0
157
+ extent: rows && layout.extent ? walkRowsExtent(rows, o.walkRowPitch) : void 0
158
158
  });
159
159
  const pixelRows = layout.pixelRows ?? false;
160
160
  const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
@@ -226,7 +226,7 @@ function figureSvg(graph, layout, o = {}) {
226
226
  serializeEl(
227
227
  walkRowsTree(
228
228
  rows,
229
- { ...t, width: w, height: h },
229
+ { ...t, width: w, height: h, ...o.walkRowPitch },
230
230
  {
231
231
  ramp: rampInterval,
232
232
  rowGenes: o.rowGenes
@@ -235,9 +235,10 @@ function figureSvg(graph, layout, o = {}) {
235
235
  )
236
236
  );
237
237
  }
238
- const rowLabels = rows ? [rows.reference, ...rows.rows].map((r, i) => ({
238
+ const pitch = o.walkRowPitch;
239
+ const rowLabels = rows ? pitch && !pitch.labelled ? [] : [rows.reference, ...rows.rows].map((r, i) => ({
239
240
  label: r.label,
240
- y: i * ROW_HEIGHT_PX
241
+ y: i * (pitch?.rowPx ?? ROW_HEIGHT_PX)
241
242
  })) : layout.rowLabels ?? [];
242
243
  for (const { label, y } of rowLabels) {
243
244
  const sy = y * t.scaleY + t.translateY;
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var version = "7.0.0";
2
+ var version = "7.2.0";
3
3
 
4
4
  export {
5
5
  version
@@ -24,6 +24,9 @@ var OUTSIDE_CUT = "#bdbdbd";
24
24
  var GENE_INK = "#1c1c22";
25
25
  var BAR_PX = 12;
26
26
  var GAP_PX = 4;
27
+ var LABELLED_ROW_PX = 12;
28
+ var READOUT_ROW_PX = 14;
29
+ var MIN_TILED_BAR_PX = 6;
27
30
  var MIN_TILE_PX = 3;
28
31
  function kb(bp) {
29
32
  return `${(bp / 1e3).toFixed(bp < 1e4 ? 1 : 0)} kb`;
@@ -209,6 +212,9 @@ function walkRowsKey(bars, o = {}) {
209
212
  var CALL_TICK = "#111";
210
213
  var UNBACKED_TICK = "#9e9e9e";
211
214
  var DISAGREES = "#c62828";
215
+ function callTickInk(call) {
216
+ return call.agrees === false ? DISAGREES : call.spanningReads === 0 ? UNBACKED_TICK : CALL_TICK;
217
+ }
212
218
  function walkRowsKeyTree(entries, x, y, idPrefix = "walk-key") {
213
219
  let at = 0;
214
220
  const parts = entries.flatMap((e, i) => {
@@ -262,6 +268,19 @@ function walkRowsKeyTree(entries, x, y, idPrefix = "walk-key") {
262
268
  )
263
269
  };
264
270
  }
271
+ function rowPitch(count, room, minRowPx = 0) {
272
+ const fit = room / Math.max(1, count);
273
+ const rowPx = Math.max(
274
+ minRowPx,
275
+ Math.min(ROW_HEIGHT_PX, fit >= 1 ? Math.floor(fit) : fit)
276
+ );
277
+ return {
278
+ rowPx,
279
+ barPx: rowPx <= 3 ? rowPx : Math.max(3, Math.round(rowPx * 0.6)),
280
+ labelled: rowPx >= LABELLED_ROW_PX,
281
+ readouts: rowPx >= READOUT_ROW_PX
282
+ };
283
+ }
265
284
  function coalesceRuns(runs, pxPerBp) {
266
285
  const minBp = 1 / pxPerBp;
267
286
  const out = [];
@@ -358,6 +377,7 @@ function walkRowsTree(bars, frame, o = {}) {
358
377
  const X = (bp) => bp * frame.scaleX + frame.translateX;
359
378
  const rowPx = frame.rowPx ?? ROW_HEIGHT_PX;
360
379
  const barPx = frame.barPx ?? BAR_PX;
380
+ const overhang = Math.min(3, Math.max(0, (rowPx - barPx) / 2));
361
381
  const Y = (row) => row * rowPx * frame.scaleY + frame.translateY;
362
382
  const { origin, unit, reference, rows } = bars;
363
383
  const along = (offset) => X(origin + offset);
@@ -392,7 +412,7 @@ function walkRowsTree(bars, frame, o = {}) {
392
412
  rect
393
413
  ] : [rect];
394
414
  });
395
- const ticks = unit ? unitTicks(row.bp, unit, frame.scaleX).map(
415
+ const ticks = unit && barPx >= MIN_TILED_BAR_PX ? unitTicks(row.bp, unit, frame.scaleX).map(
396
416
  (k) => el("line", {
397
417
  x1: along(k),
398
418
  x2: along(k),
@@ -425,10 +445,10 @@ function walkRowsTree(bars, frame, o = {}) {
425
445
  call && el("rect", {
426
446
  "data-testid": "graph-walk-call",
427
447
  x: along(call.bp) - 1,
428
- y: y - barPx / 2 - 3,
448
+ y: y - barPx / 2 - overhang,
429
449
  width: 2,
430
- height: barPx + 6,
431
- fill: call.spanningReads === 0 ? UNBACKED_TICK : CALL_TICK
450
+ height: barPx + 2 * overhang,
451
+ fill: callTickInk(call)
432
452
  }),
433
453
  readouts && el(
434
454
  "text",
@@ -457,6 +477,7 @@ export {
457
477
  GENE_INK,
458
478
  BAR_PX,
459
479
  GAP_PX,
480
+ LABELLED_ROW_PX,
460
481
  kb,
461
482
  walkRowReadout,
462
483
  readoutPlacement,
@@ -469,8 +490,12 @@ export {
469
490
  boxesGenes,
470
491
  rowGeneBoxes,
471
492
  walkRowsKey,
493
+ CALL_TICK,
494
+ UNBACKED_TICK,
472
495
  DISAGREES,
496
+ callTickInk,
473
497
  walkRowsKeyTree,
498
+ rowPitch,
474
499
  coalesceRuns,
475
500
  walkRowsTree
476
501
  };
@@ -1,21 +1,11 @@
1
+ import {
2
+ stressPlacement
3
+ } from "./chunk-LYJ4US66.js";
1
4
  import {
2
5
  isBackbone
3
6
  } from "./chunk-FYQWPEEY.js";
4
7
 
5
8
  // 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
9
  var QUALITY = [
20
10
  { walks: 2, iterations: 15 },
21
11
  { walks: 4, iterations: 30 },
@@ -24,11 +14,12 @@ var QUALITY = [
24
14
  { walks: 16, iterations: 120 }
25
15
  ];
26
16
  var UNTANGLE_PASSES = 20;
27
- var UNTANGLE_RADIUS = 15;
17
+ var UNTANGLE_LINKS = 3;
28
18
  var UNTANGLE_PUSH = 0.3;
29
19
  var SMOOTH_PASSES = 2;
30
20
  var MAX_HOPS = 200;
31
21
  var EPS = 0.1;
22
+ var WARM = 0.5;
32
23
  function flipped(strand, name) {
33
24
  const own = name.at(-1);
34
25
  return (strand === "+" || strand === "-") && (own === "+" || own === "-") && strand !== own;
@@ -137,12 +128,36 @@ function chains(request) {
137
128
  off,
138
129
  nb,
139
130
  wt,
140
- n
131
+ n,
132
+ link
141
133
  };
142
134
  }
135
+ function components(c) {
136
+ const { n, off, nb } = c;
137
+ const comp = new Int32Array(n).fill(-1);
138
+ const members = [];
139
+ for (let s = 0; s < n; s++) {
140
+ if (comp[s] >= 0) {
141
+ continue;
142
+ }
143
+ const queue = [s];
144
+ comp[s] = members.length;
145
+ for (const u of queue) {
146
+ for (let e = off[u]; e < off[u + 1]; e++) {
147
+ const v = nb[e];
148
+ if (comp[v] < 0) {
149
+ comp[v] = members.length;
150
+ queue.push(v);
151
+ }
152
+ }
153
+ }
154
+ members.push(queue);
155
+ }
156
+ return { comp, members };
157
+ }
143
158
  function stressLayout(request, opts = {}) {
144
159
  const c = chains(request);
145
- const { n, off, nb, wt, seedX, seedY, backbone, first, count, per } = c;
160
+ const { n, off, nb, wt, seedX, seedY, backbone, first, count, per, link } = c;
146
161
  const o = request.options;
147
162
  const quality = QUALITY[Math.max(0, Math.min(4, num(opts.quality ?? o.quality, 2)))];
148
163
  const gap = num(opts.componentSeparation ?? o.componentSeparation, 15);
@@ -153,6 +168,7 @@ function stressLayout(request, opts = {}) {
153
168
  if (n === 0) {
154
169
  return { nodePositions };
155
170
  }
171
+ const { comp, members } = components(c);
156
172
  let dmin = Infinity;
157
173
  let total = 0;
158
174
  for (let e = 0; e < nb.length; e++) {
@@ -167,9 +183,13 @@ function stressLayout(request, opts = {}) {
167
183
  break;
168
184
  }
169
185
  }
186
+ const placed = anySeed ? stressPlacement(c) : void 0;
170
187
  const side = Math.sqrt(n) * 20;
171
188
  for (let i = 0; i < n; i++) {
172
- if (anySeed && !Number.isNaN(seedX[i])) {
189
+ if (placed && !Number.isNaN(placed.X[i])) {
190
+ X[i] = placed.X[i];
191
+ Y[i] = placed.Y[i];
192
+ } else if (anySeed && !Number.isNaN(seedX[i])) {
173
193
  X[i] = seedX[i] + rand() - 0.5;
174
194
  Y[i] = seedY[i] + rand() - 0.5;
175
195
  } else {
@@ -177,15 +197,18 @@ function stressLayout(request, opts = {}) {
177
197
  Y[i] = rand() * side;
178
198
  }
179
199
  }
180
- const refIdx = [];
181
- for (let i = 0; i < n; i++) {
182
- if (backbone[i] && !Number.isNaN(seedX[i])) {
183
- refIdx.push(i);
200
+ const refIdx = members.map(
201
+ (points) => Int32Array.from(
202
+ points.filter((i) => backbone[i] && !Number.isNaN(seedX[i])).sort((a, b) => seedX[a] - seedX[b])
203
+ )
204
+ );
205
+ const refX = refIdx.map((idx) => Float64Array.from(idx, (i) => seedX[i]));
206
+ let span = 0;
207
+ for (const xs of refX) {
208
+ if (xs.length) {
209
+ span = Math.max(span, xs[xs.length - 1] - xs[0]);
184
210
  }
185
211
  }
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
212
  const dmax = Math.max(span, total / 4, dmin * 10);
190
213
  const etaMax = dmax * dmax;
191
214
  const etaMin = EPS * dmin * dmin;
@@ -209,6 +232,7 @@ function stressLayout(request, opts = {}) {
209
232
  X[j] = X[j] + rx;
210
233
  Y[j] = Y[j] + ry;
211
234
  };
235
+ let walked = 0;
212
236
  const walk = (i, length) => {
213
237
  let chain = c.chainOf[i];
214
238
  let idx = c.indexOf[i];
@@ -219,48 +243,57 @@ function stressLayout(request, opts = {}) {
219
243
  const step = per[chain];
220
244
  const toEnd = (dir > 0 ? points - 1 - idx : idx) * step;
221
245
  if (remaining <= toEnd) {
222
- return first[chain] + idx + dir * Math.round(remaining / step);
246
+ const steps = Math.round(remaining / step);
247
+ walked = length - remaining + steps * step;
248
+ return first[chain] + idx + dir * steps;
223
249
  }
224
250
  remaining -= toEnd;
225
251
  const out = c.links[chain][dir > 0 ? 1 : 0];
226
252
  if (out.length === 0) {
253
+ walked = length - remaining;
227
254
  return first[chain] + (dir > 0 ? points - 1 : 0);
228
255
  }
229
256
  const [next, enterSide] = out[Math.floor(rand() * out.length)];
230
- remaining -= num(o.edgeLength, 1);
257
+ remaining -= link;
231
258
  chain = next;
232
259
  dir = enterSide === 0 ? 1 : -1;
233
260
  idx = enterSide === 0 ? 0 : count[next] - 1;
234
261
  if (remaining <= 0) {
262
+ walked = length - remaining;
235
263
  return first[chain] + idx;
236
264
  }
237
265
  }
238
266
  return -1;
239
267
  };
240
- const refPartner = (i) => {
241
- const length = Math.exp(logMin + rand() * (logMax - logMin)) * (rand() < 0.5 ? -1 : 1);
242
- const target = seedX[i] + length;
268
+ const referenceTerm = (i, eta) => {
269
+ const idx = refIdx[comp[i]];
270
+ if (!backbone[i] || idx.length < 2 || Number.isNaN(seedX[i])) {
271
+ return;
272
+ }
273
+ const xs = refX[comp[i]];
274
+ const target = seedX[i] + Math.exp(logMin + rand() * (logMax - logMin)) * (rand() < 0.5 ? -1 : 1);
243
275
  let lo = 0;
244
- let hi = refX.length;
276
+ let hi = xs.length;
245
277
  while (lo < hi) {
246
278
  const mid = lo + hi >> 1;
247
- if (refX[mid] < target) {
279
+ if (xs[mid] < target) {
248
280
  lo = mid + 1;
249
281
  } else {
250
282
  hi = mid;
251
283
  }
252
284
  }
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])) {
285
+ const j = idx[Math.min(lo, idx.length - 1)];
286
+ const d = seedX[j] - seedX[i];
287
+ if (j === i || d === 0) {
257
288
  return;
258
289
  }
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);
290
+ let mu = eta / (d * d);
291
+ if (mu > 1) {
292
+ mu = 1;
263
293
  }
294
+ const r = mu * (X[j] - X[i] - d) / 2;
295
+ X[i] = X[i] + r;
296
+ X[j] = X[j] - r;
264
297
  };
265
298
  const adjacentTerms = (i, eta) => {
266
299
  for (let e = off[i]; e < off[i + 1]; e++) {
@@ -274,7 +307,8 @@ function stressLayout(request, opts = {}) {
274
307
  for (let i = 0; i < n; i++) {
275
308
  order[i] = i;
276
309
  }
277
- for (let t = 0; t < iterations; t++) {
310
+ const warm = anySeed ? Math.floor(WARM * iterations) : 0;
311
+ for (let t = warm; t < iterations; t++) {
278
312
  const eta = etaMax * Math.exp(-lambda * t);
279
313
  for (let k = n - 1; k > 0; k--) {
280
314
  const r = Math.floor(rand() * (k + 1));
@@ -288,15 +322,15 @@ function stressLayout(request, opts = {}) {
288
322
  for (let w = 0; w < walks; w++) {
289
323
  const length = Math.exp(logMin + rand() * (logMax - logMin));
290
324
  const j = walk(i, length);
291
- if (j >= 0 && j !== i) {
292
- move(i, j, length, eta);
325
+ if (j >= 0 && j !== i && walked > 0) {
326
+ move(i, j, walked, eta);
293
327
  }
294
328
  }
295
329
  referenceTerm(i, eta);
296
330
  }
297
331
  }
298
- const cell = UNTANGLE_RADIUS;
299
- const key = (x, y) => Math.floor(x / cell) * 73856093 ^ Math.floor(y / cell) * 19349663;
332
+ const radius = UNTANGLE_LINKS * link;
333
+ const key = (x, y) => Math.floor(x / radius) * 73856093 ^ Math.floor(y / radius) * 19349663;
300
334
  for (let u = 0; u < UNTANGLE_PASSES; u++) {
301
335
  const grid = /* @__PURE__ */ new Map();
302
336
  for (let i = 0; i < n; i++) {
@@ -309,8 +343,8 @@ function stressLayout(request, opts = {}) {
309
343
  }
310
344
  }
311
345
  for (let i = 0; i < n; i++) {
312
- const cx = Math.floor(X[i] / cell);
313
- const cy = Math.floor(Y[i] / cell);
346
+ const cx = Math.floor(X[i] / radius);
347
+ const cy = Math.floor(Y[i] / radius);
314
348
  for (let gx = cx - 1; gx <= cx + 1; gx++) {
315
349
  for (let gy = cy - 1; gy <= cy + 1; gy++) {
316
350
  const list = grid.get(gx * 73856093 ^ gy * 19349663);
@@ -334,18 +368,18 @@ function stressLayout(request, opts = {}) {
334
368
  const dx = X[i] - X[j];
335
369
  const dy = Y[i] - Y[j];
336
370
  const mag = Math.sqrt(dx * dx + dy * dy);
337
- if (mag >= UNTANGLE_RADIUS) {
371
+ if (mag >= radius) {
338
372
  continue;
339
373
  }
340
374
  let rx;
341
375
  let ry;
342
376
  if (mag > 1e-6) {
343
- const r = UNTANGLE_PUSH * (UNTANGLE_RADIUS - mag) / (2 * mag);
377
+ const r = UNTANGLE_PUSH * (radius - mag) / (2 * mag);
344
378
  rx = r * dx;
345
379
  ry = r * dy;
346
380
  } else {
347
- rx = UNTANGLE_PUSH * UNTANGLE_RADIUS * (rand() - 0.5);
348
- ry = UNTANGLE_PUSH * UNTANGLE_RADIUS * (rand() - 0.5);
381
+ rx = UNTANGLE_PUSH * radius * (rand() - 0.5);
382
+ ry = UNTANGLE_PUSH * radius * (rand() - 0.5);
349
383
  }
350
384
  X[i] = X[i] + rx;
351
385
  Y[i] = Y[i] + ry;
@@ -374,28 +408,9 @@ function stressLayout(request, opts = {}) {
374
408
  Y[a + i] = 0.5 * ys[i] + 0.25 * (ys[i - 1] + ys[i + 1]);
375
409
  }
376
410
  }
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
- }
411
+ respace(X, Y, a, m, per[k]);
397
412
  }
398
- packComponents(c, X, Y, anySeed, gap);
413
+ packComponents(comp, members, X, Y, anySeed, gap);
399
414
  for (let k = 0; k < c.ids.length; k++) {
400
415
  const points = [];
401
416
  for (let i = 0; i < count[k]; i++) {
@@ -405,99 +420,110 @@ function stressLayout(request, opts = {}) {
405
420
  }
406
421
  return { nodePositions };
407
422
  }
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;
423
+ function respace(X, Y, a, m, per) {
424
+ const arc = new Float64Array(m);
425
+ for (let i = 1; i < m; i++) {
426
+ arc[i] = arc[i - 1] + Math.hypot(X[a + i] - X[a + i - 1], Y[a + i] - Y[a + i - 1]);
427
+ }
428
+ if (!arc[m - 1]) {
429
+ return;
430
+ }
431
+ const loss = per * (m - 1) - arc[m - 1];
432
+ if (loss > 0) {
433
+ extend(X, Y, a, a + 1, loss / 2);
434
+ extend(X, Y, a + m - 1, a + m - 2, loss / 2);
435
+ for (let i = 1; i < m; i++) {
436
+ arc[i] = arc[i - 1] + Math.hypot(X[a + i] - X[a + i - 1], Y[a + i] - Y[a + i - 1]);
415
437
  }
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
- }
438
+ }
439
+ const total = arc[m - 1];
440
+ const xs = X.slice(a, a + m);
441
+ const ys = Y.slice(a, a + m);
442
+ let seg = 1;
443
+ for (let i = 1; i + 1 < m; i++) {
444
+ const s = total * i / (m - 1);
445
+ while (seg < m - 1 && arc[seg] < s) {
446
+ seg++;
426
447
  }
427
- components++;
448
+ const span = arc[seg] - arc[seg - 1];
449
+ const t = span > 0 ? (s - arc[seg - 1]) / span : 0;
450
+ X[a + i] = xs[seg - 1] + (xs[seg] - xs[seg - 1]) * t;
451
+ Y[a + i] = ys[seg - 1] + (ys[seg] - ys[seg - 1]) * t;
428
452
  }
453
+ }
454
+ function extend(X, Y, end, next, by) {
455
+ const dx = X[end] - X[next];
456
+ const dy = Y[end] - Y[next];
457
+ const mag = Math.hypot(dx, dy);
458
+ if (mag > 0) {
459
+ X[end] = X[end] + dx / mag * by;
460
+ Y[end] = Y[end] + dy / mag * by;
461
+ }
462
+ }
463
+ function packComponents(comp, members, X, Y, anySeed, gap) {
429
464
  if (!anySeed) {
430
- for (let k = 0; k < components; k++) {
431
- rotateToPrincipalAxis(X, Y, comp, k);
465
+ for (const points of members) {
466
+ rotateToPrincipalAxis(X, Y, points);
432
467
  }
433
468
  }
434
- if (components < 2) {
469
+ if (members.length < 2) {
435
470
  return;
436
471
  }
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++) {
472
+ const minX = new Float64Array(members.length).fill(Infinity);
473
+ const minY = new Float64Array(members.length).fill(Infinity);
474
+ const maxY = new Float64Array(members.length).fill(-Infinity);
475
+ for (let i = 0; i < X.length; i++) {
441
476
  const k = comp[i];
442
477
  minX[k] = Math.min(minX[k], X[i]);
443
478
  minY[k] = Math.min(minY[k], Y[i]);
444
479
  maxY[k] = Math.max(maxY[k], Y[i]);
445
480
  }
446
- const shift = new Float64Array(components);
481
+ const shift = new Float64Array(members.length);
447
482
  let y = 0;
448
- for (let k = 0; k < components; k++) {
483
+ for (let k = 0; k < members.length; k++) {
449
484
  shift[k] = y - minY[k];
450
485
  y += maxY[k] - minY[k] + gap;
451
486
  }
452
- for (let i = 0; i < n; i++) {
487
+ for (let i = 0; i < X.length; i++) {
453
488
  const k = comp[i];
454
489
  X[i] = X[i] - minX[k];
455
490
  Y[i] = Y[i] + shift[k];
456
491
  }
457
492
  }
458
- function rotateToPrincipalAxis(X, Y, comp, k) {
459
- let m = 0;
493
+ function rotateToPrincipalAxis(X, Y, points) {
494
+ if (points.length < 3) {
495
+ return;
496
+ }
460
497
  let cx = 0;
461
498
  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;
499
+ for (const i of points) {
500
+ cx += X[i];
501
+ cy += Y[i];
471
502
  }
472
- cx /= m;
473
- cy /= m;
503
+ cx /= points.length;
504
+ cy /= points.length;
474
505
  let sxx = 0;
475
506
  let syy = 0;
476
507
  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
- }
508
+ for (const i of points) {
509
+ const dx = X[i] - cx;
510
+ const dy = Y[i] - cy;
511
+ sxx += dx * dx;
512
+ syy += dy * dy;
513
+ sxy += dx * dy;
485
514
  }
486
515
  const angle = -0.5 * Math.atan2(2 * sxy, sxx - syy);
487
516
  const cos = Math.cos(angle);
488
517
  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
- }
518
+ for (const i of points) {
519
+ const dx = X[i] - cx;
520
+ const dy = Y[i] - cy;
521
+ X[i] = cx + cos * dx - sin * dy;
522
+ Y[i] = cy + sin * dx + cos * dy;
496
523
  }
497
524
  }
498
525
 
499
526
  export {
500
- LAYOUT_ENGINES,
501
- LAYOUT_ENGINE_VALUES,
527
+ chains,
502
528
  stressLayout
503
529
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  clampZoom
3
- } from "./chunk-6UFS3EWI.js";
3
+ } from "./chunk-EPWYRSHH.js";
4
4
 
5
5
  // src/viewport.ts
6
6
  function axisScaleOf(scale, pixelRows) {