@jbrowse/bandage-core 4.0.28 → 4.0.29

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.
@@ -145,6 +145,9 @@ function isOffReference(node) {
145
145
  function backboneNodes(graph) {
146
146
  return graph.nodes.filter(isBackbone);
147
147
  }
148
+ function overlapsWindow(node, window) {
149
+ return node.stable.start < window.end && node.stable.start + node.length > window.start;
150
+ }
148
151
  function backbonePositions(backbone) {
149
152
  const positions = {};
150
153
  for (const node of backbone) {
@@ -445,6 +448,7 @@ function surveySegments(gfaGraph) {
445
448
  }
446
449
  return { canonical, traversals };
447
450
  }
451
+ var walkStart = (w) => w.start === -1 ? 0 : w.start;
448
452
  function anchorablePaths(gfaGraph) {
449
453
  return [
450
454
  ...gfaGraph.paths.map((p) => ({
@@ -456,10 +460,7 @@ function anchorablePaths(gfaGraph) {
456
460
  })),
457
461
  ...gfaGraph.walks.map((w) => ({
458
462
  name: `${w.sample}#${w.haplotype}#${w.contig}`,
459
- // `*` parses to -1, meaning the record declines to say; 0 is then the
460
- // only offset that can be assumed, and it makes the walk self-relative
461
- // rather than wrong
462
- start: w.start === -1 ? 0 : w.start,
463
+ start: walkStart(w),
463
464
  steps: w.segments.map((s) => ({
464
465
  id: s.id,
465
466
  strand: s.strand === "-" ? "-" : "+"
@@ -521,7 +522,11 @@ function convertGFAToGraph(gfaGraph, name = "Imported GFA") {
521
522
  }
522
523
  for (const gfaPath of gfaGraph.paths) {
523
524
  const nodeIds = gfaPath.path.split(",").map((segment) => nodeId(segment.slice(0, -1)));
524
- paths.push({ name: gfaPath.name, nodeIds });
525
+ paths.push({
526
+ name: gfaPath.name,
527
+ nodeIds,
528
+ start: pathOrigin(gfaPath.name).start
529
+ });
525
530
  recordPathEdges(nodeIds, gfaPath.name);
526
531
  }
527
532
  for (const walk of gfaGraph.walks) {
@@ -530,6 +535,7 @@ function convertGFAToGraph(gfaGraph, name = "Imported GFA") {
530
535
  paths.push({
531
536
  name: name2,
532
537
  nodeIds,
538
+ start: walkStart(walk),
533
539
  sample: walk.sample,
534
540
  haplotype: walk.haplotype,
535
541
  contig: walk.contig
@@ -1910,18 +1916,25 @@ function offsetTiers(name, tiers) {
1910
1916
  return tiers.map((t) => `${t} @${groupDigits(start)}`);
1911
1917
  }
1912
1918
  function pathLegend(paths, colors) {
1913
- const named = paths.map((p) => labelTiers(p.name));
1919
+ const firstOf = /* @__PURE__ */ new Map();
1920
+ paths.forEach((path, i) => {
1921
+ if (!firstOf.has(path.name)) {
1922
+ firstOf.set(path.name, i);
1923
+ }
1924
+ });
1925
+ const entries = [...firstOf].map(([name, i]) => ({ name, i }));
1926
+ const named = entries.map((e) => labelTiers(e.name));
1914
1927
  const candidates = [0, 1, 2].flatMap((t) => [
1915
- paths.map((_, i) => named[i][t]),
1916
- paths.map((p, i) => offsetTiers(p.name, named[i])[t])
1928
+ entries.map((_, k) => named[k][t]),
1929
+ entries.map((e, k) => offsetTiers(e.name, named[k])[t])
1917
1930
  ]);
1918
1931
  const distinct = candidates.find(
1919
- (labels2) => new Set(labels2).size === paths.length
1932
+ (labels2) => new Set(labels2).size === entries.length
1920
1933
  );
1921
1934
  const labels = distinct ?? candidates.at(-1);
1922
- return paths.map((path, i) => ({
1923
- name: path.name,
1924
- label: labels[i],
1935
+ return entries.map(({ name, i }, k) => ({
1936
+ name,
1937
+ label: labels[k],
1925
1938
  color: colors?.[i] ?? pathCssColor(i, paths.length)
1926
1939
  }));
1927
1940
  }
@@ -1962,21 +1975,26 @@ function sliceBetween(nodeIds, span, region, flanked = true) {
1962
1975
  }
1963
1976
  function walkRows(graph, region, unit) {
1964
1977
  const paths = graph.paths ?? [];
1965
- const reference = paths.find((p) => pathOrigin(p.name).name === graph.referencePath) ?? paths[0];
1966
- if (!reference || paths.length < 2) {
1978
+ const isReference2 = (p) => pathOrigin(p.name).name === graph.referencePath;
1979
+ const referencePieces2 = paths.some(isReference2) ? paths.filter(isReference2).sort((a, b) => (a.start ?? 0) - (b.start ?? 0)) : paths.slice(0, 1);
1980
+ const first = referencePieces2[0];
1981
+ const others = paths.filter((p) => !referencePieces2.includes(p));
1982
+ if (!first || others.length === 0) {
1967
1983
  return void 0;
1968
1984
  }
1969
1985
  const lengthOf = new Map(graph.nodes.map((n2) => [n2.id, n2.length]));
1970
- const referenceStart = graph.anchorPaths?.find((p) => p.name === pathOrigin(reference.name).name)?.start ?? 0;
1986
+ const referenceStart = first.start ?? 0;
1971
1987
  const cut = region && region.end > region.start ? region : void 0;
1972
1988
  const span = /* @__PURE__ */ new Map();
1973
- let pos = referenceStart;
1974
- for (const id of reference.nodeIds) {
1975
- const len = lengthOf.get(id) ?? 0;
1976
- if (!span.has(id)) {
1977
- span.set(id, { start: pos, end: pos + len });
1989
+ for (const piece of referencePieces2) {
1990
+ let pos = piece.start ?? 0;
1991
+ for (const id of piece.nodeIds) {
1992
+ const len = lengthOf.get(id) ?? 0;
1993
+ if (!span.has(id)) {
1994
+ span.set(id, { start: pos, end: pos + len });
1995
+ }
1996
+ pos += len;
1978
1997
  }
1979
- pos += len;
1980
1998
  }
1981
1999
  const flanked = cut === void 0 || [...span.values()].some((s) => s.end <= cut.start) && [...span.values()].some((s) => s.start >= cut.end);
1982
2000
  const rowOf = (path) => {
@@ -2026,18 +2044,15 @@ function walkRows(graph, region, unit) {
2026
2044
  runs
2027
2045
  };
2028
2046
  };
2029
- const meetsReference = (p) => p.nodeIds.some((id) => span.has(id));
2030
- const placedWalks = new Set(
2031
- paths.filter((p) => p !== reference && meetsReference(p)).map((p) => p.name)
2032
- );
2033
2047
  const origin = cut ? cut.start : referenceStart;
2034
2048
  return {
2035
2049
  origin,
2036
2050
  unit,
2037
- reference: rowOf(reference),
2038
- rows: paths.filter(
2039
- (p) => p !== reference && (meetsReference(p) || !placedWalks.has(p.name))
2040
- ).map(rowOf).sort(
2051
+ reference: rowOf({
2052
+ ...first,
2053
+ nodeIds: referencePieces2.flatMap((piece) => piece.nodeIds)
2054
+ }),
2055
+ rows: others.map(rowOf).sort(
2041
2056
  (a, b) => Number(b.complete) - Number(a.complete) || b.bp - a.bp || a.label.localeCompare(b.label)
2042
2057
  )
2043
2058
  };
@@ -2061,7 +2076,10 @@ function walkRowLayout(graph, region) {
2061
2076
  if (backbone.length === 0 || !walks) {
2062
2077
  return void 0;
2063
2078
  }
2064
- const nodePositions = backbonePositions(backbone);
2079
+ const overWindow = region ? backbone.filter((n2) => overlapsWindow(n2, region)) : backbone;
2080
+ const nodePositions = backbonePositions(
2081
+ overWindow.length > 0 ? overWindow : backbone
2082
+ );
2065
2083
  const rowLabels = [
2066
2084
  { label: walks.reference.label, y: 0 },
2067
2085
  ...walks.rows.map((row, i) => ({
@@ -4168,12 +4186,14 @@ function encodingSwatchCss(encoding) {
4168
4186
  }
4169
4187
 
4170
4188
  // src/walkHighlight.ts
4171
- function referenceOf(graph) {
4172
- return graph.referencePath ? graph.paths?.find((p) => pathOrigin(p.name).name === graph.referencePath) : void 0;
4189
+ var isReference = (graph, name) => !!graph.referencePath && pathOrigin(name).name === graph.referencePath;
4190
+ function piecesOf(graph, name) {
4191
+ return (graph.paths ?? []).filter((p) => p.name === name).sort((a, b) => (a.start ?? 0) - (b.start ?? 0));
4173
4192
  }
4174
4193
  function walkHighlight(graph, name) {
4175
- const path = graph.paths?.find((p) => p.name === name);
4176
- if (!path) {
4194
+ const pieces = piecesOf(graph, name);
4195
+ const first = pieces[0];
4196
+ if (!first) {
4177
4197
  return void 0;
4178
4198
  }
4179
4199
  const byId = new Map(graph.nodes.map((n2) => [n2.id, n2]));
@@ -4181,43 +4201,51 @@ function walkHighlight(graph, name) {
4181
4201
  graph.edges.forEach((e, i) => {
4182
4202
  edgeAt.set(`${e.from}>${e.to}`, i);
4183
4203
  });
4184
- const edgeIndexes = /* @__PURE__ */ new Set();
4185
- for (let i = 1; i < path.nodeIds.length; i++) {
4186
- const a = path.nodeIds[i - 1];
4187
- const b = path.nodeIds[i];
4188
- const index = edgeAt.get(`${a}>${b}`) ?? edgeAt.get(`${b}>${a}`);
4189
- if (index !== void 0) {
4190
- edgeIndexes.add(index);
4191
- }
4192
- }
4193
4204
  const lengthOf = (id) => byId.get(id)?.length ?? 0;
4194
- const bp = path.nodeIds.reduce((sum, id) => sum + lengthOf(id), 0);
4205
+ const bpOf = (path) => path.nodeIds.reduce((sum, id) => sum + lengthOf(id), 0);
4206
+ const bp = pieces.reduce((sum, piece) => sum + bpOf(piece), 0);
4207
+ const range = first.start === void 0 ? void 0 : {
4208
+ contig: first.contig ?? panSNContig(pathOrigin(name).name),
4209
+ start: first.start,
4210
+ end: Math.max(...pieces.map((p) => (p.start ?? 0) + bpOf(p)))
4211
+ };
4212
+ const span = range ? range.end - range.start : bp;
4213
+ const nodeIds = /* @__PURE__ */ new Set();
4214
+ const edgeIndexes = /* @__PURE__ */ new Set();
4195
4215
  const progress = /* @__PURE__ */ new Map();
4196
4216
  let before = 0;
4197
- for (const id of path.nodeIds) {
4198
- const length = lengthOf(id);
4199
- if (!progress.has(id)) {
4200
- progress.set(id, bp > 0 ? (before + length / 2) / bp : 0);
4201
- }
4202
- before += length;
4217
+ for (const piece of pieces) {
4218
+ if (range) {
4219
+ before = Math.max(before, (piece.start ?? 0) - range.start);
4220
+ }
4221
+ piece.nodeIds.forEach((id, i) => {
4222
+ nodeIds.add(id);
4223
+ if (i > 0) {
4224
+ const a = piece.nodeIds[i - 1];
4225
+ const index = edgeAt.get(`${a}>${id}`) ?? edgeAt.get(`${id}>${a}`);
4226
+ if (index !== void 0) {
4227
+ edgeIndexes.add(index);
4228
+ }
4229
+ }
4230
+ const length = lengthOf(id);
4231
+ if (!progress.has(id)) {
4232
+ progress.set(id, span > 0 ? (before + length / 2) / span : 0);
4233
+ }
4234
+ before += length;
4235
+ });
4203
4236
  }
4204
- const reference = referenceOf(graph);
4205
- const originName = pathOrigin(name).name;
4206
- const origin = graph.anchorPaths?.find((a) => a.name === originName && a.length === bp) ?? graph.anchorPaths?.find((a) => a.name === originName);
4237
+ const reference = isReference(graph, name);
4238
+ const referencePieces2 = reference ? [] : (graph.paths ?? []).filter((p) => isReference(graph, p.name));
4207
4239
  return {
4208
4240
  name,
4209
- reference: reference === path,
4210
- range: origin ? {
4211
- contig: path.contig ?? panSNContig(origin.name),
4212
- start: origin.start,
4213
- end: origin.start + origin.length
4214
- } : void 0,
4215
- nodeIds: new Set(path.nodeIds),
4241
+ reference,
4242
+ range,
4243
+ nodeIds,
4216
4244
  edgeIndexes,
4217
4245
  progress,
4218
- steps: path.nodeIds.length,
4246
+ steps: pieces.reduce((sum, piece) => sum + piece.nodeIds.length, 0),
4219
4247
  bp,
4220
- referenceBp: reference && reference !== path ? reference.nodeIds.reduce((sum, id) => sum + lengthOf(id), 0) : void 0
4248
+ referenceBp: referencePieces2.length > 0 ? referencePieces2.reduce((sum, piece) => sum + bpOf(piece), 0) : void 0
4221
4249
  };
4222
4250
  }
4223
4251
  function laneColors(graph, walk, encoding, ramp) {
@@ -4232,14 +4260,13 @@ function laneColors(graph, walk, encoding, ramp) {
4232
4260
  return colors;
4233
4261
  }
4234
4262
  function reversedNodes(graph, walk) {
4235
- const reference = referenceOf(graph);
4236
4263
  const reversed = /* @__PURE__ */ new Set();
4237
4264
  let bp = 0;
4238
- if (!reference || walk.reference) {
4265
+ const referencePath = graph.referencePath;
4266
+ if (!referencePath || walk.reference) {
4239
4267
  return { reversed, bp };
4240
4268
  }
4241
4269
  const walkPath = pathOrigin(walk.name).name;
4242
- const referencePath = pathOrigin(reference.name).name;
4243
4270
  const byId = new Map(graph.nodes.map((n2) => [n2.id, n2]));
4244
4271
  for (const id of walk.progress.keys()) {
4245
4272
  const node = byId.get(id);
@@ -4336,7 +4363,9 @@ function walkPosition(walk, nodeId, length) {
4336
4363
  if (progress === void 0 || !range) {
4337
4364
  return void 0;
4338
4365
  }
4339
- const start = Math.round(range.start + progress * walk.bp - length / 2);
4366
+ const start = Math.round(
4367
+ range.start + progress * (range.end - range.start) - length / 2
4368
+ );
4340
4369
  return `${range.contig}:${groupDigits(start)}-${groupDigits(start + length)}`;
4341
4370
  }
4342
4371
 
@@ -4389,11 +4418,26 @@ function facetGrid({
4389
4418
  }
4390
4419
  return best;
4391
4420
  }
4392
- function facetCells(names, by) {
4421
+ function facetSettingOf(facet) {
4422
+ const raw = typeof facet === "string" ? { field: facet } : facet ?? {};
4423
+ const field = raw.field === "walk" || raw.field === "sample" ? raw.field : "";
4424
+ return {
4425
+ field,
4426
+ domain: [...raw.domain ?? []],
4427
+ ...raw.columns === void 0 ? {} : { columns: raw.columns }
4428
+ };
4429
+ }
4430
+ function domainFirst(names, domain) {
4431
+ const rank = new Map(domain.map((name, i) => [name, i]));
4432
+ const listed = (name) => rank.get(name) ?? domain.length;
4433
+ return names.map((name, i) => ({ name, i })).sort((a, b) => listed(a.name) - listed(b.name) || a.i - b.i).map(({ name }) => name);
4434
+ }
4435
+ function facetCells(names, by, domain = []) {
4436
+ const wrapOrder = domainFirst(names, domain);
4393
4437
  const wrap = {
4394
4438
  columns: void 0,
4395
4439
  count: names.length,
4396
- cells: names.map((_, i) => i)
4440
+ cells: names.map((name) => wrapOrder.indexOf(name))
4397
4441
  };
4398
4442
  if (by === "walk") {
4399
4443
  return wrap;
@@ -4403,7 +4447,10 @@ function facetCells(names, by) {
4403
4447
  const n2 = Number(haplotype);
4404
4448
  return contig !== void 0 && Number.isInteger(n2) && n2 >= 0 ? { sample, column: Math.max(0, n2 - 1) } : void 0;
4405
4449
  });
4406
- const rows = [...new Set(places.map((p) => p?.sample))];
4450
+ const rows = domainFirst(
4451
+ [...new Set(places.flatMap((p) => p ? [p.sample] : []))],
4452
+ domain
4453
+ );
4407
4454
  const columns = Math.max(1, ...places.map((p) => p ? p.column + 1 : 1));
4408
4455
  const cells = places.map(
4409
4456
  (p) => p ? rows.indexOf(p.sample) * columns + p.column : -1
@@ -4741,7 +4788,7 @@ function svgCanvas(width, height) {
4741
4788
  }
4742
4789
 
4743
4790
  // src/version.ts
4744
- var version = "4.0.28";
4791
+ var version = "4.0.29";
4745
4792
 
4746
4793
  // src/viewport.ts
4747
4794
  function axisScaleOf(scale, pixelRows) {
@@ -4848,8 +4895,8 @@ function figureSvg(graph, layout, o = {}) {
4848
4895
  const layers = o.walks ?? [];
4849
4896
  const walkRamp = layers.some((l) => l.color?.field === "reference") ? referenceRamp ?? computeReferenceRamp(graph, rampDomain) : void 0;
4850
4897
  const lift = layers.length > 0 && !layout.tubeMap ? walkLift(graph, layers, walkRamp) : void 0;
4851
- const by = o.facet ?? "none";
4852
- const panels = by !== "none" && lift && lift.walks.length > 1 ? facetLifts(graph, lift, layers, walkRamp) : void 0;
4898
+ const facet = facetSettingOf(o.facet);
4899
+ const panels = facet.field && lift && lift.walks.length > 1 ? facetLifts(graph, lift, layers, walkRamp) : void 0;
4853
4900
  const labels = new Map(
4854
4901
  (graph.paths?.length ? pathLegend(graph.paths) : []).map((p) => [
4855
4902
  p.name,
@@ -4966,7 +5013,8 @@ function figureSvg(graph, layout, o = {}) {
4966
5013
  if (panels) {
4967
5014
  const place = facetCells(
4968
5015
  panels.map((p) => p.walks[0].name),
4969
- by
5016
+ facet.field || "walk",
5017
+ facet.domain
4970
5018
  );
4971
5019
  const grid = facetGrid({
4972
5020
  count: place.count,
@@ -4974,7 +5022,7 @@ function figureSvg(graph, layout, o = {}) {
4974
5022
  pixelRows,
4975
5023
  width,
4976
5024
  room,
4977
- columns: place.columns ?? o.columns
5025
+ columns: place.columns ?? facet.columns
4978
5026
  });
4979
5027
  panels.forEach((panel, i) => {
4980
5028
  const cell = place.cells[i];
@@ -5343,6 +5391,135 @@ function featuresOnBackbone(features, backbone, aliases) {
5343
5391
  });
5344
5392
  }
5345
5393
 
5394
+ // src/gbzJoin.ts
5395
+ var UNKNOWN_SAMPLE = "unknown";
5396
+ function parseWalk(line) {
5397
+ const fields = line.split(" ");
5398
+ return {
5399
+ fields,
5400
+ name: fields.slice(1, 4).join(" "),
5401
+ start: +fields[4],
5402
+ end: +fields[5]
5403
+ };
5404
+ }
5405
+ var stepsOf = (walk) => walk.fields[6].match(/[<>][^<>]+/g) ?? [];
5406
+ function segmentLengths(lines) {
5407
+ const lengths = /* @__PURE__ */ new Map();
5408
+ for (const line of lines) {
5409
+ if (line.startsWith("S ")) {
5410
+ const [, id, sequence, ...tags] = line.split(" ");
5411
+ const ln = tags.find((tag) => tag.startsWith("LN:i:"));
5412
+ lengths.set(
5413
+ id,
5414
+ sequence === "*" && ln ? +ln.slice("LN:i:".length) : sequence.length
5415
+ );
5416
+ }
5417
+ }
5418
+ return lengths;
5419
+ }
5420
+ function extend(a, b, lengths) {
5421
+ if (b.end <= a.end) {
5422
+ return a;
5423
+ }
5424
+ const steps = stepsOf(b);
5425
+ let pos = b.start;
5426
+ for (const [i, step] of steps.entries()) {
5427
+ if (pos === a.end) {
5428
+ const fields = [...a.fields];
5429
+ fields[5] = String(b.end);
5430
+ fields[6] = a.fields[6] + steps.slice(i).join("");
5431
+ return { ...a, fields, end: b.end };
5432
+ }
5433
+ const length = lengths.get(step.slice(1));
5434
+ if (pos > a.end || length === void 0) {
5435
+ return void 0;
5436
+ }
5437
+ pos += length;
5438
+ }
5439
+ return void 0;
5440
+ }
5441
+ function joinPieces(pieces, lengths, startsPath) {
5442
+ const joined = [];
5443
+ for (const piece of pieces.sort((a, b) => a.start - b.start)) {
5444
+ const last = joined.at(-1);
5445
+ const [, sample, haplotype, contig] = piece.fields;
5446
+ const joins = last !== void 0 && (piece.start < last.end || piece.start === last.end && !startsPath(sample, +haplotype, contig, piece.start));
5447
+ const extended = joins ? extend(last, piece, lengths) : void 0;
5448
+ if (extended) {
5449
+ joined[joined.length - 1] = extended;
5450
+ } else {
5451
+ joined.push(piece);
5452
+ }
5453
+ }
5454
+ return joined;
5455
+ }
5456
+ var flip2 = (sign) => sign === "+" ? "-" : "+";
5457
+ function* stepLinks(walk) {
5458
+ const steps = stepsOf(walk).map((step) => ({
5459
+ id: step.slice(1),
5460
+ sign: step.startsWith(">") ? "+" : "-"
5461
+ }));
5462
+ for (let i = 1; i < steps.length; i++) {
5463
+ const a = steps[i - 1];
5464
+ const b = steps[i];
5465
+ yield {
5466
+ forward: `L ${a.id} ${a.sign} ${b.id} ${b.sign} 0M`,
5467
+ reverse: `L ${b.id} ${flip2(b.sign)} ${a.id} ${flip2(a.sign)} 0M`
5468
+ };
5469
+ }
5470
+ }
5471
+ function joinCuts(gfas, startsPath) {
5472
+ if (gfas.length <= 1) {
5473
+ return gfas[0] ?? "";
5474
+ }
5475
+ const cuts = gfas.map((gfa) => gfa.split("\n").filter((line) => line !== ""));
5476
+ const ofType = (cut, type) => cut.filter((line) => line.startsWith(`${type} `));
5477
+ const lengths = segmentLengths(cuts.flat());
5478
+ let unknownOffset = 0;
5479
+ const walksByCut = cuts.map((cut) => {
5480
+ const walks2 = ofType(cut, "W").map(parseWalk);
5481
+ const offset = unknownOffset;
5482
+ for (const walk of walks2.filter((w) => w.fields[1] === UNKNOWN_SAMPLE)) {
5483
+ const haplotype = +walk.fields[2] + offset;
5484
+ walk.fields[2] = String(haplotype);
5485
+ walk.name = walk.fields.slice(1, 4).join(" ");
5486
+ unknownOffset = Math.max(unknownOffset, haplotype);
5487
+ }
5488
+ return walks2;
5489
+ });
5490
+ const byName = /* @__PURE__ */ new Map();
5491
+ for (const walk of [
5492
+ ...walksByCut.flatMap((walks2) => walks2.slice(0, 1)),
5493
+ ...walksByCut.flatMap((walks2) => walks2.slice(1))
5494
+ ]) {
5495
+ const pieces = byName.get(walk.name);
5496
+ if (pieces) {
5497
+ pieces.push(walk);
5498
+ } else {
5499
+ byName.set(walk.name, [walk]);
5500
+ }
5501
+ }
5502
+ const walks = [...byName.values()].flatMap(
5503
+ (pieces) => joinPieces(pieces, lengths, startsPath)
5504
+ );
5505
+ const links = new Set(cuts.flatMap((cut) => ofType(cut, "L")));
5506
+ for (const walk of walks) {
5507
+ for (const link of stepLinks(walk)) {
5508
+ if (!links.has(link.forward) && !links.has(link.reverse)) {
5509
+ links.add(link.forward);
5510
+ }
5511
+ }
5512
+ }
5513
+ const lines = /* @__PURE__ */ new Set([
5514
+ ...ofType(cuts[0], "H"),
5515
+ ...cuts.flatMap((cut) => ofType(cut, "S")),
5516
+ ...links,
5517
+ ...walks.map((walk) => walk.fields.join(" "))
5518
+ ]);
5519
+ return `${[...lines].join("\n")}
5520
+ `;
5521
+ }
5522
+
5346
5523
  // src/gbzWindow.ts
5347
5524
  var NoReferenceSampleError = class extends Error {
5348
5525
  constructor(anchor, referenceSamples) {
@@ -5399,13 +5576,13 @@ async function referencePathQuery(db, referenceSample2, refName) {
5399
5576
  haplotype: path.name.haplotype
5400
5577
  } : void 0;
5401
5578
  }
5402
- function nodeLimitError(error, limit, windowBp) {
5579
+ function nodeLimitError(error, limit, windowBp, walkedFrom = 0) {
5403
5580
  const isLimit = error instanceof Error && (error.name === "SubgraphLimitError" || /^Subgraph size limit of \d+ nodes exceeded/.test(error.message));
5404
5581
  if (!isLimit) {
5405
5582
  return void 0;
5406
5583
  } else {
5407
5584
  const walked = error.walkedBp;
5408
- const fits = typeof walked === "number" && walked > 0 ? Math.floor(walked * 0.8) : Math.floor(windowBp / 2);
5585
+ const fits = typeof walked === "number" && walked > 0 ? Math.floor((walkedFrom + walked) * 0.8) : Math.floor(windowBp / 2);
5409
5586
  return new NodeLimitError(limit, windowBp, Math.max(fits, 1));
5410
5587
  }
5411
5588
  }
@@ -5424,19 +5601,6 @@ async function referencePieces(db, query, start, end) {
5424
5601
  end: pieceEnd
5425
5602
  }));
5426
5603
  }
5427
- function joinCuts(gfas) {
5428
- const cuts = gfas.map((gfa) => gfa.split("\n").filter((line) => line !== ""));
5429
- const ofType = (cut, type) => cut.filter((line) => line.startsWith(type));
5430
- const lines = /* @__PURE__ */ new Set([
5431
- ...ofType(cuts[0] ?? [], "H"),
5432
- ...cuts.flatMap((cut) => ofType(cut, "S")),
5433
- ...cuts.flatMap((cut) => ofType(cut, "L")),
5434
- ...cuts.flatMap((cut) => ofType(cut, "W").slice(0, 1)),
5435
- ...cuts.flatMap((cut) => ofType(cut, "W").slice(1))
5436
- ]);
5437
- return lines.size === 0 ? "" : `${[...lines].join("\n")}
5438
- `;
5439
- }
5440
5604
  async function cutWindowGFA(db, query, start, end, { keep, ...opts }) {
5441
5605
  if (!query) {
5442
5606
  return "";
@@ -5449,15 +5613,24 @@ async function cutWindowGFA(db, query, start, end, { keep, ...opts }) {
5449
5613
  haplotypes: "all",
5450
5614
  ...keep === void 0 ? {} : { keep }
5451
5615
  }).catch((error) => {
5452
- throw nodeLimitError(error, opts.limit, end - start) ?? error;
5616
+ throw nodeLimitError(
5617
+ error,
5618
+ opts.limit,
5619
+ end - start,
5620
+ piece.start - start
5621
+ ) ?? error;
5453
5622
  })
5454
5623
  )
5455
5624
  );
5625
+ const paths = await db.paths();
5456
5626
  return joinCuts(
5457
5627
  await Promise.all(
5458
5628
  subgraphs.flatMap(
5459
5629
  (subgraph) => subgraph ? [subgraph.toGFA({ names: "resolved" })] : []
5460
5630
  )
5631
+ ),
5632
+ (sample, haplotype, contig, at) => paths.some(
5633
+ ({ name }) => name.sample === sample && name.haplotype === haplotype && name.contig === contig && name.fragment === at
5461
5634
  )
5462
5635
  );
5463
5636
  }
@@ -5596,6 +5769,7 @@ export {
5596
5769
  FACET_TITLE_PX,
5597
5770
  FACET_PAD_PX,
5598
5771
  facetGrid,
5772
+ facetSettingOf,
5599
5773
  facetCells,
5600
5774
  genePins,
5601
5775
  HALO_FACTOR,
@@ -17,7 +17,7 @@ import {
17
17
  openGbz,
18
18
  parseRegion,
19
19
  refNameBinding
20
- } from "../chunks/chunk-G3HO75W7.js";
20
+ } from "../chunks/chunk-QN464POI.js";
21
21
 
22
22
  // src/cli/figure.ts
23
23
  import { access, open, readFile, writeFile } from "node:fs/promises";
package/dist/index.js CHANGED
@@ -42,6 +42,7 @@ import {
42
42
  facetCells,
43
43
  facetGrid,
44
44
  facetLifts,
45
+ facetSettingOf,
45
46
  featuresOnBackbone,
46
47
  figureSvg,
47
48
  fitTransform,
@@ -100,7 +101,7 @@ import {
100
101
  wellKnownSample,
101
102
  yToXOf,
102
103
  zoomAbout
103
- } from "./chunks/chunk-G3HO75W7.js";
104
+ } from "./chunks/chunk-QN464POI.js";
104
105
 
105
106
  // src/index.ts
106
107
  import { getDpr } from "@jbrowse/render-core/canvas2dUtils";
@@ -1715,6 +1716,7 @@ function bubbleSubgraph(graph, segmentIds) {
1715
1716
  const keep = new Set(segmentIds);
1716
1717
  const nodes = graph.nodes.filter((n) => keep.has(n.name));
1717
1718
  const ids = new Set(nodes.map((n) => n.id));
1719
+ const lengthOf = new Map(graph.nodes.map((n) => [n.id, n.length]));
1718
1720
  const kept = /* @__PURE__ */ new Map();
1719
1721
  const paths = graph.paths?.flatMap((p) => {
1720
1722
  const first = p.nodeIds.findIndex((id) => ids.has(id));
@@ -1723,7 +1725,14 @@ function bubbleSubgraph(graph, segmentIds) {
1723
1725
  }
1724
1726
  const nodeIds = p.nodeIds.filter((id) => ids.has(id));
1725
1727
  kept.set(p.name, { first, nodeIds });
1726
- return [{ ...p, nodeIds }];
1728
+ const before = p.nodeIds.slice(0, first).reduce((sum, id) => sum + (lengthOf.get(id) ?? 0), 0);
1729
+ return [
1730
+ {
1731
+ ...p,
1732
+ nodeIds,
1733
+ ...p.start === void 0 ? {} : { start: p.start + before }
1734
+ }
1735
+ ];
1727
1736
  });
1728
1737
  return {
1729
1738
  name: graph.name,
@@ -1790,6 +1799,7 @@ export {
1790
1799
  facetCells,
1791
1800
  facetGrid,
1792
1801
  facetLifts,
1802
+ facetSettingOf,
1793
1803
  featuresOnBackbone,
1794
1804
  figureSvg,
1795
1805
  findHoveredEdge,
@@ -8,6 +8,10 @@ export declare function isAnchored(node: GraphNode): node is AnchoredNode;
8
8
  export declare function isBackbone(node: GraphNode): node is AnchoredNode;
9
9
  export declare function isOffReference(node: GraphNode): node is AnchoredNode;
10
10
  export declare function backboneNodes(graph: Graph): AnchoredNode[];
11
+ export declare function overlapsWindow(node: AnchoredNode, window: {
12
+ start: number;
13
+ end: number;
14
+ }): boolean;
11
15
  export declare function backbonePositions(backbone: AnchoredNode[]): Record<string, NodeSegment[]>;
12
16
  export declare function firstNodeAtOrAfter(nodes: AnchoredNode[], bp: number): number;
13
17
  export declare function backboneSpan(backbone: AnchoredNode[]): number;
@@ -20,7 +20,28 @@ export declare function facetGrid({ count, bounds, pixelRows, width, room, colum
20
20
  columns?: number;
21
21
  }): FacetGrid;
22
22
  export type FacetBy = 'walk' | 'sample';
23
- export declare function facetCells(names: string[], by: FacetBy): {
23
+ /**
24
+ * The facet as a session or a figure spec writes it, in the shape every
25
+ * JBrowse display's `facet` takes: the field the panels split on, `walk` for
26
+ * a panel per lifted walk or `sample` for a row per sample and a column per
27
+ * haplotype, the order the panels take, and how many go across under `walk`.
28
+ */
29
+ export interface FacetSetting {
30
+ field: '' | FacetBy;
31
+ /** The walks, or under `sample` the samples, whose panels come first, in order. */
32
+ domain: string[];
33
+ /** How many panels go across; unset takes whichever count draws each largest. */
34
+ columns?: number;
35
+ }
36
+ /** What a session or a spec may write for the facet: the field bare, or the setting's members. */
37
+ export type FacetInput = string | {
38
+ field?: string;
39
+ domain?: readonly string[];
40
+ columns?: number;
41
+ };
42
+ /** A facet written bare is its field, and a field the pane cannot split on is none. */
43
+ export declare function facetSettingOf(facet: FacetInput | undefined): FacetSetting;
44
+ export declare function facetCells(names: string[], by: FacetBy, domain?: readonly string[]): {
24
45
  columns: undefined;
25
46
  count: number;
26
47
  cells: number[];
@@ -1,5 +1,5 @@
1
1
  import type { ColorScheme } from './colorSchemes.js';
2
- import type { FacetBy } from './facetGrid.js';
2
+ import type { FacetInput } from './facetGrid.js';
3
3
  import type { GeneModel } from './genes/genePins.js';
4
4
  import type { NodeWidth } from './nodeWidths.js';
5
5
  import type { Graph, LayoutResult } from './types.js';
@@ -8,8 +8,7 @@ export interface FigureOptions {
8
8
  width?: number;
9
9
  height?: number;
10
10
  walks?: WalkLayer[];
11
- facet?: 'none' | FacetBy;
12
- columns?: number;
11
+ facet?: FacetInput;
13
12
  colorScheme?: ColorScheme;
14
13
  nodeWidth?: NodeWidth;
15
14
  showDeletionEdges?: boolean;
@@ -0,0 +1,2 @@
1
+ export type StartsPath = (sample: string, haplotype: number, contig: string, at: number) => boolean;
2
+ export declare function joinCuts(gfas: string[], startsPath: StartsPath): string;
@@ -16,7 +16,7 @@ export declare function resolveReferenceSample({ configured, anchorPrefix, refer
16
16
  referenceSamples: string[];
17
17
  }): string;
18
18
  export declare function referencePathQuery(db: GBZBase, referenceSample: string, refName: string): Promise<PathQuery | undefined>;
19
- export declare function nodeLimitError(error: unknown, limit: number, windowBp: number): NodeLimitError | undefined;
19
+ export declare function nodeLimitError(error: unknown, limit: number, windowBp: number, walkedFrom?: number): NodeLimitError | undefined;
20
20
  export interface GbzWindowOptions {
21
21
  context: number;
22
22
  snarls: RangeOptions['snarls'];
@@ -42,8 +42,8 @@ export type { GbzSource } from './gbzCut.js';
42
42
  export { version } from './version.js';
43
43
  export type { FigureOptions } from './figure.js';
44
44
  export { svgCanvas } from './renderer/svgCanvas.js';
45
- export { FACET_GAP_PX, FACET_PAD_PX, FACET_TITLE_PX, facetCells, facetGrid, } from './facetGrid.js';
46
- export type { FacetBy, FacetGrid } from './facetGrid.js';
45
+ export { FACET_GAP_PX, FACET_PAD_PX, FACET_TITLE_PX, facetCells, facetGrid, facetSettingOf, } from './facetGrid.js';
46
+ export type { FacetBy, FacetGrid, FacetInput, FacetSetting } from './facetGrid.js';
47
47
  export type { LiftedWalk, WalkLift } from './walkHighlight.js';
48
48
  export { WALK_FIELDS, WALK_SCHEMES, encodingStops, encodingSwatchCss, resolveEncoding, schemeCss, } from './walkEncoding.js';
49
49
  export type { WalkEncoding, WalkField, WalkLayer, WalkScheme, } from './walkEncoding.js';
@@ -19,6 +19,7 @@ export interface GraphEdge {
19
19
  export interface GraphPath {
20
20
  name: string;
21
21
  nodeIds: string[];
22
+ start?: number;
22
23
  sample?: string;
23
24
  haplotype?: number;
24
25
  contig?: string;
@@ -1 +1 @@
1
- export declare const version = "4.0.28";
1
+ export declare const version = "4.0.29";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/bandage-core",
3
- "version": "4.0.28",
3
+ "version": "4.0.29",
4
4
  "description": "Pangenome graph layout and drawing with no host: GFA in, the Bandage force-directed engine and reference-anchored layouts, a Canvas2D renderer, hit testing and labels out",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {