@jbrowse/bandage-core 6.0.1 → 6.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -7
- package/dist/alleleProjection/projectAlleles.js +10 -0
- package/dist/anchoredNodes.js +24 -0
- package/dist/bubbleSpreads.js +11 -0
- package/dist/bubbles/bubbleHalos.js +11 -0
- package/dist/bubbles/bubbleLine.js +14 -0
- package/dist/bubbles/bubblesFromGraph.js +10 -0
- package/dist/bubbles/classifyBubble.js +15 -0
- package/dist/bubbles/popBubble.js +9 -0
- package/dist/chunks/chunk-2CVHXFK7.js +530 -0
- package/dist/chunks/chunk-3S3YVOJ7.js +69 -0
- package/dist/chunks/chunk-47DYCZOK.js +115 -0
- package/dist/chunks/chunk-4EMPBO7S.js +117 -0
- package/dist/chunks/chunk-4S47XRYA.js +149 -0
- package/dist/chunks/chunk-5XQDMQXU.js +431 -0
- package/dist/chunks/chunk-6JEWNXCQ.js +90 -0
- package/dist/chunks/chunk-6SBUFFTB.js +97 -0
- package/dist/chunks/chunk-6SXMCK6I.js +476 -0
- package/dist/chunks/chunk-72N5DJHZ.js +155 -0
- package/dist/chunks/chunk-73RSLGZT.js +73 -0
- package/dist/chunks/chunk-7XHHVYG7.js +13 -0
- package/dist/chunks/chunk-AB35TJST.js +67 -0
- package/dist/chunks/chunk-BPLIXMH7.js +52 -0
- package/dist/chunks/chunk-BUBZ32IC.js +101 -0
- package/dist/chunks/chunk-BXEPA4KS.js +179 -0
- package/dist/chunks/chunk-C44YRP5F.js +85 -0
- package/dist/chunks/chunk-CDLPYR5O.js +135 -0
- package/dist/chunks/chunk-CJ2EE4AF.js +40 -0
- package/dist/chunks/chunk-CPMHA4W5.js +6 -0
- package/dist/chunks/chunk-CR7EGAFE.js +241 -0
- package/dist/chunks/chunk-CS3E4ETM.js +202 -0
- package/dist/chunks/chunk-CTSBC2O6.js +90 -0
- package/dist/chunks/chunk-D3M7SBKL.js +210 -0
- package/dist/chunks/chunk-DKOB2OPC.js +177 -0
- package/dist/chunks/chunk-EJ5OZTCK.js +52 -0
- package/dist/chunks/chunk-EK4HGYO7.js +174 -0
- package/dist/chunks/chunk-EPZORFTI.js +23 -0
- package/dist/chunks/chunk-ES2N6V56.js +83 -0
- package/dist/chunks/chunk-EZPBJPK6.js +54 -0
- package/dist/chunks/chunk-FYQWPEEY.js +65 -0
- package/dist/chunks/chunk-GGQRADFS.js +170 -0
- package/dist/chunks/chunk-GOY6REMT.js +43 -0
- package/dist/chunks/chunk-GQ2AOHU4.js +112 -0
- package/dist/chunks/chunk-HNQPEMYE.js +297 -0
- package/dist/chunks/chunk-IGVV37VI.js +60 -0
- package/dist/chunks/chunk-IO2PCPZI.js +218 -0
- package/dist/chunks/chunk-ISEI2FH3.js +108 -0
- package/dist/chunks/chunk-JKJWSY4V.js +32 -0
- package/dist/chunks/chunk-KQFOCDFY.js +73 -0
- package/dist/chunks/chunk-L3O7AWLI.js +52 -0
- package/dist/chunks/chunk-LLJUG2H7.js +6 -0
- package/dist/chunks/chunk-LOEA5IHZ.js +71 -0
- package/dist/chunks/chunk-LTUWJKHD.js +112 -0
- package/dist/chunks/chunk-MFKORG3F.js +44 -0
- package/dist/chunks/chunk-MI35EUIE.js +76 -0
- package/dist/chunks/chunk-MNUSQCLG.js +29 -0
- package/dist/chunks/chunk-MQJUG2D2.js +53 -0
- package/dist/chunks/chunk-MREQ7GIA.js +181 -0
- package/dist/chunks/chunk-N62KBLFC.js +326 -0
- package/dist/chunks/chunk-NNODIW4U.js +26 -0
- package/dist/chunks/chunk-PBENCR5L.js +60 -0
- package/dist/chunks/chunk-PCZ4PTYC.js +726 -0
- package/dist/chunks/chunk-PUG7LXDF.js +70 -0
- package/dist/chunks/chunk-QYBZZI5H.js +0 -0
- package/dist/chunks/chunk-R6ZUK4Y3.js +232 -0
- package/dist/chunks/chunk-RAKOB6IV.js +169 -0
- package/dist/chunks/chunk-ROF2R3RF.js +205 -0
- package/dist/chunks/chunk-RVS2QCOX.js +135 -0
- package/dist/chunks/chunk-ST2IBUAN.js +119 -0
- package/dist/chunks/chunk-SZNZ2T6D.js +198 -0
- package/dist/chunks/chunk-T43W7NKK.js +122 -0
- package/dist/chunks/chunk-TAMCZHLW.js +190 -0
- package/dist/chunks/chunk-TDQLINVI.js +16 -0
- package/dist/chunks/chunk-TEDQJPMI.js +131 -0
- package/dist/chunks/chunk-TZFWUT2C.js +66 -0
- package/dist/chunks/chunk-UVBGRJQX.js +50 -0
- package/dist/chunks/chunk-VHW7FDHU.js +42 -0
- package/dist/chunks/chunk-VZQ4FKLR.js +38 -0
- package/dist/chunks/chunk-WFX3MAKA.js +102 -0
- package/dist/chunks/chunk-WOO7QYKB.js +121 -0
- package/dist/chunks/chunk-WSIFAZU7.js +132 -0
- package/dist/chunks/chunk-X5L37W4J.js +82 -0
- package/dist/chunks/chunk-Y3IOTZJA.js +61 -0
- package/dist/chunks/chunk-YMSSF2RK.js +257 -0
- package/dist/cli/figure.js +84 -17
- package/dist/colorSchemes.js +10 -0
- package/dist/deletionEdges.js +13 -0
- package/dist/el.js +8 -0
- package/dist/facetGrid.js +16 -0
- package/dist/figure.js +51 -0
- package/dist/gaf/parseGaf.js +12 -0
- package/dist/gbzCut.js +19 -0
- package/dist/gbzJoin.js +6 -0
- package/dist/gbzWindow.js +28 -0
- package/dist/genes/exonOutline.js +11 -0
- package/dist/genes/geneFiles.js +14 -0
- package/dist/genes/genePins.js +10 -0
- package/dist/gfa/gfaConverter.js +11 -0
- package/dist/gfa-core/gfaParser.js +8 -0
- package/dist/gfa-core/index.js +9 -0
- package/dist/graphLabels.js +24 -0
- package/dist/index.js +262 -1407
- package/dist/labelLayout.js +24 -0
- package/dist/layout/anchoredLayout.js +11 -0
- package/dist/layout/deletionRoutes.js +10 -0
- package/dist/layout/drawnScale.js +22 -0
- package/dist/layout/mergeRuns.js +15 -0
- package/dist/layout/orderedLayout.js +12 -0
- package/dist/layout/orientToReference.js +7 -0
- package/dist/layout/placeOffReference.js +9 -0
- package/dist/layout/referenceSeeds.js +11 -0
- package/dist/layout/rowSpacing.js +6 -0
- package/dist/layout/sampleRowLayout.js +11 -0
- package/dist/layout/trimToWindow.js +99 -0
- package/dist/layout/tubeMapLayout.js +28 -0
- package/dist/layout/walkRowDraw.js +65 -0
- package/dist/layout/walkRowLayout.js +13 -0
- package/dist/layout/walkRows.js +11 -0
- package/dist/layout/walkStrip.js +40 -0
- package/dist/layoutModes.js +35 -0
- package/dist/loadBandage.js +6 -0
- package/dist/nodeWidths.js +18 -0
- package/dist/overlayLabels.js +18 -0
- package/dist/pansn.js +18 -0
- package/dist/pathAnchoring.js +18 -0
- package/dist/pathColors.js +31 -0
- package/dist/pipeline.js +40 -0
- package/dist/reference.js +24 -0
- package/dist/referenceRampCss.js +29 -0
- package/dist/referenceSpan.js +11 -0
- package/dist/referenceStrip.js +35 -0
- package/dist/renderer/Canvas2DRenderer.js +9 -0
- package/dist/renderer/GeometryBuilder.js +42 -0
- package/dist/renderer/colorBits.js +8 -0
- package/dist/renderer/recordingCanvas.js +44 -0
- package/dist/renderer/svgCanvas.js +6 -0
- package/dist/renderer/types.js +0 -0
- package/dist/tubeMap/axis.js +25 -0
- package/dist/tubeMap/coarsen.js +361 -0
- package/dist/tubeMap/connectors.js +13 -0
- package/dist/tubeMap/deviations.js +85 -0
- package/dist/tubeMap/draw.js +15 -0
- package/dist/tubeMap/frame.js +9 -0
- package/dist/tubeMap/genes.js +20 -0
- package/dist/tubeMap/mismatches.js +6 -0
- package/dist/tubeMap/nodeColors.js +21 -0
- package/dist/tubeMap/reads.js +17 -0
- package/dist/tubeMap/warp.js +8 -0
- package/dist/types/version.d.ts +1 -1
- package/dist/types.js +0 -0
- package/dist/util/SpatialIndex.js +13 -0
- package/dist/util/edgeCurves.js +10 -0
- package/dist/util/geometry.js +39 -0
- package/dist/util/hitDetection.js +20 -0
- package/dist/util/wheelZoom.js +6 -0
- package/dist/version.js +6 -0
- package/dist/viewport.js +33 -0
- package/dist/walkEncoding.js +39 -0
- package/dist/walkHighlight.js +26 -0
- package/dist/walkKey.js +17 -0
- package/package.json +38 -5
- package/dist/chunks/chunk-EXSFRX2F.js +0 -7400
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RAMP_GRADIENT_CSS,
|
|
3
|
+
RAMP_GRADIENT_STOPS,
|
|
4
|
+
rampHueCss,
|
|
5
|
+
rampStops
|
|
6
|
+
} from "./chunk-CJ2EE4AF.js";
|
|
7
|
+
import {
|
|
8
|
+
REFERENCE_RAMP_ALT_CSS
|
|
9
|
+
} from "./chunk-PCZ4PTYC.js";
|
|
10
|
+
import {
|
|
11
|
+
LABEL_CHAR_PX
|
|
12
|
+
} from "./chunk-AB35TJST.js";
|
|
13
|
+
import {
|
|
14
|
+
ROW_HEIGHT_PX
|
|
15
|
+
} from "./chunk-CPMHA4W5.js";
|
|
16
|
+
import {
|
|
17
|
+
el
|
|
18
|
+
} from "./chunk-NNODIW4U.js";
|
|
19
|
+
|
|
20
|
+
// src/layout/walkRowDraw.ts
|
|
21
|
+
var ON_REFERENCE = "#2f8fd6";
|
|
22
|
+
var OFF_REFERENCE = "#8e3fbf";
|
|
23
|
+
var OUTSIDE_CUT = "#bdbdbd";
|
|
24
|
+
var GENE_INK = "#1c1c22";
|
|
25
|
+
var BAR_PX = 12;
|
|
26
|
+
var GAP_PX = 4;
|
|
27
|
+
var MIN_TILE_PX = 3;
|
|
28
|
+
function kb(bp) {
|
|
29
|
+
return `${(bp / 1e3).toFixed(bp < 1e4 ? 1 : 0)} kb`;
|
|
30
|
+
}
|
|
31
|
+
function units(bp, unit) {
|
|
32
|
+
return unit ? ` \u2248 ${Math.round(bp / unit)} units` : "";
|
|
33
|
+
}
|
|
34
|
+
function walkRowReadout(row, reference, unit, call) {
|
|
35
|
+
const { bp, gapBp, complete } = row;
|
|
36
|
+
if (!reference) {
|
|
37
|
+
return `${kb(bp)}${units(bp, unit)}`;
|
|
38
|
+
}
|
|
39
|
+
const delta = bp - reference.bp;
|
|
40
|
+
const against = delta === 0 ? "" : ` (${delta > 0 ? "+" : "\u2212"}${kb(Math.abs(delta))})`;
|
|
41
|
+
const outside = gapBp > 0 ? ` \xB7 ${kb(gapBp)} outside the cut` : "";
|
|
42
|
+
const called = call ? ` \xB7 called ${kb(call.bp)}${call.spanningReads === 0 ? " \xB7 no spanning read" : ""}` : "";
|
|
43
|
+
return `${kb(bp)}${units(bp, unit)}${against}${outside}${complete ? "" : " \xB7 partial walk"}${called}`;
|
|
44
|
+
}
|
|
45
|
+
function readoutPlacement(text, endX, width) {
|
|
46
|
+
const x = endX + 6;
|
|
47
|
+
const fits = x + text.length * LABEL_CHAR_PX < width;
|
|
48
|
+
return fits ? { x, anchor: "start", halo: false } : { x: endX - 6, anchor: "end", halo: true };
|
|
49
|
+
}
|
|
50
|
+
function unitTicks(bp, unit, pxPerBp) {
|
|
51
|
+
if (unit * pxPerBp < MIN_TILE_PX) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const ticks = [];
|
|
55
|
+
for (let k = unit; k < bp; k += unit) {
|
|
56
|
+
ticks.push(k);
|
|
57
|
+
}
|
|
58
|
+
return ticks;
|
|
59
|
+
}
|
|
60
|
+
function runPaint(run, ramp) {
|
|
61
|
+
if (run.gap) {
|
|
62
|
+
return { fill: OUTSIDE_CUT };
|
|
63
|
+
}
|
|
64
|
+
if (!ramp) {
|
|
65
|
+
return { fill: run.onReference ? ON_REFERENCE : OFF_REFERENCE };
|
|
66
|
+
}
|
|
67
|
+
if (run.referenceStart === void 0) {
|
|
68
|
+
return { fill: REFERENCE_RAMP_ALT_CSS };
|
|
69
|
+
}
|
|
70
|
+
const stops = rampStops({ ...run, start: run.referenceStart }, ramp);
|
|
71
|
+
return stops.length === 1 ? { fill: rampHueCss(stops[0]) } : { stops: stops.map(rampHueCss) };
|
|
72
|
+
}
|
|
73
|
+
function alongRow(axis, start, end) {
|
|
74
|
+
return axis.reversed ? { start: axis.start - end, end: axis.start - start } : { start: start - axis.start, end: end - axis.start };
|
|
75
|
+
}
|
|
76
|
+
function rowSpan(axis, bp) {
|
|
77
|
+
return axis.reversed ? { start: axis.start - bp, end: axis.start } : { start: axis.start, end: axis.start + bp };
|
|
78
|
+
}
|
|
79
|
+
function genesOnRow(row, genes) {
|
|
80
|
+
const contig = row.axis?.contig;
|
|
81
|
+
return genes.filter(
|
|
82
|
+
(g) => g.refName === contig || g.refName.endsWith(`#${contig}`)
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
function placeRowGenes(rows, byRow) {
|
|
86
|
+
const placed = /* @__PURE__ */ new Map();
|
|
87
|
+
for (const row of rows) {
|
|
88
|
+
const genes = byRow.get(row.name);
|
|
89
|
+
if (!row.axis || !genes) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const axis = row.axis;
|
|
93
|
+
const clip = ({ start, end }) => ({
|
|
94
|
+
start: Math.max(0, start),
|
|
95
|
+
end: Math.min(row.bp, end)
|
|
96
|
+
});
|
|
97
|
+
placed.set(
|
|
98
|
+
row.name,
|
|
99
|
+
genes.map((g) => ({
|
|
100
|
+
name: g.name,
|
|
101
|
+
...clip(alongRow(axis, g.start, g.end)),
|
|
102
|
+
exons: g.exons.map((e) => clip(alongRow(axis, e.start, e.end))).filter((e) => e.end > e.start)
|
|
103
|
+
})).filter((g) => g.end > g.start).sort((a, b) => a.start - b.start)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return placed;
|
|
107
|
+
}
|
|
108
|
+
var GENE_FONT_PX = 9;
|
|
109
|
+
var GENE_CHAR_PX = 5.2;
|
|
110
|
+
var MIN_LETTERED_BAR_PX = 8;
|
|
111
|
+
var genePad = (rowPx, barPx) => Math.min(2, (rowPx - barPx) / 2 - 1);
|
|
112
|
+
function boxesGenes(rowPx, barPx) {
|
|
113
|
+
return genePad(rowPx, barPx) >= 1;
|
|
114
|
+
}
|
|
115
|
+
function rowGeneBoxes(genes, X, y, { rowPx = ROW_HEIGHT_PX, barPx = BAR_PX } = {}) {
|
|
116
|
+
if (!boxesGenes(rowPx, barPx)) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
const pad = genePad(rowPx, barPx);
|
|
120
|
+
const boxes = genes.map((g) => {
|
|
121
|
+
const x = X(g.start);
|
|
122
|
+
return {
|
|
123
|
+
g,
|
|
124
|
+
x,
|
|
125
|
+
w: Math.max(2, X(g.end) - x)
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
const taken = [];
|
|
129
|
+
const named = new Set(
|
|
130
|
+
(barPx >= MIN_LETTERED_BAR_PX ? [...boxes] : []).sort((a, b) => a.w - b.w).filter(({ g, x, w }) => {
|
|
131
|
+
const half = g.name.length * GENE_CHAR_PX / 2;
|
|
132
|
+
const at = [x + w / 2 - half, x + w / 2 + half];
|
|
133
|
+
if (2 * half > w + 6 || taken.some(([a, b]) => at[0] < b + 2 && at[1] > a - 2)) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
taken.push(at);
|
|
137
|
+
return true;
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
return boxes.map((box) => ({
|
|
141
|
+
name: box.g.name,
|
|
142
|
+
x: box.x,
|
|
143
|
+
y: y - barPx / 2 - pad,
|
|
144
|
+
w: box.w,
|
|
145
|
+
h: barPx + 2 * pad,
|
|
146
|
+
exons: box.g.exons.map((e) => ({
|
|
147
|
+
x: X(e.start),
|
|
148
|
+
w: Math.max(1, X(e.end) - X(e.start))
|
|
149
|
+
})),
|
|
150
|
+
label: named.has(box) ? { x: box.x + box.w / 2, y: y + 3, size: GENE_FONT_PX } : void 0
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
var rowsText = (n) => `${n} row${n === 1 ? "" : "s"}`;
|
|
154
|
+
function walkRowsKey(bars, o = {}) {
|
|
155
|
+
const entries = [
|
|
156
|
+
{
|
|
157
|
+
swatch: o.ramp ? { kind: "bar", fill: o.rampCss ?? RAMP_GRADIENT_CSS, ramp: true } : { kind: "bar", fill: ON_REFERENCE },
|
|
158
|
+
label: `on ${bars.reference.label}'s path`
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
swatch: {
|
|
162
|
+
kind: "bar",
|
|
163
|
+
fill: o.ramp ? REFERENCE_RAMP_ALT_CSS : OFF_REFERENCE
|
|
164
|
+
},
|
|
165
|
+
label: `off ${bars.reference.label}'s path`
|
|
166
|
+
}
|
|
167
|
+
];
|
|
168
|
+
if ([bars.reference, ...bars.rows].some((row) => row.gapBp > 0)) {
|
|
169
|
+
entries.push({
|
|
170
|
+
swatch: { kind: "gap", fill: OUTSIDE_CUT },
|
|
171
|
+
label: "walked outside the cut"
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (o.genes?.crowded) {
|
|
175
|
+
entries.push({
|
|
176
|
+
swatch: { kind: "gene" },
|
|
177
|
+
label: "genes left out: too many rows to box them in",
|
|
178
|
+
note: true
|
|
179
|
+
});
|
|
180
|
+
} else if (o.genes) {
|
|
181
|
+
entries.push({
|
|
182
|
+
swatch: { kind: "gene" },
|
|
183
|
+
label: "genes, each row's own annotation"
|
|
184
|
+
});
|
|
185
|
+
if (o.genes.untracked) {
|
|
186
|
+
entries.push({
|
|
187
|
+
swatch: { kind: "gene" },
|
|
188
|
+
label: `no gene track for ${rowsText(o.genes.untracked)}`,
|
|
189
|
+
note: true
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
if (o.genes.unplaced) {
|
|
193
|
+
entries.push({
|
|
194
|
+
swatch: { kind: "gene" },
|
|
195
|
+
label: `no contig coordinates for ${rowsText(o.genes.unplaced)}`,
|
|
196
|
+
note: true
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
if (o.genes.unread) {
|
|
200
|
+
entries.push({
|
|
201
|
+
swatch: { kind: "gene" },
|
|
202
|
+
label: `genes not read for the last ${rowsText(o.genes.unread)}`,
|
|
203
|
+
note: true
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return entries;
|
|
208
|
+
}
|
|
209
|
+
var CALL_TICK = "#111";
|
|
210
|
+
var UNBACKED_TICK = "#9e9e9e";
|
|
211
|
+
var DISAGREES = "#c62828";
|
|
212
|
+
function walkRowsKeyTree(entries, x, y, idPrefix = "walk-key") {
|
|
213
|
+
let at = 0;
|
|
214
|
+
const parts = entries.flatMap((e, i) => {
|
|
215
|
+
const rampId = `${idPrefix}-ramp-${i}`;
|
|
216
|
+
const ramp = !e.note && e.swatch.kind === "bar" && e.swatch.ramp ? el(
|
|
217
|
+
"linearGradient",
|
|
218
|
+
{ id: rampId },
|
|
219
|
+
...RAMP_GRADIENT_STOPS.map(
|
|
220
|
+
(s) => el("stop", { offset: s.offset, "stop-color": s.color })
|
|
221
|
+
)
|
|
222
|
+
) : void 0;
|
|
223
|
+
const swatch = e.note ? void 0 : e.swatch.kind === "gene" ? el("rect", {
|
|
224
|
+
x: at,
|
|
225
|
+
y: 5,
|
|
226
|
+
width: 18,
|
|
227
|
+
height: 8,
|
|
228
|
+
fill: "none",
|
|
229
|
+
stroke: GENE_INK,
|
|
230
|
+
"stroke-width": 1.5
|
|
231
|
+
}) : el("rect", {
|
|
232
|
+
x: at,
|
|
233
|
+
y: e.swatch.kind === "gap" ? 7 : 5,
|
|
234
|
+
width: 18,
|
|
235
|
+
height: e.swatch.kind === "gap" ? GAP_PX : 8,
|
|
236
|
+
rx: 2,
|
|
237
|
+
fill: ramp ? `url(#${rampId})` : e.swatch.fill
|
|
238
|
+
});
|
|
239
|
+
const text = el(
|
|
240
|
+
"text",
|
|
241
|
+
{
|
|
242
|
+
x: e.note ? at : at + 23,
|
|
243
|
+
y: 13,
|
|
244
|
+
"font-style": e.note ? "italic" : void 0,
|
|
245
|
+
fill: e.note ? "#666" : void 0
|
|
246
|
+
},
|
|
247
|
+
e.label
|
|
248
|
+
);
|
|
249
|
+
at += (e.note ? 0 : 23) + e.label.length * LABEL_CHAR_PX + 16;
|
|
250
|
+
return [ramp, swatch, text].filter((p) => p !== void 0);
|
|
251
|
+
});
|
|
252
|
+
return {
|
|
253
|
+
width: at,
|
|
254
|
+
tree: el(
|
|
255
|
+
"g",
|
|
256
|
+
{
|
|
257
|
+
transform: `translate(${x} ${y})`,
|
|
258
|
+
"font-family": "Helvetica, Arial, sans-serif",
|
|
259
|
+
"font-size": 11
|
|
260
|
+
},
|
|
261
|
+
...parts
|
|
262
|
+
)
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function coalesceRuns(runs, pxPerBp) {
|
|
266
|
+
const minBp = 1 / pxPerBp;
|
|
267
|
+
const out = [];
|
|
268
|
+
let bucket = [];
|
|
269
|
+
const flush = () => {
|
|
270
|
+
if (bucket.length === 0) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (bucket.length === 1) {
|
|
274
|
+
out.push(bucket[0]);
|
|
275
|
+
} else {
|
|
276
|
+
let on = 0;
|
|
277
|
+
let off = 0;
|
|
278
|
+
for (const r of bucket) {
|
|
279
|
+
if (r.onReference) {
|
|
280
|
+
on += r.bp;
|
|
281
|
+
} else {
|
|
282
|
+
off += r.bp;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const onReference = on >= off;
|
|
286
|
+
const lead = bucket.find((r) => r.onReference === onReference);
|
|
287
|
+
out.push({
|
|
288
|
+
start: bucket[0].start,
|
|
289
|
+
bp: on + off,
|
|
290
|
+
onReference,
|
|
291
|
+
referenceStart: onReference ? lead.referenceStart : void 0,
|
|
292
|
+
...lead.reversed ? { reversed: true } : {}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
bucket = [];
|
|
296
|
+
};
|
|
297
|
+
let held = 0;
|
|
298
|
+
for (const run of runs) {
|
|
299
|
+
if (run.gap || run.bp >= minBp) {
|
|
300
|
+
flush();
|
|
301
|
+
held = 0;
|
|
302
|
+
out.push(run);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
bucket.push(run);
|
|
306
|
+
held += run.bp;
|
|
307
|
+
if (held >= minBp) {
|
|
308
|
+
flush();
|
|
309
|
+
held = 0;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
flush();
|
|
313
|
+
return out;
|
|
314
|
+
}
|
|
315
|
+
function geneTree(box, y, barPx) {
|
|
316
|
+
return el(
|
|
317
|
+
"g",
|
|
318
|
+
{ class: "row-gene", "data-testid": "graph-walk-gene" },
|
|
319
|
+
el("title", {}, box.name),
|
|
320
|
+
...box.exons.map(
|
|
321
|
+
(e) => el("rect", {
|
|
322
|
+
x: e.x,
|
|
323
|
+
y: y - barPx / 2,
|
|
324
|
+
width: e.w,
|
|
325
|
+
height: barPx,
|
|
326
|
+
fill: GENE_INK,
|
|
327
|
+
opacity: 0.35
|
|
328
|
+
})
|
|
329
|
+
),
|
|
330
|
+
el("rect", {
|
|
331
|
+
x: box.x,
|
|
332
|
+
y: box.y,
|
|
333
|
+
width: box.w,
|
|
334
|
+
height: box.h,
|
|
335
|
+
fill: "none",
|
|
336
|
+
stroke: GENE_INK,
|
|
337
|
+
"stroke-width": 1.5
|
|
338
|
+
}),
|
|
339
|
+
box.label && el(
|
|
340
|
+
"text",
|
|
341
|
+
{
|
|
342
|
+
x: box.label.x,
|
|
343
|
+
y: box.label.y,
|
|
344
|
+
"font-family": "sans-serif",
|
|
345
|
+
"font-size": box.label.size,
|
|
346
|
+
"font-weight": 600,
|
|
347
|
+
"text-anchor": "middle",
|
|
348
|
+
fill: GENE_INK,
|
|
349
|
+
stroke: "white",
|
|
350
|
+
"stroke-width": 2.5,
|
|
351
|
+
"paint-order": "stroke"
|
|
352
|
+
},
|
|
353
|
+
box.name
|
|
354
|
+
)
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
function walkRowsTree(bars, frame, o = {}) {
|
|
358
|
+
const X = (bp) => bp * frame.scaleX + frame.translateX;
|
|
359
|
+
const rowPx = frame.rowPx ?? ROW_HEIGHT_PX;
|
|
360
|
+
const barPx = frame.barPx ?? BAR_PX;
|
|
361
|
+
const Y = (row) => row * rowPx * frame.scaleY + frame.translateY;
|
|
362
|
+
const { origin, unit, reference, rows } = bars;
|
|
363
|
+
const along = (offset) => X(origin + offset);
|
|
364
|
+
const prefix = o.idPrefix ?? "walkrow";
|
|
365
|
+
const rowTree = (row, i) => {
|
|
366
|
+
const y = Y(i);
|
|
367
|
+
if (y < -barPx || y > frame.height + barPx) {
|
|
368
|
+
return void 0;
|
|
369
|
+
}
|
|
370
|
+
const runs = coalesceRuns(row.runs, frame.scaleX).flatMap((run) => {
|
|
371
|
+
const paint = runPaint(run, o.ramp);
|
|
372
|
+
const h = run.gap ? Math.min(GAP_PX, barPx) : barPx;
|
|
373
|
+
const id = `${prefix}-${i}-${run.start}`;
|
|
374
|
+
const rect = el("rect", {
|
|
375
|
+
x: along(run.start),
|
|
376
|
+
y: y - h / 2,
|
|
377
|
+
width: Math.max(1, run.bp * frame.scaleX),
|
|
378
|
+
height: h,
|
|
379
|
+
fill: "stops" in paint ? `url(#${id})` : paint.fill
|
|
380
|
+
});
|
|
381
|
+
return "stops" in paint ? [
|
|
382
|
+
el(
|
|
383
|
+
"linearGradient",
|
|
384
|
+
{ id },
|
|
385
|
+
...paint.stops.map(
|
|
386
|
+
(color, k) => el("stop", {
|
|
387
|
+
offset: k / (paint.stops.length - 1),
|
|
388
|
+
"stop-color": color
|
|
389
|
+
})
|
|
390
|
+
)
|
|
391
|
+
),
|
|
392
|
+
rect
|
|
393
|
+
] : [rect];
|
|
394
|
+
});
|
|
395
|
+
const ticks = unit ? unitTicks(row.bp, unit, frame.scaleX).map(
|
|
396
|
+
(k) => el("line", {
|
|
397
|
+
x1: along(k),
|
|
398
|
+
x2: along(k),
|
|
399
|
+
y1: y - barPx / 2,
|
|
400
|
+
y2: y + barPx / 2,
|
|
401
|
+
stroke: "white",
|
|
402
|
+
"stroke-width": 1
|
|
403
|
+
})
|
|
404
|
+
) : [];
|
|
405
|
+
const { call } = row;
|
|
406
|
+
const text = walkRowReadout(
|
|
407
|
+
row,
|
|
408
|
+
i === 0 ? void 0 : reference,
|
|
409
|
+
unit,
|
|
410
|
+
call
|
|
411
|
+
);
|
|
412
|
+
const at = readoutPlacement(text, along(row.bp), frame.width);
|
|
413
|
+
const readouts = frame.readouts ?? true;
|
|
414
|
+
return el(
|
|
415
|
+
"g",
|
|
416
|
+
{
|
|
417
|
+
"data-testid": i === 0 ? "graph-walk-reference" : "graph-walk-row"
|
|
418
|
+
},
|
|
419
|
+
...runs,
|
|
420
|
+
...ticks,
|
|
421
|
+
...rowGeneBoxes(o.rowGenes?.get(row.name) ?? [], along, y, {
|
|
422
|
+
rowPx,
|
|
423
|
+
barPx
|
|
424
|
+
}).map((box) => geneTree(box, y, barPx)),
|
|
425
|
+
call && el("rect", {
|
|
426
|
+
"data-testid": "graph-walk-call",
|
|
427
|
+
x: along(call.bp) - 1,
|
|
428
|
+
y: y - barPx / 2 - 3,
|
|
429
|
+
width: 2,
|
|
430
|
+
height: barPx + 6,
|
|
431
|
+
fill: call.spanningReads === 0 ? UNBACKED_TICK : CALL_TICK
|
|
432
|
+
}),
|
|
433
|
+
readouts && el(
|
|
434
|
+
"text",
|
|
435
|
+
{
|
|
436
|
+
x: at.x,
|
|
437
|
+
y: y + 4,
|
|
438
|
+
"font-family": "sans-serif",
|
|
439
|
+
"font-size": 11,
|
|
440
|
+
fill: call?.agrees === false ? DISAGREES : "#333",
|
|
441
|
+
stroke: at.halo ? "white" : void 0,
|
|
442
|
+
"stroke-width": at.halo ? 3 : void 0,
|
|
443
|
+
"paint-order": "stroke",
|
|
444
|
+
"text-anchor": at.anchor
|
|
445
|
+
},
|
|
446
|
+
text
|
|
447
|
+
)
|
|
448
|
+
);
|
|
449
|
+
};
|
|
450
|
+
return el("g", {}, ...[reference, ...rows].map(rowTree));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export {
|
|
454
|
+
ON_REFERENCE,
|
|
455
|
+
OFF_REFERENCE,
|
|
456
|
+
OUTSIDE_CUT,
|
|
457
|
+
GENE_INK,
|
|
458
|
+
BAR_PX,
|
|
459
|
+
GAP_PX,
|
|
460
|
+
kb,
|
|
461
|
+
walkRowReadout,
|
|
462
|
+
readoutPlacement,
|
|
463
|
+
unitTicks,
|
|
464
|
+
runPaint,
|
|
465
|
+
alongRow,
|
|
466
|
+
rowSpan,
|
|
467
|
+
genesOnRow,
|
|
468
|
+
placeRowGenes,
|
|
469
|
+
boxesGenes,
|
|
470
|
+
rowGeneBoxes,
|
|
471
|
+
walkRowsKey,
|
|
472
|
+
DISAGREES,
|
|
473
|
+
walkRowsKeyTree,
|
|
474
|
+
coalesceRuns,
|
|
475
|
+
walkRowsTree
|
|
476
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
stableCoordinate
|
|
3
|
+
} from "./chunk-CDLPYR5O.js";
|
|
4
|
+
import {
|
|
5
|
+
pathOrigin,
|
|
6
|
+
surveyPaths
|
|
7
|
+
} from "./chunk-ST2IBUAN.js";
|
|
8
|
+
|
|
9
|
+
// src/gfa/gfaConverter.ts
|
|
10
|
+
function surveySegments(gfaGraph) {
|
|
11
|
+
const canonical = /* @__PURE__ */ new Map();
|
|
12
|
+
const traversals = /* @__PURE__ */ new Map();
|
|
13
|
+
function claim(id, strand) {
|
|
14
|
+
if (!canonical.has(id)) {
|
|
15
|
+
canonical.set(id, strand);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function traverse(id, strand) {
|
|
19
|
+
claim(id, strand);
|
|
20
|
+
traversals.set(id, (traversals.get(id) ?? 0) + 1);
|
|
21
|
+
}
|
|
22
|
+
for (const link of gfaGraph.links) {
|
|
23
|
+
claim(link.source, link.strand1 === "-" ? "-" : "+");
|
|
24
|
+
claim(link.target, link.strand2 === "-" ? "-" : "+");
|
|
25
|
+
}
|
|
26
|
+
for (const gfaPath of gfaGraph.paths) {
|
|
27
|
+
for (const segment of gfaPath.path.split(",")) {
|
|
28
|
+
traverse(segment.slice(0, -1), segment.endsWith("-") ? "-" : "+");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
for (const walk of gfaGraph.walks) {
|
|
32
|
+
for (const seg of walk.segments) {
|
|
33
|
+
traverse(seg.id, seg.strand === "-" ? "-" : "+");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { canonical, traversals };
|
|
37
|
+
}
|
|
38
|
+
var walkStart = (w) => w.start === -1 ? 0 : w.start;
|
|
39
|
+
function anchorablePaths(gfaGraph) {
|
|
40
|
+
return [
|
|
41
|
+
...gfaGraph.paths.map((p) => ({
|
|
42
|
+
...pathOrigin(p.name),
|
|
43
|
+
steps: p.path.split(",").map((segment) => ({
|
|
44
|
+
id: segment.slice(0, -1),
|
|
45
|
+
strand: segment.endsWith("-") ? "-" : "+"
|
|
46
|
+
}))
|
|
47
|
+
})),
|
|
48
|
+
...gfaGraph.walks.map((w) => ({
|
|
49
|
+
name: `${w.sample}#${w.haplotype}#${w.contig}`,
|
|
50
|
+
start: walkStart(w),
|
|
51
|
+
steps: w.segments.map((s) => ({
|
|
52
|
+
id: s.id,
|
|
53
|
+
strand: s.strand === "-" ? "-" : "+"
|
|
54
|
+
}))
|
|
55
|
+
}))
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
function carriedSamples(gfaNode) {
|
|
59
|
+
const sm = gfaNode.tags.SM;
|
|
60
|
+
if (typeof sm !== "string") {
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
const samples = sm.split(",").map((s) => s.trim()).filter(Boolean);
|
|
64
|
+
return samples.length > 0 ? samples : void 0;
|
|
65
|
+
}
|
|
66
|
+
var positive = (v) => typeof v === "number" && v > 0 ? v : void 0;
|
|
67
|
+
function statedDepth({ tags, length }) {
|
|
68
|
+
const count = positive(tags.RC) ?? positive(tags.FC) ?? positive(tags.KC);
|
|
69
|
+
return positive(tags.dp) ?? positive(tags.DP) ?? (count !== void 0 && length > 0 ? count / length : void 0);
|
|
70
|
+
}
|
|
71
|
+
function makeNode(gfaNode, strand, depth) {
|
|
72
|
+
return {
|
|
73
|
+
id: `${gfaNode.id}${strand}`,
|
|
74
|
+
name: gfaNode.id,
|
|
75
|
+
length: gfaNode.length,
|
|
76
|
+
depth,
|
|
77
|
+
stable: stableCoordinate(gfaNode),
|
|
78
|
+
samples: carriedSamples(gfaNode)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function convertGFAToGraph(gfaGraph, name = "Imported GFA") {
|
|
82
|
+
const nodes = [];
|
|
83
|
+
const edges = [];
|
|
84
|
+
const { canonical, traversals } = surveySegments(gfaGraph);
|
|
85
|
+
const nodeId = (segmentId) => `${segmentId}${canonical.get(segmentId) ?? "+"}`;
|
|
86
|
+
for (const gfaNode of gfaGraph.nodes) {
|
|
87
|
+
const depth = statedDepth(gfaNode) ?? Math.max(traversals.get(gfaNode.id) ?? 1, 1);
|
|
88
|
+
nodes.push(makeNode(gfaNode, canonical.get(gfaNode.id) ?? "+", depth));
|
|
89
|
+
}
|
|
90
|
+
for (const link of gfaGraph.links) {
|
|
91
|
+
edges.push({
|
|
92
|
+
from: nodeId(link.source),
|
|
93
|
+
to: nodeId(link.target),
|
|
94
|
+
fromStrand: link.strand1 === "-" ? "-" : "+",
|
|
95
|
+
toStrand: link.strand2 === "-" ? "-" : "+"
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const paths = [];
|
|
99
|
+
const edgeToPathsMap = /* @__PURE__ */ new Map();
|
|
100
|
+
const edgeKey = (a, b) => [a, b].sort().join("--");
|
|
101
|
+
function recordPathEdges(nodeIds, name2) {
|
|
102
|
+
for (let i = 0; i < nodeIds.length - 1; i++) {
|
|
103
|
+
const key = edgeKey(nodeIds[i], nodeIds[i + 1]);
|
|
104
|
+
if (!edgeToPathsMap.has(key)) {
|
|
105
|
+
edgeToPathsMap.set(key, /* @__PURE__ */ new Set());
|
|
106
|
+
}
|
|
107
|
+
edgeToPathsMap.get(key).add(name2);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const gfaPath of gfaGraph.paths) {
|
|
111
|
+
const nodeIds = gfaPath.path.split(",").map((segment) => nodeId(segment.slice(0, -1)));
|
|
112
|
+
paths.push({
|
|
113
|
+
name: gfaPath.name,
|
|
114
|
+
nodeIds,
|
|
115
|
+
start: pathOrigin(gfaPath.name).start
|
|
116
|
+
});
|
|
117
|
+
recordPathEdges(nodeIds, gfaPath.name);
|
|
118
|
+
}
|
|
119
|
+
for (const walk of gfaGraph.walks) {
|
|
120
|
+
const nodeIds = walk.segments.map((seg) => nodeId(seg.id));
|
|
121
|
+
const name2 = `${walk.sample}#${walk.haplotype}#${walk.contig}`;
|
|
122
|
+
paths.push({
|
|
123
|
+
name: name2,
|
|
124
|
+
nodeIds,
|
|
125
|
+
start: walkStart(walk),
|
|
126
|
+
sample: walk.sample,
|
|
127
|
+
haplotype: walk.haplotype,
|
|
128
|
+
contig: walk.contig
|
|
129
|
+
});
|
|
130
|
+
recordPathEdges(nodeIds, name2);
|
|
131
|
+
}
|
|
132
|
+
for (const edge of edges) {
|
|
133
|
+
const pathIds = edgeToPathsMap.get(edgeKey(edge.from, edge.to));
|
|
134
|
+
if (pathIds && pathIds.size > 0) {
|
|
135
|
+
edge.pathIds = [...pathIds];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const lengths = new Map(gfaGraph.nodes.map((n) => [n.id, n.length]));
|
|
139
|
+
const anchoring = gfaGraph.paths.length > 0 || gfaGraph.walks.length > 0 ? surveyPaths(anchorablePaths(gfaGraph), (id) => lengths.get(id) ?? 0) : void 0;
|
|
140
|
+
return {
|
|
141
|
+
name,
|
|
142
|
+
nodes,
|
|
143
|
+
edges,
|
|
144
|
+
paths: paths.length > 0 ? paths : void 0,
|
|
145
|
+
anchorPaths: anchoring?.anchorPaths,
|
|
146
|
+
pathVisits: anchoring?.pathVisits,
|
|
147
|
+
// rGFA's own tags win wherever they are present; pathAnchoring only ever
|
|
148
|
+
// fills in for a graph that carries none.
|
|
149
|
+
anchoredBy: nodes.some((n) => n.stable) ? "tags" : void 0
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export {
|
|
154
|
+
convertGFAToGraph
|
|
155
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GBZ_CUT_DEFAULTS,
|
|
3
|
+
cutWindowGFA,
|
|
4
|
+
haplotypeWanted,
|
|
5
|
+
referencePathQuery,
|
|
6
|
+
referenceSamplesOf,
|
|
7
|
+
resolveReferenceSample
|
|
8
|
+
} from "./chunk-LTUWJKHD.js";
|
|
9
|
+
|
|
10
|
+
// src/gbzCut.ts
|
|
11
|
+
import { GBZBase } from "@gmod/gbz-base";
|
|
12
|
+
var HPRC_GBZ = {
|
|
13
|
+
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",
|
|
14
|
+
index: "https://jbrowse.org/demos/hprc/hprc-v2.1-mc-grch38.haplotype-index.f3.db"
|
|
15
|
+
};
|
|
16
|
+
function haplotypeIndexBeside(db) {
|
|
17
|
+
const GBZ_DB = /\.gbz\.db$/i;
|
|
18
|
+
return GBZ_DB.test(db) ? db.replace(GBZ_DB, ".haplotype-index.db") : void 0;
|
|
19
|
+
}
|
|
20
|
+
function parseRegion(text) {
|
|
21
|
+
const m = /^\s*([^:\s]+):([\d,]+)-([\d,]+)\s*$/.exec(text);
|
|
22
|
+
if (!m) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`"${text}" is not a region like chr6:160,614,798-160,647,758`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
const n = (s) => Number(s.replaceAll(",", ""));
|
|
28
|
+
return { refName: m[1], start: n(m[2]), end: n(m[3]) };
|
|
29
|
+
}
|
|
30
|
+
async function openGbz(db, index) {
|
|
31
|
+
const base = await GBZBase.open({ source: db, haplotypeIndex: index });
|
|
32
|
+
return { base, referenceSamples: await referenceSamplesOf(base) };
|
|
33
|
+
}
|
|
34
|
+
function referenceSample(ref, samples) {
|
|
35
|
+
try {
|
|
36
|
+
return resolveReferenceSample({
|
|
37
|
+
configured: "",
|
|
38
|
+
anchorPrefix: ref ?? samples[0] ?? "",
|
|
39
|
+
referenceSamples: samples
|
|
40
|
+
});
|
|
41
|
+
} catch (e) {
|
|
42
|
+
if (ref) {
|
|
43
|
+
return ref;
|
|
44
|
+
}
|
|
45
|
+
throw e;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function cutGbzRegion({ base, referenceSamples }, src, signal) {
|
|
49
|
+
const region = parseRegion(src.region);
|
|
50
|
+
const sample = referenceSample(src.referenceSample, referenceSamples);
|
|
51
|
+
const query = await referencePathQuery(base, sample, region.refName);
|
|
52
|
+
if (!query) {
|
|
53
|
+
throw new Error(`${sample} has no indexed path named ${region.refName}`);
|
|
54
|
+
}
|
|
55
|
+
signal?.throwIfAborted();
|
|
56
|
+
const wanted = src.haplotypes?.length ? src.haplotypes : void 0;
|
|
57
|
+
const text = await cutWindowGFA(base, query, region.start, region.end, {
|
|
58
|
+
context: src.context ?? GBZ_CUT_DEFAULTS.context,
|
|
59
|
+
snarls: src.snarls ?? GBZ_CUT_DEFAULTS.snarls,
|
|
60
|
+
limit: GBZ_CUT_DEFAULTS.limit,
|
|
61
|
+
signal,
|
|
62
|
+
keep: wanted ? (name) => haplotypeWanted(name, wanted) : void 0
|
|
63
|
+
});
|
|
64
|
+
return { text, region, sample };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
HPRC_GBZ,
|
|
69
|
+
haplotypeIndexBeside,
|
|
70
|
+
parseRegion,
|
|
71
|
+
openGbz,
|
|
72
|
+
cutGbzRegion
|
|
73
|
+
};
|