@jbrowse/bandage-core 6.0.1 → 6.0.2
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-3C2LGJKY.js +108 -0
- package/dist/chunks/chunk-3PLYZLYV.js +513 -0
- package/dist/chunks/chunk-3S3YVOJ7.js +69 -0
- package/dist/chunks/chunk-3WHKR5TY.js +726 -0
- package/dist/chunks/chunk-47DYCZOK.js +115 -0
- package/dist/chunks/chunk-4S47XRYA.js +149 -0
- package/dist/chunks/chunk-527AI7CP.js +122 -0
- package/dist/chunks/chunk-57ZIXHZA.js +90 -0
- package/dist/chunks/chunk-5QT6NPSO.js +40 -0
- package/dist/chunks/chunk-6JEWNXCQ.js +90 -0
- package/dist/chunks/chunk-6Q6HFBJT.js +170 -0
- package/dist/chunks/chunk-6SBUFFTB.js +97 -0
- package/dist/chunks/chunk-6YRRNX2P.js +61 -0
- package/dist/chunks/chunk-72N5DJHZ.js +155 -0
- package/dist/chunks/chunk-73RSLGZT.js +73 -0
- package/dist/chunks/chunk-7CHJKABZ.js +82 -0
- package/dist/chunks/chunk-7XHHVYG7.js +13 -0
- package/dist/chunks/chunk-AB35TJST.js +67 -0
- package/dist/chunks/chunk-AE3UQZY3.js +43 -0
- package/dist/chunks/chunk-BJV2W4KZ.js +257 -0
- package/dist/chunks/chunk-BPLIXMH7.js +52 -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-CPMHA4W5.js +6 -0
- package/dist/chunks/chunk-CR7EGAFE.js +241 -0
- package/dist/chunks/chunk-CZ54VG2H.js +431 -0
- package/dist/chunks/chunk-D3M7SBKL.js +210 -0
- package/dist/chunks/chunk-EJ5OZTCK.js +52 -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-GQ2AOHU4.js +112 -0
- package/dist/chunks/chunk-HB6PDCOU.js +177 -0
- package/dist/chunks/chunk-HCANBJDD.js +326 -0
- package/dist/chunks/chunk-HIJVRRYE.js +174 -0
- package/dist/chunks/chunk-HNQPEMYE.js +297 -0
- package/dist/chunks/chunk-IGVV37VI.js +60 -0
- package/dist/chunks/chunk-IMW3AK27.js +102 -0
- package/dist/chunks/chunk-JKJWSY4V.js +32 -0
- package/dist/chunks/chunk-KCW7L3NV.js +6 -0
- package/dist/chunks/chunk-KQFOCDFY.js +73 -0
- package/dist/chunks/chunk-L3O7AWLI.js +52 -0
- package/dist/chunks/chunk-L5RYUPZI.js +53 -0
- package/dist/chunks/chunk-LOEA5IHZ.js +71 -0
- package/dist/chunks/chunk-LTUWJKHD.js +112 -0
- package/dist/chunks/chunk-LVZZH2NI.js +181 -0
- package/dist/chunks/chunk-MFKORG3F.js +44 -0
- package/dist/chunks/chunk-MHGHYHTL.js +205 -0
- package/dist/chunks/chunk-MI35EUIE.js +76 -0
- package/dist/chunks/chunk-MNUSQCLG.js +29 -0
- package/dist/chunks/chunk-NNODIW4U.js +26 -0
- package/dist/chunks/chunk-P7B3GDAY.js +232 -0
- package/dist/chunks/chunk-PBENCR5L.js +60 -0
- package/dist/chunks/chunk-PUG7LXDF.js +70 -0
- package/dist/chunks/chunk-Q3KCTA4N.js +202 -0
- package/dist/chunks/chunk-QCYM44UL.js +476 -0
- package/dist/chunks/chunk-QYBZZI5H.js +0 -0
- package/dist/chunks/chunk-RAKOB6IV.js +169 -0
- package/dist/chunks/chunk-RVS2QCOX.js +135 -0
- package/dist/chunks/chunk-SDGM243E.js +190 -0
- package/dist/chunks/chunk-ST2IBUAN.js +119 -0
- package/dist/chunks/chunk-SV27AJCC.js +101 -0
- package/dist/chunks/chunk-SZNZ2T6D.js +198 -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-VKB57ZYE.js +218 -0
- package/dist/chunks/chunk-VZQ4FKLR.js +38 -0
- package/dist/chunks/chunk-WOO7QYKB.js +121 -0
- package/dist/chunks/chunk-WSIFAZU7.js +132 -0
- package/dist/chunks/chunk-X32PABB7.js +117 -0
- package/dist/chunks/chunk-YJQ7CT7F.js +23 -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,29 @@
|
|
|
1
|
+
// src/renderer/colorBits.ts
|
|
2
|
+
import {
|
|
3
|
+
abgrAlpha,
|
|
4
|
+
abgrBlue,
|
|
5
|
+
abgrGreen,
|
|
6
|
+
abgrRed,
|
|
7
|
+
packAbgr
|
|
8
|
+
} from "@jbrowse/core/util/colorBits";
|
|
9
|
+
function fadeAbgr(c, alpha) {
|
|
10
|
+
return packAbgr(
|
|
11
|
+
abgrRed(c),
|
|
12
|
+
abgrGreen(c),
|
|
13
|
+
abgrBlue(c),
|
|
14
|
+
Math.round(abgrAlpha(c) * alpha)
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
function brightenAbgr(c, factor) {
|
|
18
|
+
return packAbgr(
|
|
19
|
+
Math.min(255, Math.round(abgrRed(c) * factor)),
|
|
20
|
+
Math.min(255, Math.round(abgrGreen(c) * factor)),
|
|
21
|
+
Math.min(255, Math.round(abgrBlue(c) * factor)),
|
|
22
|
+
abgrAlpha(c)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
fadeAbgr,
|
|
28
|
+
brightenAbgr
|
|
29
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/el.ts
|
|
2
|
+
function el(tag, attrs = {}, ...children) {
|
|
3
|
+
return {
|
|
4
|
+
tag,
|
|
5
|
+
attrs,
|
|
6
|
+
children: children.filter((c) => !!c || c === "")
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function escapeText(s) {
|
|
10
|
+
return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
11
|
+
}
|
|
12
|
+
function escapeAttr(s) {
|
|
13
|
+
return escapeText(s).replaceAll('"', """);
|
|
14
|
+
}
|
|
15
|
+
function serializeEl(node) {
|
|
16
|
+
if (typeof node === "string") {
|
|
17
|
+
return escapeText(node);
|
|
18
|
+
}
|
|
19
|
+
const attrs = Object.entries(node.attrs).filter(([, v]) => v !== void 0).map(([k, v]) => ` ${k}="${escapeAttr(String(v))}"`).join("");
|
|
20
|
+
return `<${node.tag}${attrs}>${node.children.map(serializeEl).join("")}</${node.tag}>`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
el,
|
|
25
|
+
serializeEl
|
|
26
|
+
};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canonicalStrand,
|
|
3
|
+
readColor,
|
|
4
|
+
stepName,
|
|
5
|
+
tubeMapReads
|
|
6
|
+
} from "./chunk-TEDQJPMI.js";
|
|
7
|
+
import {
|
|
8
|
+
pathCssColor,
|
|
9
|
+
pathGreyCssColor
|
|
10
|
+
} from "./chunk-7CHJKABZ.js";
|
|
11
|
+
import {
|
|
12
|
+
pathOrigin
|
|
13
|
+
} from "./chunk-ST2IBUAN.js";
|
|
14
|
+
import {
|
|
15
|
+
isBackbone
|
|
16
|
+
} from "./chunk-FYQWPEEY.js";
|
|
17
|
+
|
|
18
|
+
// src/layout/tubeMapLayout.ts
|
|
19
|
+
import { layoutTubeMap } from "@gmod/tubemap-core";
|
|
20
|
+
function visitStrands(graph) {
|
|
21
|
+
const strands = /* @__PURE__ */ new Map();
|
|
22
|
+
for (const [segment, visits] of graph.pathVisits ?? []) {
|
|
23
|
+
for (const visit of visits) {
|
|
24
|
+
const key = `${visit.path} ${segment}`;
|
|
25
|
+
const list = strands.get(key);
|
|
26
|
+
if (list) {
|
|
27
|
+
list.push(visit.strand);
|
|
28
|
+
} else {
|
|
29
|
+
strands.set(key, [visit.strand]);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return strands;
|
|
34
|
+
}
|
|
35
|
+
function tubeMapTracks(graph) {
|
|
36
|
+
const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
37
|
+
const strands = visitStrands(graph);
|
|
38
|
+
const paths = (graph.paths ?? []).map((path, index) => ({ path, index }));
|
|
39
|
+
const reference = paths.findIndex(
|
|
40
|
+
(p) => pathOrigin(p.path.name).name === graph.referencePath
|
|
41
|
+
);
|
|
42
|
+
if (reference > 0) {
|
|
43
|
+
paths.unshift(...paths.splice(reference, 1));
|
|
44
|
+
}
|
|
45
|
+
return paths.flatMap(({ path, index }) => {
|
|
46
|
+
const visitName = pathOrigin(path.name).name;
|
|
47
|
+
const seen = /* @__PURE__ */ new Map();
|
|
48
|
+
const sequence = path.nodeIds.flatMap((id) => {
|
|
49
|
+
const node = nodeById.get(id);
|
|
50
|
+
if (!node) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
const k = seen.get(node.name) ?? 0;
|
|
54
|
+
seen.set(node.name, k + 1);
|
|
55
|
+
const strand = strands.get(`${visitName} ${node.name}`)?.[k] ?? canonicalStrand(node);
|
|
56
|
+
return [stepName(node, strand)];
|
|
57
|
+
});
|
|
58
|
+
return sequence.length > 0 ? [
|
|
59
|
+
{
|
|
60
|
+
id: index,
|
|
61
|
+
name: path.name,
|
|
62
|
+
sequence,
|
|
63
|
+
sourceTrackID: 0,
|
|
64
|
+
type: "haplotype"
|
|
65
|
+
}
|
|
66
|
+
] : [];
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function tubeMapNodes(graph) {
|
|
70
|
+
return graph.nodes.map((n) => ({
|
|
71
|
+
name: n.id,
|
|
72
|
+
seq: "",
|
|
73
|
+
sequenceLength: Math.max(1, n.length)
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
function hasTubeMapPaths(graph) {
|
|
77
|
+
return (graph.paths?.length ?? 0) > 0;
|
|
78
|
+
}
|
|
79
|
+
var STACK_PX = 600;
|
|
80
|
+
var MIN_TUBE_PX = 3;
|
|
81
|
+
var MAX_TUBE_PX = 15;
|
|
82
|
+
function tubeWidth(trackCount) {
|
|
83
|
+
return Math.max(
|
|
84
|
+
MIN_TUBE_PX,
|
|
85
|
+
Math.min(MAX_TUBE_PX, Math.floor(STACK_PX / Math.max(1, trackCount)))
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
function runTubeMap(graph) {
|
|
89
|
+
const paths = graph.paths ?? [];
|
|
90
|
+
const tracks = tubeMapTracks(graph);
|
|
91
|
+
const reads = graph.reads ? tubeMapReads(graph, graph.reads, paths.length) : [];
|
|
92
|
+
const tubeColor = reads.length > 0 ? pathGreyCssColor : pathCssColor;
|
|
93
|
+
const pathColors = paths.map((_, i) => tubeColor(i, paths.length));
|
|
94
|
+
const layout = layoutTubeMap(tubeMapNodes(graph), tracks, reads, {
|
|
95
|
+
nodeWidthOption: "compressed",
|
|
96
|
+
trackWidth: tubeWidth(tracks.length),
|
|
97
|
+
trackColor: (track) => track.type === "read" ? readColor(track) : pathColors[track.id]
|
|
98
|
+
});
|
|
99
|
+
return layout ? { layout, pathColors } : void 0;
|
|
100
|
+
}
|
|
101
|
+
function drawnNodes(layout) {
|
|
102
|
+
const nodes = [];
|
|
103
|
+
layout.nodes.forEach((node) => {
|
|
104
|
+
if (node.order >= 0) {
|
|
105
|
+
nodes.push(node);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return nodes;
|
|
109
|
+
}
|
|
110
|
+
function centreline(node, yOffset, x0, x1) {
|
|
111
|
+
const y = node.y + node.contentHeight / 2 - yOffset;
|
|
112
|
+
return [
|
|
113
|
+
{ x: x0, y },
|
|
114
|
+
{ x: x1, y }
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
function extentOf(layout, yOffset) {
|
|
118
|
+
return {
|
|
119
|
+
minY: layout.bounds.minY - yOffset,
|
|
120
|
+
maxY: layout.bounds.maxY - yOffset
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function tubeMapLayout(graph) {
|
|
124
|
+
const run = hasTubeMapPaths(graph) ? runTubeMap(graph) : void 0;
|
|
125
|
+
if (!run) {
|
|
126
|
+
return void 0;
|
|
127
|
+
}
|
|
128
|
+
const { layout, pathColors } = run;
|
|
129
|
+
const yOffset = layout.bounds.minY;
|
|
130
|
+
const nodePositions = {};
|
|
131
|
+
for (const node of drawnNodes(layout)) {
|
|
132
|
+
nodePositions[node.name] = centreline(
|
|
133
|
+
node,
|
|
134
|
+
yOffset,
|
|
135
|
+
node.x,
|
|
136
|
+
node.x + node.pixelWidth
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
const { minY, maxY } = extentOf(layout, yOffset);
|
|
140
|
+
return {
|
|
141
|
+
nodePositions,
|
|
142
|
+
tubeMap: { layout, yOffset, pathColors },
|
|
143
|
+
extent: {
|
|
144
|
+
minX: layout.bounds.minX,
|
|
145
|
+
maxX: layout.bounds.maxX,
|
|
146
|
+
minY,
|
|
147
|
+
maxY
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function tubeMapColumns(graph, layout) {
|
|
152
|
+
const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
153
|
+
const byOrder = /* @__PURE__ */ new Map();
|
|
154
|
+
for (const node of drawnNodes(layout)) {
|
|
155
|
+
const list = byOrder.get(node.order);
|
|
156
|
+
if (list) {
|
|
157
|
+
list.push(node);
|
|
158
|
+
} else {
|
|
159
|
+
byOrder.set(node.order, [node]);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const columns = [];
|
|
163
|
+
let end = -Infinity;
|
|
164
|
+
for (const order of [...byOrder.keys()].sort((a, b) => a - b)) {
|
|
165
|
+
const nodes = byOrder.get(order);
|
|
166
|
+
let bp0 = Infinity;
|
|
167
|
+
let bp1 = -Infinity;
|
|
168
|
+
for (const node of nodes) {
|
|
169
|
+
const graphNode = nodeById.get(node.name);
|
|
170
|
+
if (graphNode && isBackbone(graphNode)) {
|
|
171
|
+
bp0 = Math.min(bp0, graphNode.stable.start);
|
|
172
|
+
bp1 = Math.max(bp1, graphNode.stable.start + node.sequenceLength);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (bp0 === Infinity) {
|
|
176
|
+
bp0 = bp1 = end;
|
|
177
|
+
}
|
|
178
|
+
if (bp0 < end) {
|
|
179
|
+
bp0 = end;
|
|
180
|
+
bp1 = Math.max(end, bp1);
|
|
181
|
+
}
|
|
182
|
+
end = Math.max(end, bp1);
|
|
183
|
+
columns.push({
|
|
184
|
+
order,
|
|
185
|
+
x0: Math.min(...nodes.map((n) => n.x)),
|
|
186
|
+
x1: Math.max(...nodes.map((n) => n.x + n.pixelWidth)),
|
|
187
|
+
bp0,
|
|
188
|
+
bp1
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
const first = columns.find((c) => c.bp0 !== -Infinity);
|
|
192
|
+
for (const column of columns) {
|
|
193
|
+
if (column.bp0 === -Infinity) {
|
|
194
|
+
column.bp0 = column.bp1 = first?.bp0 ?? 0;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return columns;
|
|
198
|
+
}
|
|
199
|
+
function pathsReachBackbone(graph) {
|
|
200
|
+
return graph.nodes.some(isBackbone) && hasTubeMapPaths(graph);
|
|
201
|
+
}
|
|
202
|
+
function tubeMapReferenceLayout(graph) {
|
|
203
|
+
const run = pathsReachBackbone(graph) ? runTubeMap(graph) : void 0;
|
|
204
|
+
if (!run) {
|
|
205
|
+
return void 0;
|
|
206
|
+
}
|
|
207
|
+
const { layout, pathColors } = run;
|
|
208
|
+
const columns = tubeMapColumns(graph, layout);
|
|
209
|
+
const byOrder = new Map(columns.map((c) => [c.order, c]));
|
|
210
|
+
const yOffset = layout.bounds.minY;
|
|
211
|
+
const nodePositions = {};
|
|
212
|
+
for (const node of drawnNodes(layout)) {
|
|
213
|
+
const column = byOrder.get(node.order);
|
|
214
|
+
nodePositions[node.name] = centreline(node, yOffset, column.bp0, column.bp1);
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
nodePositions,
|
|
218
|
+
tubeMap: { layout, yOffset, columns, pathColors },
|
|
219
|
+
referenceAxis: true,
|
|
220
|
+
pixelRows: true,
|
|
221
|
+
extent: extentOf(layout, yOffset)
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export {
|
|
226
|
+
visitStrands,
|
|
227
|
+
tubeMapTracks,
|
|
228
|
+
hasTubeMapPaths,
|
|
229
|
+
tubeWidth,
|
|
230
|
+
tubeMapLayout,
|
|
231
|
+
tubeMapReferenceLayout
|
|
232
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// src/nodeWidths.ts
|
|
2
|
+
var NODE_WIDTHS = [
|
|
3
|
+
{
|
|
4
|
+
value: "depth",
|
|
5
|
+
label: "By depth",
|
|
6
|
+
description: "Thicker the more paths carry a node, on the square root of its depth against the mean. A graph with one depth everywhere draws uniform."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
value: "uniform",
|
|
10
|
+
label: "Uniform",
|
|
11
|
+
description: "Every node the same thickness."
|
|
12
|
+
}
|
|
13
|
+
];
|
|
14
|
+
var NODE_WIDTH_VALUES = NODE_WIDTHS.map((w) => w.value);
|
|
15
|
+
var MIN_FACTOR = 0.3;
|
|
16
|
+
var MAX_FACTOR = 3;
|
|
17
|
+
function meanDepth(graph) {
|
|
18
|
+
let weighted = 0;
|
|
19
|
+
let length = 0;
|
|
20
|
+
for (const node of graph.nodes) {
|
|
21
|
+
weighted += node.depth * node.length;
|
|
22
|
+
length += node.length;
|
|
23
|
+
}
|
|
24
|
+
return length > 0 ? weighted / length : 0;
|
|
25
|
+
}
|
|
26
|
+
function depthWidthFactor(node, mean) {
|
|
27
|
+
if (!(mean > 0) || !(node.depth > 0)) {
|
|
28
|
+
return 1;
|
|
29
|
+
}
|
|
30
|
+
return Math.min(
|
|
31
|
+
MAX_FACTOR,
|
|
32
|
+
Math.max(MIN_FACTOR, Math.sqrt(node.depth / mean))
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
function nodeWidthPx(node, contigThickness, nodeWidth, mean) {
|
|
36
|
+
return contigThickness * (nodeWidth === "depth" ? depthWidthFactor(node, mean) : 1);
|
|
37
|
+
}
|
|
38
|
+
function maxNodeWidthPx(contigThickness, nodeWidth) {
|
|
39
|
+
return contigThickness * (nodeWidth === "depth" ? MAX_FACTOR : 1);
|
|
40
|
+
}
|
|
41
|
+
function nodeInk(graph, nodeById, contigThickness, nodeWidth) {
|
|
42
|
+
const mean = graph ? meanDepth(graph) : 0;
|
|
43
|
+
return {
|
|
44
|
+
maxHalfWidthPx: maxNodeWidthPx(contigThickness, nodeWidth) / 2,
|
|
45
|
+
halfWidthPx: (id) => {
|
|
46
|
+
const node = nodeById?.get(id);
|
|
47
|
+
return node ? nodeWidthPx(node, contigThickness, nodeWidth, mean) / 2 : contigThickness / 2;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
NODE_WIDTHS,
|
|
54
|
+
NODE_WIDTH_VALUES,
|
|
55
|
+
meanDepth,
|
|
56
|
+
depthWidthFactor,
|
|
57
|
+
nodeWidthPx,
|
|
58
|
+
maxNodeWidthPx,
|
|
59
|
+
nodeInk
|
|
60
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isBackbone
|
|
3
|
+
} from "./chunk-FYQWPEEY.js";
|
|
4
|
+
|
|
5
|
+
// src/layout/orientToReference.ts
|
|
6
|
+
function orientToReference(graph, positions) {
|
|
7
|
+
const backbone = graph.nodes.filter(
|
|
8
|
+
(node) => isBackbone(node) && (positions[node.id]?.length ?? 0) > 0
|
|
9
|
+
);
|
|
10
|
+
if (backbone.length < 2) {
|
|
11
|
+
return positions;
|
|
12
|
+
}
|
|
13
|
+
const points = backbone.map((node) => {
|
|
14
|
+
const segments = positions[node.id];
|
|
15
|
+
const mid = segments[Math.floor(segments.length / 2)];
|
|
16
|
+
return {
|
|
17
|
+
x: mid.x,
|
|
18
|
+
y: mid.y,
|
|
19
|
+
r: node.stable.start + node.length / 2,
|
|
20
|
+
w: node.length
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
const weight = points.reduce((sum, p) => sum + p.w, 0);
|
|
24
|
+
const cx = points.reduce((sum, p) => sum + p.x * p.w, 0) / weight;
|
|
25
|
+
const cy = points.reduce((sum, p) => sum + p.y * p.w, 0) / weight;
|
|
26
|
+
const cr = points.reduce((sum, p) => sum + p.r * p.w, 0) / weight;
|
|
27
|
+
let sxr = 0;
|
|
28
|
+
let syr = 0;
|
|
29
|
+
for (const p of points) {
|
|
30
|
+
sxr += p.w * (p.x - cx) * (p.r - cr);
|
|
31
|
+
syr += p.w * (p.y - cy) * (p.r - cr);
|
|
32
|
+
}
|
|
33
|
+
const angle = -Math.atan2(syr, sxr);
|
|
34
|
+
const c = Math.cos(angle);
|
|
35
|
+
const s = Math.sin(angle);
|
|
36
|
+
const out = {};
|
|
37
|
+
for (const [id, segments] of Object.entries(positions)) {
|
|
38
|
+
out[id] = segments.map((p) => {
|
|
39
|
+
const x = p.x - cx;
|
|
40
|
+
const y = p.y - cy;
|
|
41
|
+
return { x: cx + c * x - s * y, y: cy + s * x + c * y };
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
let below = 0;
|
|
45
|
+
let above = 0;
|
|
46
|
+
for (const node of graph.nodes) {
|
|
47
|
+
if (isBackbone(node)) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
for (const p of out[node.id] ?? []) {
|
|
51
|
+
if (p.y > cy) {
|
|
52
|
+
below++;
|
|
53
|
+
} else {
|
|
54
|
+
above++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (above > below) {
|
|
59
|
+
for (const segments of Object.values(out)) {
|
|
60
|
+
for (const p of segments) {
|
|
61
|
+
p.y = 2 * cy - p.y;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
orientToReference
|
|
70
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatBp
|
|
3
|
+
} from "./chunk-HCANBJDD.js";
|
|
4
|
+
import {
|
|
5
|
+
isBackbone
|
|
6
|
+
} from "./chunk-FYQWPEEY.js";
|
|
7
|
+
|
|
8
|
+
// src/tubeMap/axis.ts
|
|
9
|
+
function referenceBoxes(graph, layout) {
|
|
10
|
+
const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
11
|
+
const byRefName = /* @__PURE__ */ new Map();
|
|
12
|
+
layout.nodes.forEach((node) => {
|
|
13
|
+
const graphNode = nodeById.get(node.name);
|
|
14
|
+
if (node.order >= 0 && graphNode && isBackbone(graphNode)) {
|
|
15
|
+
const { refName, start } = graphNode.stable;
|
|
16
|
+
const boxes = byRefName.get(refName) ?? byRefName.set(refName, []).get(refName);
|
|
17
|
+
boxes.push({
|
|
18
|
+
bp0: start,
|
|
19
|
+
bp1: start + node.sequenceLength,
|
|
20
|
+
x0: node.x,
|
|
21
|
+
x1: node.x + node.pixelWidth
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
for (const boxes of byRefName.values()) {
|
|
26
|
+
boxes.sort((a, b) => a.bp0 - b.bp0);
|
|
27
|
+
}
|
|
28
|
+
return byRefName;
|
|
29
|
+
}
|
|
30
|
+
function tubeX(boxes, bp, isEnd = false) {
|
|
31
|
+
let lo = 0;
|
|
32
|
+
let hi = boxes.length - 1;
|
|
33
|
+
while (lo < hi) {
|
|
34
|
+
const mid = lo + hi >> 1;
|
|
35
|
+
const bp1 = boxes[mid].bp1;
|
|
36
|
+
if (isEnd ? bp1 < bp : bp1 <= bp) {
|
|
37
|
+
lo = mid + 1;
|
|
38
|
+
} else {
|
|
39
|
+
hi = mid;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const box = boxes[lo];
|
|
43
|
+
if (bp <= box.bp0) {
|
|
44
|
+
return box.x0;
|
|
45
|
+
}
|
|
46
|
+
const span = box.bp1 - box.bp0;
|
|
47
|
+
return span > 0 ? box.x0 + (Math.min(bp, box.bp1) - box.bp0) / span * (box.x1 - box.x0) : box.x0;
|
|
48
|
+
}
|
|
49
|
+
function tubeSpan(boxes, start, end) {
|
|
50
|
+
const a = tubeX(boxes, start);
|
|
51
|
+
const b = tubeX(boxes, end, true);
|
|
52
|
+
return { x0: Math.min(a, b), x1: Math.max(a, b) };
|
|
53
|
+
}
|
|
54
|
+
function rulerBoxes(byRefName) {
|
|
55
|
+
let best;
|
|
56
|
+
for (const boxes of byRefName.values()) {
|
|
57
|
+
if (!best || boxes.length > best.length) {
|
|
58
|
+
best = boxes;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return best;
|
|
62
|
+
}
|
|
63
|
+
var TICK_PX = 5;
|
|
64
|
+
var LABEL_GAP_PX = 10;
|
|
65
|
+
var TARGET_TICK_PX = 110;
|
|
66
|
+
var MIN_BRACKET_PX = 3;
|
|
67
|
+
function rulerInk(darkMode) {
|
|
68
|
+
return darkMode ? "#b0b0b8" : "#55555c";
|
|
69
|
+
}
|
|
70
|
+
function niceStep(raw) {
|
|
71
|
+
const pow = 10 ** Math.floor(Math.log10(raw));
|
|
72
|
+
const m = raw / pow;
|
|
73
|
+
return (m <= 1 ? 1 : m <= 2 ? 2 : m <= 5 ? 5 : 10) * pow;
|
|
74
|
+
}
|
|
75
|
+
function placeLabels(candidates) {
|
|
76
|
+
const placed = [];
|
|
77
|
+
for (const a of candidates) {
|
|
78
|
+
if (placed.every(
|
|
79
|
+
(b) => Math.abs(a.at - b.at) >= (a.width + b.width) / 2 + LABEL_GAP_PX
|
|
80
|
+
)) {
|
|
81
|
+
placed.push(a);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return placed.map(({ text, at }) => ({ text, at }));
|
|
85
|
+
}
|
|
86
|
+
function scaleMarks(boxes, x, width, measure) {
|
|
87
|
+
const first = boxes[0];
|
|
88
|
+
const last = boxes.at(-1);
|
|
89
|
+
if (!first || !last) {
|
|
90
|
+
return void 0;
|
|
91
|
+
}
|
|
92
|
+
const from = x(first.x0);
|
|
93
|
+
const to = x(last.x1);
|
|
94
|
+
const bpSpan = last.bp1 - first.bp0;
|
|
95
|
+
if (to <= from || bpSpan <= 0) {
|
|
96
|
+
return void 0;
|
|
97
|
+
}
|
|
98
|
+
const step = niceStep(bpSpan / (to - from) * TARGET_TICK_PX);
|
|
99
|
+
const ticks = [];
|
|
100
|
+
const positions = [];
|
|
101
|
+
for (let bp = Math.ceil(first.bp0 / step) * step; bp <= last.bp1; bp += step) {
|
|
102
|
+
const sx = Math.round(x(tubeX(boxes, bp))) + 0.5;
|
|
103
|
+
if (sx >= 0 && sx <= width) {
|
|
104
|
+
ticks.push(sx);
|
|
105
|
+
const text = bp.toLocaleString("en-US");
|
|
106
|
+
positions.push({ text, at: sx, width: measure(text) });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
spans: [{ x0: from, x1: to }],
|
|
111
|
+
ticks,
|
|
112
|
+
labels: placeLabels(positions)
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function boxMarks(boxes, x, width, measure) {
|
|
116
|
+
const shown = boxes.map((box) => ({ box, s0: x(box.x0), s1: x(box.x1) })).filter(({ s0, s1 }) => s1 >= 0 && s0 <= width);
|
|
117
|
+
const first = shown[0];
|
|
118
|
+
const last = shown.at(-1);
|
|
119
|
+
if (!first || !last) {
|
|
120
|
+
return void 0;
|
|
121
|
+
}
|
|
122
|
+
const label = (text, at) => ({
|
|
123
|
+
text,
|
|
124
|
+
at,
|
|
125
|
+
width: measure(text)
|
|
126
|
+
});
|
|
127
|
+
const position = (bp, at) => label(bp.toLocaleString("en-US"), at);
|
|
128
|
+
const spans = [];
|
|
129
|
+
const ticks = [];
|
|
130
|
+
const lengths = [];
|
|
131
|
+
const boundaries = [];
|
|
132
|
+
for (const { box, s0, s1 } of shown) {
|
|
133
|
+
spans.push({ x0: s0, x1: s1 });
|
|
134
|
+
ticks.push(Math.round(s0) + 0.5);
|
|
135
|
+
if (s1 - s0 >= MIN_BRACKET_PX) {
|
|
136
|
+
ticks.push(Math.round(s1) + 0.5);
|
|
137
|
+
}
|
|
138
|
+
const length = label(formatBp(box.bp1 - box.bp0), (s0 + s1) / 2);
|
|
139
|
+
if (length.width <= s1 - s0) {
|
|
140
|
+
lengths.push(length);
|
|
141
|
+
}
|
|
142
|
+
if (box !== first.box) {
|
|
143
|
+
boundaries.push(position(box.bp0, s0));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const ends = [
|
|
147
|
+
position(first.box.bp0, first.s0),
|
|
148
|
+
position(last.box.bp1, last.s1)
|
|
149
|
+
];
|
|
150
|
+
return {
|
|
151
|
+
spans,
|
|
152
|
+
ticks,
|
|
153
|
+
labels: placeLabels([...ends, ...lengths, ...boundaries])
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function rulerMarks(boxes, x, width, measure, referenceAxis = false) {
|
|
157
|
+
return (referenceAxis ? scaleMarks : boxMarks)(boxes, x, width, measure);
|
|
158
|
+
}
|
|
159
|
+
function drawTubeMapRuler(ctx, boxes, frame, top, referenceAxis = false) {
|
|
160
|
+
const { x, width, darkMode } = frame;
|
|
161
|
+
ctx.font = "10px sans-serif";
|
|
162
|
+
const marks = rulerMarks(
|
|
163
|
+
boxes,
|
|
164
|
+
x,
|
|
165
|
+
width,
|
|
166
|
+
(text) => ctx.measureText(text).width,
|
|
167
|
+
referenceAxis
|
|
168
|
+
);
|
|
169
|
+
if (!marks) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const ink = rulerInk(darkMode);
|
|
173
|
+
const y = top + 0.5;
|
|
174
|
+
ctx.strokeStyle = ink;
|
|
175
|
+
ctx.fillStyle = ink;
|
|
176
|
+
ctx.lineWidth = 1;
|
|
177
|
+
ctx.textAlign = "center";
|
|
178
|
+
ctx.textBaseline = "top";
|
|
179
|
+
ctx.beginPath();
|
|
180
|
+
for (const { x0, x1 } of marks.spans) {
|
|
181
|
+
ctx.moveTo(Math.max(0, x0), y);
|
|
182
|
+
ctx.lineTo(Math.min(width, x1), y);
|
|
183
|
+
}
|
|
184
|
+
for (const sx of marks.ticks) {
|
|
185
|
+
ctx.moveTo(sx, top);
|
|
186
|
+
ctx.lineTo(sx, top + TICK_PX);
|
|
187
|
+
}
|
|
188
|
+
ctx.stroke();
|
|
189
|
+
for (const { text, at } of marks.labels) {
|
|
190
|
+
ctx.fillText(text, at, top + TICK_PX + 2);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export {
|
|
195
|
+
referenceBoxes,
|
|
196
|
+
tubeX,
|
|
197
|
+
tubeSpan,
|
|
198
|
+
rulerBoxes,
|
|
199
|
+
rulerInk,
|
|
200
|
+
rulerMarks,
|
|
201
|
+
drawTubeMapRuler
|
|
202
|
+
};
|