@jbrowse/bandage-core 4.0.24 → 4.0.25

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.
@@ -1,38 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- cutWindowGFA,
3
+ HPRC_GBZ,
4
+ cutGbzRegion,
5
+ featuresOnBackbone,
4
6
  figureSvg,
5
7
  forceLayout,
6
- haplotypeWanted,
8
+ genesFromBed,
9
+ genesFromGff3Lines,
10
+ genesFromText,
11
+ graphBackbone,
7
12
  layoutModeByValue,
8
13
  loadBandage,
9
14
  loadGraph,
10
- referencePathQuery,
11
- referenceSamplesOf,
12
- resolveReferenceSample
13
- } from "../chunks/chunk-4AOHEU76.js";
15
+ openGbz,
16
+ parseRegion
17
+ } from "../chunks/chunk-B5ZHLU4N.js";
14
18
 
15
19
  // src/cli/figure.ts
16
20
  import { open, readFile, writeFile } from "node:fs/promises";
17
21
  import path from "node:path";
18
22
  import { parseArgs } from "node:util";
19
23
  import { gunzipSync } from "node:zlib";
20
- import { GBZBase } from "@gmod/gbz-base";
21
- var HPRC = {
22
- db: "https://s3-us-west-2.amazonaws.com/human-pangenomics/pangenomes/freeze/release2/minigraph-cactus/v2.1/hprc-v2.1-mc-grch38/hprc-v2.1-mc-grch38.gbz.db",
23
- index: "https://jbrowse.org/demos/hprc/hprc-v2.1-mc-grch38.haplotype-index.anchored.db"
24
- };
25
- var WALK_LIMIT = 1e5;
26
- function parseRegion(text) {
27
- const m = /^\s*([^:\s]+):([\d,]+)-([\d,]+)\s*$/.exec(text);
28
- if (!m) {
29
- throw new Error(
30
- `"${text}" is not a region like chr6:160,614,798-160,647,758`
31
- );
32
- }
33
- const n = (s) => Number(s.replaceAll(",", ""));
34
- return { refName: m[1], start: n(m[2]), end: n(m[3]) };
35
- }
24
+ import { TabixIndexedFile } from "@gmod/tabix";
36
25
  var isUrl = (s) => /^https?:\/\//.test(s);
37
26
  function byteSource(location) {
38
27
  if (isUrl(location)) {
@@ -64,36 +53,40 @@ function byteSource(location) {
64
53
  }
65
54
  };
66
55
  }
