@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,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SATURATED_PATH_COUNT
|
|
3
|
+
} from "./chunk-MFKORG3F.js";
|
|
4
|
+
|
|
5
|
+
// src/bubbles/classifyBubble.ts
|
|
6
|
+
var REPEAT_GROWTH = 10;
|
|
7
|
+
function coveringRepeat(b, repeats) {
|
|
8
|
+
const span = b.end - b.start;
|
|
9
|
+
const growth = b.longestAlleleLength - b.shortestAlleleLength;
|
|
10
|
+
return repeats.find(
|
|
11
|
+
(r) => growth <= REPEAT_GROWTH * (r.end - r.start) && (span === 0 ? r.start <= b.start && b.start <= r.end : Math.min(b.end, r.end) - Math.max(b.start, r.start) >= span / 2)
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
var SUPERBUBBLE_SEGMENTS = 40;
|
|
15
|
+
function formatBp(bp) {
|
|
16
|
+
if (bp >= 1e4) {
|
|
17
|
+
return `${(bp / 1e3).toFixed(0)} kb`;
|
|
18
|
+
}
|
|
19
|
+
if (bp >= 1e3) {
|
|
20
|
+
return `${(bp / 1e3).toFixed(1)} kb`;
|
|
21
|
+
}
|
|
22
|
+
return `${bp} bp`;
|
|
23
|
+
}
|
|
24
|
+
function formatBpRange(shortest, longest) {
|
|
25
|
+
const low = formatBp(shortest);
|
|
26
|
+
const high = formatBp(longest);
|
|
27
|
+
const unit = high.slice(high.indexOf(" "));
|
|
28
|
+
return low.endsWith(unit) ? `${low.slice(0, -unit.length)}\u2013${high}` : `${low}\u2013${high}`;
|
|
29
|
+
}
|
|
30
|
+
function routes(count) {
|
|
31
|
+
if (count >= SATURATED_PATH_COUNT) {
|
|
32
|
+
return "\u22652.1B routes";
|
|
33
|
+
}
|
|
34
|
+
if (count >= 1e5) {
|
|
35
|
+
return `${(count / 1e3).toFixed(0)}k routes`;
|
|
36
|
+
}
|
|
37
|
+
return `${count} routes`;
|
|
38
|
+
}
|
|
39
|
+
function classifyBubble(b, repeats = []) {
|
|
40
|
+
const c = classifyShape(b, repeats);
|
|
41
|
+
return b.partial ? { ...c, label: `${c.label}, partial` } : c;
|
|
42
|
+
}
|
|
43
|
+
function classifyShape(b, repeats) {
|
|
44
|
+
const refSpan = b.end - b.start;
|
|
45
|
+
const {
|
|
46
|
+
shortestAlleleLength: shortest,
|
|
47
|
+
longestAlleleLength: longest,
|
|
48
|
+
pathCount,
|
|
49
|
+
segmentCount,
|
|
50
|
+
inversion
|
|
51
|
+
} = b;
|
|
52
|
+
const range = formatBpRange(shortest, longest);
|
|
53
|
+
if (segmentCount >= SUPERBUBBLE_SEGMENTS) {
|
|
54
|
+
return {
|
|
55
|
+
kind: "superbubble",
|
|
56
|
+
label: `${range} superbubble, ${segmentCount} segments, ${routes(pathCount)}${inversion ? ", inv" : ""}`
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (inversion) {
|
|
60
|
+
return { kind: "inversion", label: `${formatBp(longest)} inv` };
|
|
61
|
+
}
|
|
62
|
+
const repeat = longest !== shortest ? coveringRepeat(b, repeats) : void 0;
|
|
63
|
+
if (repeat) {
|
|
64
|
+
const name = repeat.name ? ` (${repeat.name})` : "";
|
|
65
|
+
return {
|
|
66
|
+
kind: "repeat",
|
|
67
|
+
label: `${range} repeat array${name}, ${routes(pathCount)}`
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const alleles = pathCount > 2 ? `, ${pathCount} alleles` : "";
|
|
71
|
+
if (shortest === longest && shortest === refSpan) {
|
|
72
|
+
return refSpan <= 1 ? { kind: "snp", label: "SNP" } : { kind: "substitution", label: `${formatBp(refSpan)} sub` };
|
|
73
|
+
}
|
|
74
|
+
if (refSpan === 0 || shortest === refSpan) {
|
|
75
|
+
return {
|
|
76
|
+
kind: "insertion",
|
|
77
|
+
label: `\u2264${formatBp(longest - refSpan)} ins${alleles}`
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (longest === refSpan) {
|
|
81
|
+
return {
|
|
82
|
+
kind: "deletion",
|
|
83
|
+
label: `${formatBp(refSpan - shortest)} del${alleles}`
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
kind: "complex",
|
|
88
|
+
label: `${formatBp(refSpan)} ref \u2192 ${range}, ${pathCount} alleles`
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
var BUBBLE_KIND_COLORS = {
|
|
92
|
+
snp: "#5b6b7a",
|
|
93
|
+
substitution: "#5b6b7a",
|
|
94
|
+
insertion: "#2f8fd6",
|
|
95
|
+
deletion: "#c94040",
|
|
96
|
+
inversion: "#e07b00",
|
|
97
|
+
repeat: "#8e3fbf",
|
|
98
|
+
superbubble: "#8e3fbf",
|
|
99
|
+
complex: "#8e3fbf"
|
|
100
|
+
};
|
|
101
|
+
var BUBBLE_KIND_NAMES = {
|
|
102
|
+
snp: "SNP",
|
|
103
|
+
substitution: "substitution",
|
|
104
|
+
insertion: "insertion",
|
|
105
|
+
deletion: "deletion",
|
|
106
|
+
inversion: "inversion",
|
|
107
|
+
repeat: "repeat array",
|
|
108
|
+
superbubble: "superbubble",
|
|
109
|
+
complex: "site"
|
|
110
|
+
};
|
|
111
|
+
function bubbleSegmentIds(b) {
|
|
112
|
+
return b.segments.split(",").filter(Boolean);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export {
|
|
116
|
+
formatBp,
|
|
117
|
+
classifyBubble,
|
|
118
|
+
BUBBLE_KIND_COLORS,
|
|
119
|
+
BUBBLE_KIND_NAMES,
|
|
120
|
+
bubbleSegmentIds
|
|
121
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// src/gbzJoin.ts
|
|
2
|
+
var UNKNOWN_SAMPLE = "unknown";
|
|
3
|
+
function parseWalk(line) {
|
|
4
|
+
const fields = line.split(" ");
|
|
5
|
+
return {
|
|
6
|
+
fields,
|
|
7
|
+
name: fields.slice(1, 4).join(" "),
|
|
8
|
+
start: +fields[4],
|
|
9
|
+
end: +fields[5]
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
var stepsOf = (walk) => walk.fields[6].match(/[<>][^<>]+/g) ?? [];
|
|
13
|
+
function segmentLengths(lines) {
|
|
14
|
+
const lengths = /* @__PURE__ */ new Map();
|
|
15
|
+
for (const line of lines) {
|
|
16
|
+
if (line.startsWith("S ")) {
|
|
17
|
+
const [, id, sequence, ...tags] = line.split(" ");
|
|
18
|
+
const ln = tags.find((tag) => tag.startsWith("LN:i:"));
|
|
19
|
+
lengths.set(
|
|
20
|
+
id,
|
|
21
|
+
sequence === "*" && ln ? +ln.slice("LN:i:".length) : sequence.length
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return lengths;
|
|
26
|
+
}
|
|
27
|
+
function extend(a, b, lengths) {
|
|
28
|
+
if (b.end <= a.end) {
|
|
29
|
+
return a;
|
|
30
|
+
}
|
|
31
|
+
const steps = stepsOf(b);
|
|
32
|
+
let pos = b.start;
|
|
33
|
+
for (const [i, step] of steps.entries()) {
|
|
34
|
+
if (pos === a.end) {
|
|
35
|
+
const fields = [...a.fields];
|
|
36
|
+
fields[5] = String(b.end);
|
|
37
|
+
fields[6] = a.fields[6] + steps.slice(i).join("");
|
|
38
|
+
return { ...a, fields, end: b.end };
|
|
39
|
+
}
|
|
40
|
+
const length = lengths.get(step.slice(1));
|
|
41
|
+
if (pos > a.end || length === void 0) {
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
pos += length;
|
|
45
|
+
}
|
|
46
|
+
return void 0;
|
|
47
|
+
}
|
|
48
|
+
function joinPieces(pieces, lengths, startsPath) {
|
|
49
|
+
const joined = [];
|
|
50
|
+
for (const piece of pieces.sort((a, b) => a.start - b.start)) {
|
|
51
|
+
const last = joined.at(-1);
|
|
52
|
+
const [, sample, haplotype, contig] = piece.fields;
|
|
53
|
+
const joins = last !== void 0 && (piece.start < last.end || piece.start === last.end && !startsPath(sample, +haplotype, contig, piece.start));
|
|
54
|
+
const extended = joins ? extend(last, piece, lengths) : void 0;
|
|
55
|
+
if (extended) {
|
|
56
|
+
joined[joined.length - 1] = extended;
|
|
57
|
+
} else {
|
|
58
|
+
joined.push(piece);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return joined;
|
|
62
|
+
}
|
|
63
|
+
var flip = (sign) => sign === "+" ? "-" : "+";
|
|
64
|
+
function* stepLinks(walk) {
|
|
65
|
+
const steps = stepsOf(walk).map((step) => ({
|
|
66
|
+
id: step.slice(1),
|
|
67
|
+
sign: step.startsWith(">") ? "+" : "-"
|
|
68
|
+
}));
|
|
69
|
+
for (let i = 1; i < steps.length; i++) {
|
|
70
|
+
const a = steps[i - 1];
|
|
71
|
+
const b = steps[i];
|
|
72
|
+
yield {
|
|
73
|
+
forward: `L ${a.id} ${a.sign} ${b.id} ${b.sign} 0M`,
|
|
74
|
+
reverse: `L ${b.id} ${flip(b.sign)} ${a.id} ${flip(a.sign)} 0M`
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function joinCuts(gfas, startsPath) {
|
|
79
|
+
if (gfas.length <= 1) {
|
|
80
|
+
return gfas[0] ?? "";
|
|
81
|
+
}
|
|
82
|
+
const cuts = gfas.map((gfa) => gfa.split("\n").filter((line) => line !== ""));
|
|
83
|
+
const ofType = (cut, type) => cut.filter((line) => line.startsWith(`${type} `));
|
|
84
|
+
const lengths = segmentLengths(cuts.flat());
|
|
85
|
+
let unknownOffset = 0;
|
|
86
|
+
const walksByCut = cuts.map((cut) => {
|
|
87
|
+
const walks2 = ofType(cut, "W").map(parseWalk);
|
|
88
|
+
const offset = unknownOffset;
|
|
89
|
+
for (const walk of walks2.filter((w) => w.fields[1] === UNKNOWN_SAMPLE)) {
|
|
90
|
+
const haplotype = +walk.fields[2] + offset;
|
|
91
|
+
walk.fields[2] = String(haplotype);
|
|
92
|
+
walk.name = walk.fields.slice(1, 4).join(" ");
|
|
93
|
+
unknownOffset = Math.max(unknownOffset, haplotype);
|
|
94
|
+
}
|
|
95
|
+
return walks2;
|
|
96
|
+
});
|
|
97
|
+
const byName = /* @__PURE__ */ new Map();
|
|
98
|
+
for (const walk of [
|
|
99
|
+
...walksByCut.flatMap((walks2) => walks2.slice(0, 1)),
|
|
100
|
+
...walksByCut.flatMap((walks2) => walks2.slice(1))
|
|
101
|
+
]) {
|
|
102
|
+
const pieces = byName.get(walk.name);
|
|
103
|
+
if (pieces) {
|
|
104
|
+
pieces.push(walk);
|
|
105
|
+
} else {
|
|
106
|
+
byName.set(walk.name, [walk]);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const walks = [...byName.values()].flatMap(
|
|
110
|
+
(pieces) => joinPieces(pieces, lengths, startsPath)
|
|
111
|
+
);
|
|
112
|
+
const links = new Set(cuts.flatMap((cut) => ofType(cut, "L")));
|
|
113
|
+
for (const walk of walks) {
|
|
114
|
+
for (const link of stepLinks(walk)) {
|
|
115
|
+
if (!links.has(link.forward) && !links.has(link.reverse)) {
|
|
116
|
+
links.add(link.forward);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const lines = /* @__PURE__ */ new Set([
|
|
121
|
+
...ofType(cuts[0], "H"),
|
|
122
|
+
...cuts.flatMap((cut) => ofType(cut, "S")),
|
|
123
|
+
...links,
|
|
124
|
+
...walks.map((walk) => walk.fields.join(" "))
|
|
125
|
+
]);
|
|
126
|
+
return `${[...lines].join("\n")}
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export {
|
|
131
|
+
joinCuts
|
|
132
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
pathOrigin
|
|
3
|
+
} from "./chunk-ST2IBUAN.js";
|
|
4
|
+
import {
|
|
5
|
+
groupDigits
|
|
6
|
+
} from "./chunk-N62KBLFC.js";
|
|
7
|
+
import {
|
|
8
|
+
isGenericSample
|
|
9
|
+
} from "./chunk-BPLIXMH7.js";
|
|
10
|
+
|
|
11
|
+
// src/pathColors.ts
|
|
12
|
+
function nameHue(name) {
|
|
13
|
+
let hash = 0;
|
|
14
|
+
for (let i = 0; i < name.length; i++) {
|
|
15
|
+
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
|
16
|
+
}
|
|
17
|
+
return Math.abs(hash % 360);
|
|
18
|
+
}
|
|
19
|
+
function pathHueAt(index, count) {
|
|
20
|
+
return index * 360 / count;
|
|
21
|
+
}
|
|
22
|
+
var PATH_SATURATION = 0.7;
|
|
23
|
+
var PATH_LIGHTNESS = 0.5;
|
|
24
|
+
var MAX_PATH_COLORS = 16;
|
|
25
|
+
function pathColorsLegible(pathCount) {
|
|
26
|
+
return pathCount > 0 && pathCount <= MAX_PATH_COLORS;
|
|
27
|
+
}
|
|
28
|
+
function pathCssColor(index, count) {
|
|
29
|
+
return `hsl(${pathHueAt(index, count)}, ${PATH_SATURATION * 100}%, ${PATH_LIGHTNESS * 100}%)`;
|
|
30
|
+
}
|
|
31
|
+
function pathGreyCssColor(index, count) {
|
|
32
|
+
const lightness = count > 1 ? 25 + 45 * index / (count - 1) : 45;
|
|
33
|
+
return `hsl(0, 0%, ${lightness}%)`;
|
|
34
|
+
}
|
|
35
|
+
function labelTiers(name) {
|
|
36
|
+
const parts = pathOrigin(name).name.split("#");
|
|
37
|
+
const full = parts.join("#");
|
|
38
|
+
if (parts.length >= 3 && isGenericSample(parts[0])) {
|
|
39
|
+
const contig = parts.slice(2).join("#");
|
|
40
|
+
return [contig, contig, full];
|
|
41
|
+
}
|
|
42
|
+
return [parts[0], parts.slice(0, 2).join("#"), full];
|
|
43
|
+
}
|
|
44
|
+
function offsetTiers(name, tiers) {
|
|
45
|
+
const { start } = pathOrigin(name);
|
|
46
|
+
return tiers.map((t) => `${t} @${groupDigits(start)}`);
|
|
47
|
+
}
|
|
48
|
+
function pathLegend(paths, colors) {
|
|
49
|
+
const firstOf = /* @__PURE__ */ new Map();
|
|
50
|
+
paths.forEach((path, i) => {
|
|
51
|
+
if (!firstOf.has(path.name)) {
|
|
52
|
+
firstOf.set(path.name, i);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
const entries = [...firstOf].map(([name, i]) => ({ name, i }));
|
|
56
|
+
const named = entries.map((e) => labelTiers(e.name));
|
|
57
|
+
const candidates = [0, 1, 2].flatMap((t) => [
|
|
58
|
+
entries.map((_, k) => named[k][t]),
|
|
59
|
+
entries.map((e, k) => offsetTiers(e.name, named[k])[t])
|
|
60
|
+
]);
|
|
61
|
+
const distinct = candidates.find(
|
|
62
|
+
(labels2) => new Set(labels2).size === entries.length
|
|
63
|
+
);
|
|
64
|
+
const labels = distinct ?? candidates.at(-1);
|
|
65
|
+
return entries.map(({ name, i }, k) => ({
|
|
66
|
+
name,
|
|
67
|
+
label: labels[k],
|
|
68
|
+
color: colors?.[i] ?? pathCssColor(i, paths.length)
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export {
|
|
73
|
+
nameHue,
|
|
74
|
+
pathHueAt,
|
|
75
|
+
PATH_SATURATION,
|
|
76
|
+
PATH_LIGHTNESS,
|
|
77
|
+
MAX_PATH_COLORS,
|
|
78
|
+
pathColorsLegible,
|
|
79
|
+
pathCssColor,
|
|
80
|
+
pathGreyCssColor,
|
|
81
|
+
pathLegend
|
|
82
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bypassedPoints
|
|
3
|
+
} from "./chunk-4EMPBO7S.js";
|
|
4
|
+
import {
|
|
5
|
+
computeEdgeCurves,
|
|
6
|
+
edgeJoinOf,
|
|
7
|
+
routedEdgeCurves,
|
|
8
|
+
strandSides
|
|
9
|
+
} from "./chunk-2CVHXFK7.js";
|
|
10
|
+
|
|
11
|
+
// src/util/edgeCurves.ts
|
|
12
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
13
|
+
function baseEdgeCurves(nodePositions, graph, axis, deletions, version, routes, stranded = false) {
|
|
14
|
+
const { scaleX, scaleY } = axis;
|
|
15
|
+
const cached = cache.get(nodePositions);
|
|
16
|
+
if (cached?.graph === graph && cached.scaleX === scaleX && cached.scaleY === scaleY && cached.deletions === deletions && cached.routes === routes && cached.stranded === stranded && cached.version === version) {
|
|
17
|
+
return cached.curves;
|
|
18
|
+
}
|
|
19
|
+
const curves = /* @__PURE__ */ new Map();
|
|
20
|
+
for (let ei = 0; ei < graph.edges.length; ei++) {
|
|
21
|
+
const edge = graph.edges[ei];
|
|
22
|
+
const from = nodePositions[edge.from];
|
|
23
|
+
const to = nodePositions[edge.to];
|
|
24
|
+
if (from?.length && to?.length) {
|
|
25
|
+
const bypassed = deletions?.get(ei);
|
|
26
|
+
const route = bypassed && routes?.[ei];
|
|
27
|
+
curves.set(
|
|
28
|
+
ei,
|
|
29
|
+
route ? routedEdgeCurves(
|
|
30
|
+
from,
|
|
31
|
+
to,
|
|
32
|
+
route,
|
|
33
|
+
stranded ? strandSides(edge) : void 0
|
|
34
|
+
) : computeEdgeCurves(
|
|
35
|
+
from,
|
|
36
|
+
to,
|
|
37
|
+
edgeJoinOf(edge, stranded),
|
|
38
|
+
0,
|
|
39
|
+
0,
|
|
40
|
+
axis,
|
|
41
|
+
bypassed ? bypassedPoints(nodePositions, bypassed) : []
|
|
42
|
+
)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
cache.set(nodePositions, {
|
|
47
|
+
graph,
|
|
48
|
+
scaleX,
|
|
49
|
+
scaleY,
|
|
50
|
+
deletions,
|
|
51
|
+
routes,
|
|
52
|
+
stranded,
|
|
53
|
+
version,
|
|
54
|
+
curves
|
|
55
|
+
});
|
|
56
|
+
return curves;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
baseEdgeCurves
|
|
61
|
+
};
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EdgeSpatialIndex,
|
|
3
|
+
SpatialIndex
|
|
4
|
+
} from "./chunk-MREQ7GIA.js";
|
|
5
|
+
import {
|
|
6
|
+
pathRibbonOffsets,
|
|
7
|
+
translateCurves,
|
|
8
|
+
yToXOf
|
|
9
|
+
} from "./chunk-2CVHXFK7.js";
|
|
10
|
+
|
|
11
|
+
// src/util/hitDetection.ts
|
|
12
|
+
function distanceToSegment(px, py, x1, y1, x2, y2) {
|
|
13
|
+
const dx = x2 - x1;
|
|
14
|
+
const dy = y2 - y1;
|
|
15
|
+
const lenSq = dx * dx + dy * dy;
|
|
16
|
+
if (lenSq === 0) {
|
|
17
|
+
return Math.hypot(px - x1, py - y1);
|
|
18
|
+
}
|
|
19
|
+
const t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
|
|
20
|
+
return Math.hypot(px - (x1 + t * dx), py - (y1 + t * dy));
|
|
21
|
+
}
|
|
22
|
+
function distanceToCubicBezier(px, py, x1, y1, cx1, cy1, cx2, cy2, x2, y2) {
|
|
23
|
+
let minDist = Infinity;
|
|
24
|
+
const samples = 20;
|
|
25
|
+
let lastX = x1;
|
|
26
|
+
let lastY = y1;
|
|
27
|
+
for (let i = 1; i <= samples; i++) {
|
|
28
|
+
const t = i / samples;
|
|
29
|
+
const u = 1 - t;
|
|
30
|
+
const bx = u * u * u * x1 + 3 * u * u * t * cx1 + 3 * u * t * t * cx2 + t * t * t * x2;
|
|
31
|
+
const by = u * u * u * y1 + 3 * u * u * t * cy1 + 3 * u * t * t * cy2 + t * t * t * y2;
|
|
32
|
+
minDist = Math.min(minDist, distanceToSegment(px, py, lastX, lastY, bx, by));
|
|
33
|
+
lastX = bx;
|
|
34
|
+
lastY = by;
|
|
35
|
+
}
|
|
36
|
+
return minDist;
|
|
37
|
+
}
|
|
38
|
+
function distanceToBezierCurve(px, py, c) {
|
|
39
|
+
return distanceToCubicBezier(
|
|
40
|
+
px,
|
|
41
|
+
py,
|
|
42
|
+
c.x0,
|
|
43
|
+
c.y0,
|
|
44
|
+
c.cx0,
|
|
45
|
+
c.cy0,
|
|
46
|
+
c.cx1,
|
|
47
|
+
c.cy1,
|
|
48
|
+
c.x1,
|
|
49
|
+
c.y1
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
function distanceToEdgeCurves(px, py, curves, yToX = 1) {
|
|
53
|
+
let minDist = Infinity;
|
|
54
|
+
for (const c of curves) {
|
|
55
|
+
const d = distanceToBezierCurve(
|
|
56
|
+
px,
|
|
57
|
+
py,
|
|
58
|
+
yToX === 1 ? c : {
|
|
59
|
+
x0: c.x0,
|
|
60
|
+
y0: c.y0 * yToX,
|
|
61
|
+
cx0: c.cx0,
|
|
62
|
+
cy0: c.cy0 * yToX,
|
|
63
|
+
cx1: c.cx1,
|
|
64
|
+
cy1: c.cy1 * yToX,
|
|
65
|
+
x1: c.x1,
|
|
66
|
+
y1: c.y1 * yToX
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
if (d < minDist) {
|
|
70
|
+
minDist = d;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return minDist;
|
|
74
|
+
}
|
|
75
|
+
var nodeCache = /* @__PURE__ */ new WeakMap();
|
|
76
|
+
function getSpatialIndex(nodePositions, version) {
|
|
77
|
+
const cached = nodeCache.get(nodePositions);
|
|
78
|
+
if (cached?.version === version) {
|
|
79
|
+
return cached.index;
|
|
80
|
+
}
|
|
81
|
+
const index = new SpatialIndex(nodePositions);
|
|
82
|
+
nodeCache.set(nodePositions, { version, index });
|
|
83
|
+
return index;
|
|
84
|
+
}
|
|
85
|
+
var edgeCache = /* @__PURE__ */ new WeakMap();
|
|
86
|
+
function getEdgeSpatialIndex(nodePositions, graph, drawPaths, axis, version, deletions, routes, stranded = false) {
|
|
87
|
+
const cached = edgeCache.get(nodePositions);
|
|
88
|
+
if (cached?.graph === graph && cached.drawPaths === drawPaths && cached.scaleX === axis.scaleX && cached.scaleY === axis.scaleY && cached.version === version && cached.deletions === deletions && cached.routes === routes && cached.stranded === stranded) {
|
|
89
|
+
return cached.index;
|
|
90
|
+
}
|
|
91
|
+
const index = new EdgeSpatialIndex(
|
|
92
|
+
nodePositions,
|
|
93
|
+
graph,
|
|
94
|
+
drawPaths,
|
|
95
|
+
axis,
|
|
96
|
+
void 0,
|
|
97
|
+
deletions,
|
|
98
|
+
version,
|
|
99
|
+
routes,
|
|
100
|
+
stranded
|
|
101
|
+
);
|
|
102
|
+
edgeCache.set(nodePositions, {
|
|
103
|
+
graph,
|
|
104
|
+
drawPaths,
|
|
105
|
+
scaleX: axis.scaleX,
|
|
106
|
+
scaleY: axis.scaleY,
|
|
107
|
+
version,
|
|
108
|
+
index,
|
|
109
|
+
deletions,
|
|
110
|
+
routes,
|
|
111
|
+
stranded
|
|
112
|
+
});
|
|
113
|
+
return index;
|
|
114
|
+
}
|
|
115
|
+
var MIN_NODE_REACH_PX = 5;
|
|
116
|
+
function findHoveredNode(nodePositions, graphX, graphY, axis, version = 0, ink) {
|
|
117
|
+
const yToX = yToXOf(axis);
|
|
118
|
+
const index = getSpatialIndex(nodePositions, version);
|
|
119
|
+
const reach = (px) => Math.max(MIN_NODE_REACH_PX, px) / axis.scaleX;
|
|
120
|
+
const queryReach = reach(ink?.maxHalfWidthPx ?? 0);
|
|
121
|
+
const candidates = index.query(graphX, graphY, queryReach, queryReach / yToX);
|
|
122
|
+
let hovered = null;
|
|
123
|
+
let best = Infinity;
|
|
124
|
+
for (const { nodeId, segmentIdx } of candidates) {
|
|
125
|
+
const segments = nodePositions[nodeId];
|
|
126
|
+
const dist = distanceToSegment(
|
|
127
|
+
graphX,
|
|
128
|
+
graphY * yToX,
|
|
129
|
+
segments[segmentIdx].x,
|
|
130
|
+
segments[segmentIdx].y * yToX,
|
|
131
|
+
segments[segmentIdx + 1].x,
|
|
132
|
+
segments[segmentIdx + 1].y * yToX
|
|
133
|
+
);
|
|
134
|
+
if (dist < best && dist < reach(ink?.halfWidthPx(nodeId) ?? 0)) {
|
|
135
|
+
best = dist;
|
|
136
|
+
hovered = nodeId;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return hovered;
|
|
140
|
+
}
|
|
141
|
+
function segmentMeetsBox(a, b, box) {
|
|
142
|
+
const dx = b.x - a.x;
|
|
143
|
+
const dy = b.y - a.y;
|
|
144
|
+
let t0 = 0;
|
|
145
|
+
let t1 = 1;
|
|
146
|
+
for (const [p, q] of [
|
|
147
|
+
[-dx, a.x - box.x0],
|
|
148
|
+
[dx, box.x1 - a.x],
|
|
149
|
+
[-dy, a.y - box.y0],
|
|
150
|
+
[dy, box.y1 - a.y]
|
|
151
|
+
]) {
|
|
152
|
+
if (p === 0) {
|
|
153
|
+
if (q < 0) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
} else if (p < 0) {
|
|
157
|
+
t0 = Math.max(t0, q / p);
|
|
158
|
+
} else {
|
|
159
|
+
t1 = Math.min(t1, q / p);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return t0 <= t1;
|
|
163
|
+
}
|
|
164
|
+
function nodesUnderBox(nodePositions, t, version = 0) {
|
|
165
|
+
const index = getSpatialIndex(nodePositions, version);
|
|
166
|
+
const screen = (p) => ({
|
|
167
|
+
x: p.x * t.scaleX + t.translateX,
|
|
168
|
+
y: p.y * t.scaleY + t.translateY
|
|
169
|
+
});
|
|
170
|
+
return (box) => {
|
|
171
|
+
const hits = /* @__PURE__ */ new Set();
|
|
172
|
+
const candidates = index.query(
|
|
173
|
+
((box.x0 + box.x1) / 2 - t.translateX) / t.scaleX,
|
|
174
|
+
((box.y0 + box.y1) / 2 - t.translateY) / t.scaleY,
|
|
175
|
+
Math.abs((box.x1 - box.x0) / 2 / t.scaleX),
|
|
176
|
+
Math.abs((box.y1 - box.y0) / 2 / t.scaleY)
|
|
177
|
+
);
|
|
178
|
+
for (const { nodeId, segmentIdx } of candidates) {
|
|
179
|
+
const line = nodePositions[nodeId];
|
|
180
|
+
if (!hits.has(nodeId) && segmentMeetsBox(
|
|
181
|
+
screen(line[segmentIdx]),
|
|
182
|
+
screen(line[segmentIdx + 1]),
|
|
183
|
+
box
|
|
184
|
+
)) {
|
|
185
|
+
hits.add(nodeId);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return hits;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function findHoveredEdge(nodePositions, graph, graphX, graphY, axis, drawPaths, version = 0, deletions, hiddenEdges, deletionRoutes, stranded = false) {
|
|
192
|
+
const yToX = yToXOf(axis);
|
|
193
|
+
const edgeThreshold = 10 / axis.scaleX;
|
|
194
|
+
const edgeIndex = getEdgeSpatialIndex(
|
|
195
|
+
nodePositions,
|
|
196
|
+
graph,
|
|
197
|
+
drawPaths,
|
|
198
|
+
axis,
|
|
199
|
+
version,
|
|
200
|
+
deletions,
|
|
201
|
+
deletionRoutes,
|
|
202
|
+
stranded
|
|
203
|
+
);
|
|
204
|
+
const candidates = edgeIndex.query(
|
|
205
|
+
graphX,
|
|
206
|
+
graphY,
|
|
207
|
+
edgeThreshold,
|
|
208
|
+
edgeThreshold / yToX
|
|
209
|
+
);
|
|
210
|
+
let hovered = null;
|
|
211
|
+
let best = edgeThreshold;
|
|
212
|
+
for (const edgeIdx of candidates) {
|
|
213
|
+
const edge = graph.edges[edgeIdx];
|
|
214
|
+
const fromSegments = nodePositions[edge.from];
|
|
215
|
+
const toSegments = nodePositions[edge.to];
|
|
216
|
+
if (!fromSegments?.length || !toSegments?.length || hiddenEdges?.has(edgeIdx)) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const numPaths = edge.pathIds?.length ?? 0;
|
|
220
|
+
const baseCurves = edgeIndex.getCurves(edgeIdx);
|
|
221
|
+
if (!baseCurves) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
let dist;
|
|
225
|
+
if (!drawPaths || numPaths === 0) {
|
|
226
|
+
dist = distanceToEdgeCurves(graphX, graphY * yToX, baseCurves, yToX);
|
|
227
|
+
} else {
|
|
228
|
+
let minDist = Infinity;
|
|
229
|
+
for (const offset of pathRibbonOffsets(
|
|
230
|
+
fromSegments,
|
|
231
|
+
toSegments,
|
|
232
|
+
numPaths,
|
|
233
|
+
axis
|
|
234
|
+
)) {
|
|
235
|
+
const curves = offset.x === 0 && offset.y === 0 ? baseCurves : translateCurves(baseCurves, offset.x, offset.y);
|
|
236
|
+
const d = distanceToEdgeCurves(graphX, graphY * yToX, curves, yToX);
|
|
237
|
+
if (d < minDist) {
|
|
238
|
+
minDist = d;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
dist = minDist;
|
|
242
|
+
}
|
|
243
|
+
if (dist < best) {
|
|
244
|
+
best = dist;
|
|
245
|
+
hovered = edgeIdx;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return hovered;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export {
|
|
252
|
+
distanceToSegment,
|
|
253
|
+
distanceToCubicBezier,
|
|
254
|
+
findHoveredNode,
|
|
255
|
+
nodesUnderBox,
|
|
256
|
+
findHoveredEdge
|
|
257
|
+
};
|