67
- async function gfaText(location) {
56
+ async function text(location) {
68
57
  const bytes = isUrl(location) ? new Uint8Array(await (await fetch(location)).arrayBuffer()) : await readFile(location);
69
58
  const gzipped = bytes[0] === 31 && bytes[1] === 139;
70
59
  return new TextDecoder().decode(gzipped ? gunzipSync(bytes) : bytes);
71
60
  }
72
- async function cutGbz(gbz) {
73
- const { db, index } = gbz.db === "hprc" ? HPRC : gbz;
74
- const base = await GBZBase.open(
75
- byteSource(db),
76
- index ? { haplotypeIndex: byteSource(index) } : {}
61
+ async function readGenes(genes, graph, resolve) {
62
+ const backbone = graphBackbone(graph);
63
+ if (!backbone) {
64
+ return [];
65
+ }
66
+ const file = resolve(genes.file);
67
+ const format = genes.format ?? (/\.bed(\.gz)?$/i.test(genes.file) ? "bed" : "gff3");
68
+ if (!genes.index) {
69
+ return featuresOnBackbone(genesFromText(await text(file)), backbone);
70
+ }
71
+ const index = resolve(genes.index);
72
+ const csi = index.endsWith(".csi");
73
+ const tabix = new TabixIndexedFile(
74
+ isUrl(file) ? { url: file, ...csi ? { csiUrl: index } : { tbiUrl: index } } : { path: file, ...csi ? { csiPath: index } : { tbiPath: index } }
77
75
  );
78
- const samples = await referenceSamplesOf(base);
79
- const sample = resolveReferenceSample({
80
- configured: "",
81
- anchorPrefix: gbz.referenceSample ?? samples[0] ?? "",
82
- referenceSamples: samples
83
- });
84
- const region = parseRegion(gbz.region);
85
- const query = await referencePathQuery(base, sample, region.refName);
86
- if (!query) {
87
- throw new Error(`${sample} has no indexed path named ${region.refName}`);
76
+ const indexed = new Set(await tabix.getReferenceSequenceNames());
77
+ const lines = [];
78
+ for (const contig of backbone.contigs) {
79
+ const refName = [contig.contig, contig.refName].find((n) => indexed.has(n));
80
+ if (refName !== void 0) {
81
+ await tabix.getLines(refName, contig.start, contig.end, {
82
+ lineCallback: (line) => lines.push(line)
83
+ });
84
+ }
88
85
  }
89
- const wanted = gbz.haplotypes?.length ? gbz.haplotypes : void 0;
90
- const text = await cutWindowGFA(base, query, region.start, region.end, {
91
- context: 1e3,
92
- snarls: "contained",
93
- limit: WALK_LIMIT,
94
- ...wanted ? { keep: (name) => haplotypeWanted(name, wanted) } : {}
95
- });
96
- return { text, region, name: `${sample} ${gbz.region}` };
86
+ return featuresOnBackbone(
87
+ format === "gff3" ? genesFromGff3Lines(lines) : genesFromBed(lines.join("\n")),
88
+ backbone
89
+ );
97
90
  }
98
91
  var engine = async (request) => {
99
92
  const bandage = await loadBandage();
@@ -101,15 +94,26 @@ var engine = async (request) => {
101
94
  const result = bandage.computeLayout(request.graph, request.options);
102
95
  return { result, duration: performance.now() - start };
103
96
  };
104
- async function renderSpec(spec, base = ".") {
105
- const source = spec.gbz ? await cutGbz(spec.gbz) : spec.gfa ? {
106
- text: await gfaText(
107
- isUrl(spec.gfa) ? spec.gfa : path.resolve(base, spec.gfa)
108
- ),
109
- region: spec.region ? parseRegion(spec.region) : void 0,
110
- name: path.basename(spec.gfa)
111
- } : void 0;
112
- if (!source) {
97
+ async function renderSpec(spec, base) {
98
+ const resolve = (location) => isUrl(location) ? location : path.resolve(base, location);
99
+ let source;
100
+ if (spec.gbz) {
101
+ const { db, index } = spec.gbz.db === "hprc" ? HPRC_GBZ : spec.gbz;
102
+ const cut = await cutGbzRegion(
103
+ await openGbz(
104
+ byteSource(resolve(db)),
105
+ index ? byteSource(resolve(index)) : void 0
106
+ ),
107
+ spec.gbz
108
+ );
109
+ source = { ...cut, name: `${cut.sample} ${spec.gbz.region}` };
110
+ } else if (spec.gfa) {
111
+ source = {
112
+ text: await text(resolve(spec.gfa)),
113
+ name: path.basename(spec.gfa),
114
+ region: spec.region ? parseRegion(spec.region) : void 0
115
+ };
116
+ } else {
113
117
  throw new Error("a spec names its graph as `gfa` or `gbz`");
114
118
  }
115
119
  const graph = loadGraph(source.text, source.name, {
@@ -128,7 +132,8 @@ async function renderSpec(spec, base = ".") {
128
132
  ...spec,
129
133
  walks: spec.walks?.map((w) => typeof w === "string" ? { walk: w } : w),
130
134
  region: source.region,
131
- metadata: JSON.stringify(spec)
135
+ genes: spec.genes ? await readGenes(spec.genes, graph, resolve) : void 0,
136
+ spec
132
137
  });
133
138
  }
134
139
  async function main() {
package/dist/index.js CHANGED
@@ -8,10 +8,13 @@ import {
8
8
  FADED_ALPHA,
9
9
  FIT_PADDING,
10
10
  FORCE_LAYOUT_LABEL,
11
+ HALO_FACTOR,
12
+ HPRC_GBZ,
11
13
  LABEL_CHAR_PX,
12
14
  LABEL_PAD,
13
15
  LABEL_PX,
14
16
  LAYOUT_MODES,
17
+ LEGEND_INSET_PX,
15
18
  LIFT_BACKDROP_COLOR,
16
19
  NODE_WIDTHS,
17
20
  REFERENCE_RAMP_MAX_HUE,
@@ -29,6 +32,7 @@ import {
29
32
  computeReferenceRamp,
30
33
  contains,
31
34
  curveBounds,
35
+ cutGbzRegion,
32
36
  cutWindowGFA,
33
37
  deletionEdges,
34
38
  drawingBounds,
@@ -42,15 +46,20 @@ import {
42
46
  fadeAbgr,
43
47
  featuresOnBackbone,
44
48
  figureSvg,
45
- firstNodeAtOrAfter,
46
49
  fitTransform,
47
50
  forceLayout,
48
51
  formatBp,
52
+ geneCoverageNote,
53
+ genePins,
54
+ genesFromBed,
55
+ genesFromGff3Lines,
56
+ genesFromText,
49
57
  getNodeColor,
50
58
  graphBackbone,
51
59
  haplotypeWanted,
52
60
  isBackbone,
53
61
  layoutExtent,
62
+ layoutLabels,
54
63
  layoutModeByValue,
55
64
  loadBandage,
56
65
  loadGraph,
@@ -58,19 +67,16 @@ import {
58
67
  meanDepth,
59
68
  modeUsesLayoutEngine,
60
69
  nodeInk,
61
- nodeLabelBudget,
62
70
  nodeWidthPx,
63
- occupancy,
71
+ openGbz,
64
72
  padded,
65
73
  panSNContig,
66
74
  panSNHaplotype,
67
75
  panSNSample,
76
+ parseRegion,
68
77
  pathColorsLegible,
69
78
  pathLegend,
70
79
  pathRibbonOffsets,
71
- placeLabels,
72
- placeSizeLabels,
73
- polylineSlice,
74
80
  rangeText,
75
81
  refNameBinding,
76
82
  referenceOrder,
@@ -79,23 +85,23 @@ import {
79
85
  resolveColorScheme,
80
86
  resolveEncoding,
81
87
  resolveReferenceSample,
82
- rowLabelBox,
83
88
  schemeCss,
84
89
  screenToLayout,
85
- sizeLabelCandidates,
86
90
  svgCanvas,
87
91
  svgPath,
88
92
  translateCurves,
93
+ version,
89
94
  viewportOf,
90
95
  walkHighlight,
91
96
  walkKey,
92
97
  walkLift,
98
+ walkPosition,
93
99
  walkRows,
94
100
  walkRowsExtent,
95
101
  wellKnownSample,
96
102
  yToXOf,
97
103
  zoomAbout
98
- } from "./chunks/chunk-4AOHEU76.js";
104
+ } from "./chunks/chunk-B5ZHLU4N.js";
99
105
 
100
106
  // src/index.ts
101
107
  import { getDpr } from "@jbrowse/render-core/canvas2dUtils";
@@ -212,14 +218,14 @@ var SpatialIndex = class {
212
218
  }
213
219
  };
214
220
  var EdgeSpatialIndex = class {
215
- constructor(nodePositions, graph, drawPaths, axis, cellSize, deletions, version = 0) {
221
+ constructor(nodePositions, graph, drawPaths, axis, cellSize, deletions, version2 = 0) {
216
222
  this.cells = /* @__PURE__ */ new Map();
217
223
  this.edgeCurves = baseEdgeCurves(
218
224
  nodePositions,
219
225
  graph,
220
226
  axis,
221
227
  deletions,
222
- version
228
+ version2
223
229
  );
224
230
  const boxes = [];
225
231
  let extentX = 0;
@@ -332,19 +338,19 @@ function distanceToEdgeCurves(px, py, curves, yToX = 1) {
332
338
  return minDist;
333
339
  }
334
340
  var nodeCache = /* @__PURE__ */ new WeakMap();
335
- function getSpatialIndex(nodePositions, version) {
341
+ function getSpatialIndex(nodePositions, version2) {
336
342
  const cached = nodeCache.get(nodePositions);
337
- if (cached?.version === version) {
343
+ if (cached?.version === version2) {
338
344
  return cached.index;
339
345
  }
340
346
  const index = new SpatialIndex(nodePositions);
341
- nodeCache.set(nodePositions, { version, index });
347
+ nodeCache.set(nodePositions, { version: version2, index });
342
348
  return index;
343
349
  }
344
350
  var edgeCache = /* @__PURE__ */ new WeakMap();
345
- function getEdgeSpatialIndex(nodePositions, graph, drawPaths, axis, version, deletions) {
351
+ function getEdgeSpatialIndex(nodePositions, graph, drawPaths, axis, version2, deletions) {
346
352
  const cached = edgeCache.get(nodePositions);
347
- if (cached?.graph === graph && cached.drawPaths === drawPaths && cached.scaleX === axis.scaleX && cached.scaleY === axis.scaleY && cached.version === version && cached.deletions === deletions) {
353
+ if (cached?.graph === graph && cached.drawPaths === drawPaths && cached.scaleX === axis.scaleX && cached.scaleY === axis.scaleY && cached.version === version2 && cached.deletions === deletions) {
348
354
  return cached.index;
349
355
  }
350
356
  const index = new EdgeSpatialIndex(
@@ -354,23 +360,23 @@ function getEdgeSpatialIndex(nodePositions, graph, drawPaths, axis, version, del
354
360
  axis,
355
361
  void 0,
356
362
  deletions,
357
- version
363
+ version2
358
364
  );
359
365
  edgeCache.set(nodePositions, {
360
366
  graph,
361
367
  drawPaths,
362
368
  scaleX: axis.scaleX,
363
369
  scaleY: axis.scaleY,
364
- version,
370
+ version: version2,
365
371
  index,
366
372
  deletions
367
373
  });
368
374
  return index;
369
375
  }
370
376
  var MIN_NODE_REACH_PX = 5;
371
- function findHoveredNode(nodePositions, graphX, graphY, axis, version = 0, ink) {
377
+ function findHoveredNode(nodePositions, graphX, graphY, axis, version2 = 0, ink) {
372
378
  const yToX = yToXOf(axis);
373
- const index = getSpatialIndex(nodePositions, version);
379
+ const index = getSpatialIndex(nodePositions, version2);
374
380
  const reach = (px) => Math.max(MIN_NODE_REACH_PX, px) / axis.scaleX;
375
381
  const queryReach = reach(ink?.maxHalfWidthPx ?? 0);
376
382
  const candidates = index.query(graphX, graphY, queryReach, queryReach / yToX);
@@ -393,7 +399,7 @@ function findHoveredNode(nodePositions, graphX, graphY, axis, version = 0, ink)
393
399
  }
394
400
  return hovered;
395
401
  }
396
- function findHoveredEdge(nodePositions, graph, graphX, graphY, axis, drawPaths, version = 0, deletions, hiddenEdges) {
402
+ function findHoveredEdge(nodePositions, graph, graphX, graphY, axis, drawPaths, version2 = 0, deletions, hiddenEdges) {
397
403
  const yToX = yToXOf(axis);
398
404
  const edgeThreshold = 10 / axis.scaleX;
399
405
  const edgeIndex = getEdgeSpatialIndex(
@@ -401,7 +407,7 @@ function findHoveredEdge(nodePositions, graph, graphX, graphY, axis, drawPaths,
401
407
  graph,
402
408
  drawPaths,
403
409
  axis,
404
- version,
410
+ version2,
405
411
  deletions
406
412
  );
407
413
  const candidates = edgeIndex.query(
@@ -470,6 +476,7 @@ var REFERENCE_STRIP_PX = 12;
470
476
  var REFERENCE_STRIP_ZONE_PX = REFERENCE_STRIP_PX + 8;
471
477
  var OVERHANG_PX = 8;
472
478
  var OVERHANG_CAP_PX = OVERHANG_PX + 3;
479
+ var MIN_STAGGER_PX = 12;
473
480
  var PALE = abgrToCssRgba(fadeAbgr(LIFT_BACKDROP_COLOR, FADED_ALPHA));
474
481
  function referenceStripBlocks(graph, {
475
482
  colorScheme,
@@ -553,7 +560,8 @@ function stripPixels(blocks, frame, width, dpr) {
553
560
  claimed = px1;
554
561
  }
555
562
  }
556
- return out;
563
+ const covered = out.length > 0 ? out.at(-1).x1 - out[0].x0 : 0;
564
+ return covered >= out.length * MIN_STAGGER_PX ? out : out.map(({ tier: _, ...b }) => b);
557
565
  }
558
566
  function drawOverhang(ctx, edgeX, inward, ink) {
559
567
  const capX = inward > 0 ? edgeX : edgeX - OVERHANG_CAP_PX;
@@ -580,9 +588,11 @@ function drawReferenceStrip(ctx, blocks, frame, {
580
588
  const ink = darkMode ? "#ffffff" : "#18181c";
581
589
  ctx.clearRect(0, 0, width, REFERENCE_STRIP_ZONE_PX);
582
590
  for (const b of stripPixels(blocks, frame, width, dpr)) {
583
- const rows = b.colors.length > 1 ? b.colors.length : 2;
591
+ const { tier } = b;
592
+ const staggered = tier !== void 0 && b.colors.length === 1;
593
+ const rows = staggered ? 2 : b.colors.length;
584
594
  b.colors.forEach((color, i) => {
585
- const row = b.colors.length > 1 ? i : b.tier;
595
+ const row = staggered ? tier : i;
586
596
  const y0 = rowEdge(row, rows, dpr);
587
597
  ctx.fillStyle = color;
588
598
  ctx.fillRect(b.x0, y0, b.x1 - b.x0, rowEdge(row + 1, rows, dpr) - y0);
@@ -1052,7 +1062,7 @@ function niceStep(raw) {
1052
1062
  const m = raw / pow;
1053
1063
  return (m <= 1 ? 1 : m <= 2 ? 2 : m <= 5 ? 5 : 10) * pow;
1054
1064
  }
1055
- function placeLabels2(candidates) {
1065
+ function placeLabels(candidates) {
1056
1066
  const placed = [];
1057
1067
  for (const a of candidates) {
1058
1068
  if (placed.every(
@@ -1089,7 +1099,7 @@ function scaleMarks(boxes, x, width, measure) {
1089
1099
  return {
1090
1100
  spans: [{ x0: from, x1: to }],
1091
1101
  ticks,
1092
- labels: placeLabels2(positions)
1102
+ labels: placeLabels(positions)
1093
1103
  };
1094
1104
  }
1095
1105
  function boxMarks(boxes, x, width, measure) {
@@ -1130,7 +1140,7 @@ function boxMarks(boxes, x, width, measure) {
1130
1140
  return {
1131
1141
  spans,
1132
1142
  ticks,
1133
- labels: placeLabels2([...ends, ...lengths, ...boundaries])
1143
+ labels: placeLabels([...ends, ...lengths, ...boundaries])
1134
1144
  };
1135
1145
  }
1136
1146
  function rulerMarks(boxes, x, width, measure, referenceAxis = false) {
@@ -1698,220 +1708,6 @@ function bubbleSubgraph(graph, segmentIds) {
1698
1708
  referencePath: graph.referencePath
1699
1709
  };
1700
1710
  }
1701
-
1702
- // src/labelLayout.ts
1703
- var HALO_FACTOR = 3.4;
1704
- var LEGEND_INSET_PX = 6;
1705
- var ROUTE_STACK = 8;
1706
- var GENE_PIN_DROP_PX = 18;
1707
- var GENE_STACK = 2;
1708
- var TOPMOST_BASELINE = LABEL_PX + LABEL_PAD;
1709
- function geneCoverageNote(pin) {
1710
- const length = pin.gene.end - pin.gene.start;
1711
- if (pin.covered >= 0.98) {
1712
- return void 0;
1713
- }
1714
- const [per, unit] = length >= 1e6 ? [1e6, "Mb"] : length >= 1e3 ? [1e3, "kb"] : [1, "bp"];
1715
- const amount = (bp) => +(bp / per).toFixed(per === 1 ? 0 : 1);
1716
- return `${amount(pin.covered * length)} of ${amount(length)} ${unit}`;
1717
- }
1718
- function byExtent(items, extent) {
1719
- return [...items].sort((a, b) => extent(b) - extent(a));
1720
- }
1721
- function layoutLabels(m) {
1722
- const { paneWidth: width, canvasHeight: height, translateX, translateY } = m;
1723
- const { scaleX, scaleY } = m.axisScale;
1724
- const frame = { width, height };
1725
- const screen = (p) => ({
1726
- x: p.x * scaleX + translateX,
1727
- y: p.y * scaleY + translateY
1728
- });
1729
- const reserved = m.drawnRowLabels.map(
1730
- ({ label, y }) => rowLabelBox(label, y * scaleY + translateY)
1731
- );
1732
- const stripPx = m.referenceStripZonePx ?? 0;
1733
- if (stripPx > 0) {
1734
- reserved.push({ x0: 0, x1: width, y0: 0, y1: stripPx });
1735
- }
1736
- if (m.legendSize.width > 0) {
1737
- reserved.push({
1738
- x0: width - LEGEND_INSET_PX - m.legendSize.width,
1739
- x1: width,
1740
- y0: 0,
1741
- y1: stripPx + LEGEND_INSET_PX + m.legendSize.height
1742
- });
1743
- }
1744
- if (m.poppedFrom) {
1745
- reserved.push({
1746
- x0: 0,
1747
- x1: 60 + m.poppedFrom.label.length * LABEL_CHAR_PX * 1.2,
1748
- y0: 0,
1749
- y1: 44
1750
- });
1751
- }
1752
- const take = occupancy(frame, reserved);
1753
- const onRows = m.drawnRowLabels.length > 0;
1754
- const rowsTop = onRows ? Math.min(...m.drawnRowLabels.map((r) => r.y)) : Infinity;
1755
- const halo = m.contigThickness * HALO_FACTOR;
1756
- const byBubble = byExtent(m.bubbleHalos, (h) => h.members);
1757
- const bubbles = placeLabels(
1758
- byBubble.map((h) => {
1759
- const { x, y } = screen({
1760
- x: h.labelAt.x,
1761
- y: Math.min(h.labelAt.y, rowsTop)
1762
- });
1763
- const baseline = y - halo / 2 - 6;
1764
- return {
1765
- item: h,
1766
- x,
1767
- y: onRows ? Math.max(baseline, TOPMOST_BASELINE) : baseline,
1768
- text: h.label
1769
- };
1770
- }),
1771
- frame,
1772
- take
1773
- );
1774
- const genes = placeLabels(
1775
- byExtent(m.genePins, (pin) => pin.gene.end - pin.gene.start).map((pin) => {
1776
- const { x, y } = screen(pin.at);
1777
- const note = geneCoverageNote(pin);
1778
- return {
1779
- item: pin,
1780
- x,
1781
- y: y + m.contigThickness + GENE_PIN_DROP_PX,
1782
- text: note ? `${pin.gene.name} \xB7 ${note}` : pin.gene.name,
1783
- fallback: note ? `${pin.gene.name} \u2026` : void 0,
1784
- stack: GENE_STACK
1785
- };
1786
- }),
1787
- frame,
1788
- take
1789
- );
1790
- const sizeCandidates = m.nodePositions && m.labelsNodeSizes ? sizeLabelCandidates({
1791
- nodePositions: m.nodePositions,
1792
- nodeLengths: m.nodeLengths,
1793
- deletions: m.showDeletionEdges ? m.deletions : [],
1794
- alleleDeletions: m.alleleDeletions,
1795
- axis: m.axisScale,
1796
- translateX,
1797
- translateY,
1798
- width,
1799
- height,
1800
- version: m.positionsVersion
1801
- }) : { deletions: [], nodes: [] };
1802
- const deletionLabels = placeSizeLabels(sizeCandidates.deletions, take);
1803
- const routes2 = placeLabels(
1804
- byBubble.flatMap(
1805
- (h) => h.routes.map((route) => {
1806
- const { x, y } = screen(route.at);
1807
- return {
1808
- item: { halo: h, route },
1809
- x,
1810
- y: y + 4,
1811
- text: route.text,
1812
- stack: ROUTE_STACK
1813
- };
1814
- })
1815
- ),
1816
- frame,
1817
- take
1818
- );
1819
- const nodeLabels = placeSizeLabels(
1820
- sizeCandidates.nodes,
1821
- take,
1822
- nodeLabelBudget(width, height)
1823
- );
1824
- return {
1825
- bubbles,
1826
- genes,
1827
- routes: routes2,
1828
- sizes: [...deletionLabels, ...nodeLabels]
1829
- };
1830
- }
1831
-
1832
- // src/genes/genePins.ts
1833
- function genePins(graph, genes, positions) {
1834
- const byRefName = /* @__PURE__ */ new Map();
1835
- for (const node of graph.nodes) {
1836
- if (isBackbone(node)) {
1837
- const { refName } = node.stable;
1838
- if (positions[node.id]?.length) {
1839
- const entry = byRefName.get(refName) ?? byRefName.set(refName, { nodes: [], reach: 0 }).get(refName);
1840
- entry.nodes.push(node);
1841
- entry.reach = Math.max(entry.reach, node.length);
1842
- }
1843
- }
1844
- }
1845
- for (const { nodes } of byRefName.values()) {
1846
- nodes.sort((a, b) => a.stable.start - b.stable.start);
1847
- }
1848
- const pins = [];
1849
- for (const gene of genes) {
1850
- const backbone = byRefName.get(gene.refName);
1851
- if (!backbone) {
1852
- continue;
1853
- }
1854
- const parts = [];
1855
- const exonsByNode = [];
1856
- let at;
1857
- let atDistance = Infinity;
1858
- let covered = 0;
1859
- const mid = (gene.start + gene.end) / 2;
1860
- const { nodes, reach } = backbone;
1861
- for (let i = firstNodeAtOrAfter(nodes, gene.start - reach); i < nodes.length && nodes[i].stable.start < gene.end; i++) {
1862
- const node = nodes[i];
1863
- const nodeStart = node.stable.start;
1864
- const nodeEnd = nodeStart + node.length;
1865
- if (nodeEnd <= gene.start) {
1866
- continue;
1867
- }
1868
- const line = positions[node.id];
1869
- covered += Math.min(nodeEnd, gene.end) - Math.max(nodeStart, gene.start);
1870
- const nodeParts = [];
1871
- for (const exon of gene.exons) {
1872
- const a = Math.max(exon.start, nodeStart);
1873
- const b = Math.min(exon.end, nodeEnd);
1874
- if (b <= a) {
1875
- continue;
1876
- }
1877
- const stretch = polylineSlice(
1878
- line,
1879
- (a - nodeStart) / node.length,
1880
- (b - nodeStart) / node.length
1881
- );
1882
- if (stretch.length === 1) {
1883
- stretch.push({ ...stretch[0] });
1884
- }
1885
- nodeParts.push(svgPath(stretch));
1886
- }
1887
- if (nodeParts.length > 0) {
1888
- parts.push(...nodeParts);
1889
- exonsByNode.push({ nodeId: node.id, d: nodeParts.join("") });
1890
- }
1891
- const pinBp = Math.min(Math.max(mid, nodeStart), nodeEnd);
1892
- const distance = Math.abs(pinBp - mid);
1893
- if (distance < atDistance) {
1894
- atDistance = distance;
1895
- const [p] = polylineSlice(
1896
- line,
1897
- (pinBp - nodeStart) / node.length,
1898
- (pinBp - nodeStart) / node.length
1899
- );
1900
- at = p;
1901
- }
1902
- }
1903
- if (at) {
1904
- pins.push({
1905
- gene,
1906
- exons: parts.join(""),
1907
- exonsByNode,
1908
- at,
1909
- covered: covered / (gene.end - gene.start)
1910
- });
1911
- }
1912
- }
1913
- return pins;
1914
- }
1915
1711
  export {
1916
1712
  BUBBLE_KIND_COLORS,
1917
1713
  BUBBLE_KIND_NAMES,
@@ -1924,6 +1720,7 @@ export {
1924
1720
  FIT_PADDING,
1925
1721
  FORCE_LAYOUT_LABEL,
1926
1722
  HALO_FACTOR,
1723
+ HPRC_GBZ,
1927
1724
  LABEL_CHAR_PX,
1928
1725
  LABEL_PAD,
1929
1726
  LABEL_PX,
@@ -1949,6 +1746,7 @@ export {
1949
1746
  computeReferenceRamp,
1950
1747
  connectorAt,
1951
1748
  contains,
1749
+ cutGbzRegion,
1952
1750
  cutWindowGFA,
1953
1751
  deletionEdges,
1954
1752
  drawReferenceStrip,
@@ -1973,6 +1771,9 @@ export {
1973
1771
  formatBp,
1974
1772
  geneCoverageNote,
1975
1773
  genePins,
1774
+ genesFromBed,
1775
+ genesFromGff3Lines,
1776
+ genesFromText,
1976
1777
  getDpr,
1977
1778
  graphBackbone,
1978
1779
  haplotypeWanted,
@@ -1986,10 +1787,12 @@ export {
1986
1787
  modeUsesLayoutEngine,
1987
1788
  nodeInk,
1988
1789
  nodeWidthPx,
1790
+ openGbz,
1989
1791
  padded,
1990
1792
  panSNContig,
1991
1793
  panSNHaplotype,
1992
1794
  panSNSample,
1795
+ parseRegion,
1993
1796
  pathColorsLegible,
1994
1797
  pathLegend,
1995
1798
  rangeText,
@@ -2014,10 +1817,12 @@ export {
2014
1817
  tubeMapNodeAt,
2015
1818
  tubeMapPicture,
2016
1819
  tubeX,
1820
+ version,
2017
1821
  viewportOf,
2018
1822
  walkHighlight,
2019
1823
  walkKey,
2020
1824
  walkLift,
1825
+ walkPosition,
2021
1826
  walkRows,
2022
1827
  walkRowsExtent,
2023
1828
  warpX,
@@ -1,20 +1,20 @@
1
1
  import type { BubbleSpread } from '../bubbleSpreads.js';
2
2
  import type { FigureOptions } from '../figure.js';
3
+ import type { GbzSource } from '../gbzCut.js';
3
4
  import type { LayoutModeValue } from '../layoutModes.js';
4
5
  import type { WalkLayer } from '../walkEncoding.js';
5
- export interface FigureSpec extends Omit<FigureOptions, 'walks' | 'region'> {
6
+ export interface FigureSpec extends Omit<FigureOptions, 'walks' | 'region' | 'genes' | 'spec'> {
6
7
  gfa?: string;
7
- gbz?: {
8
- db: string;
9
- index?: string;
10
- region: string;
11
- haplotypes?: string[];
12
- referenceSample?: string;
13
- };
8
+ gbz?: GbzSource;
14
9
  region?: string;
15
10
  referencePath?: string;
16
11
  layout?: LayoutModeValue;
17
12
  quality?: number;
18
13
  bubbleSpread?: BubbleSpread;
19
14
  walks?: (string | WalkLayer)[];
15
+ genes?: {
16
+ file: string;
17
+ index?: string;
18
+ format?: 'gff3' | 'bed';
19
+ };
20
20
  }