diff --git a/src/components/ExampleSelectButtons.js b/src/components/ExampleSelectButtons.js
index 456981f6..7f62ea7a 100644
--- a/src/components/ExampleSelectButtons.js
+++ b/src/components/ExampleSelectButtons.js
@@ -80,6 +80,24 @@ class ExampleSelectButtons extends Component {
>
Alignments to Reverse Nodes
+
+
);
}
diff --git a/src/components/TubeMapContainer.js b/src/components/TubeMapContainer.js
index 47fa48ac..31f219e2 100644
--- a/src/components/TubeMapContainer.js
+++ b/src/components/TubeMapContainer.js
@@ -272,6 +272,32 @@ class TubeMapContainer extends Component {
0,
1 // Examples always have reads as track 1
);
+ break;
+ case dataOriginTypes.EXAMPLE_8:
+ vg = data.cycleGraph;
+ nodes = tubeMap.vgExtractNodes(vg);
+ tracks = tubeMap.vgExtractTracks(vg, 0, 0); // Examples have paths and haplotypes as track 0.
+ reads = tubeMap.vgExtractReads(
+ nodes,
+ tracks,
+ data.cycleReads,
+ 0,
+ 1 // Examples always have reads as track 1
+ );
+
+ break;
+ case dataOriginTypes.EXAMPLE_9:
+ vg = data.cycle2Graph;
+ nodes = tubeMap.vgExtractNodes(vg);
+ tracks = tubeMap.vgExtractTracks(vg, 0, 0); // Examples have paths and haplotypes as track 0.
+ reads = tubeMap.vgExtractReads(
+ nodes,
+ tracks,
+ data.cycle2Reads,
+ 0,
+ 1 // Examples always have reads as track 1
+ );
+
break;
case dataOriginTypes.NO_DATA:
// Leave the data empty.
diff --git a/src/enums.js b/src/enums.js
index 4ae8f1ff..fdcb2441 100644
--- a/src/enums.js
+++ b/src/enums.js
@@ -7,5 +7,7 @@ export const dataOriginTypes = {
EXAMPLE_5: "example 5",
EXAMPLE_6: "example 6",
EXAMPLE_7: "example 7",
+ EXAMPLE_8: "example 8",
+ EXAMPLE_9: "example 9",
NO_DATA: "no data",
};
diff --git a/src/util/demo-data.js b/src/util/demo-data.js
index 87164aa9..98a4a6a8 100644
--- a/src/util/demo-data.js
+++ b/src/util/demo-data.js
@@ -112,6 +112,943 @@ export const demoReads = `
{"sequence": "GGTTCCCACTCCATAAGGTAGTTCAGCACCGCCGTGTCCCGGCCGGGTCGCGGGGAGCCCCGGTACATCGCAGTGGGCTACGTGGACGACACGCAGTTCGTGCGGTTCGACAGCGACGCGGCGACTCCGAGGATGTAGCCGCAGGCGCCGTGGTTGGAGCAGGAGGGACCGGAGTATTGGGACCGGAGCACACGGAACATCAGGCCCGCGCACAGACTGACAAGAGTGAACCTGCCCATGCCGCGCCGCTACTACCACCAGAGCTAGGCCGGTGAATGACCCCGGCCTGGGGCGAAGGTCACGACCCCTCCTCATCCCCCACGGACGCCCCGGGTCCCCCCCGCGAGTCTCCGGCTCC", "path": {"mapping": [{"position": {"node_id": "3"}, "edit": [{"from_length": 1, "to_length": 1}], "rank": 2}, {"position": {"node_id": "5"}, "edit": [{"from_length": 16, "to_length": 16}], "rank": 3}, {"position": {"node_id": "6"}, "edit": [{"from_length": 1, "to_length": 1}], "rank": 4}, {"position": {"node_id": "8"}, "edit": [{"from_length": 10, "to_length": 10}], "rank": 5}, {"position": {"node_id": "9"}, "edit": [{"from_length": 29, "to_length": 29}], "rank": 6}, {"position": {"node_id": "10"}, "edit": [{"from_length": 1, "to_length": 1}], "rank": 7}, {"position": {"node_id": "12"}, "edit": [{"from_length": 2, "to_length": 2}], "rank": 8}, {"position": {"node_id": "13"}, "edit": [{"from_length": 32, "to_length": 32}], "rank": 9}, {"position": {"node_id": "14"}, "edit": [{"from_length": 32, "to_length": 32}], "rank": 10}, {"position": {"node_id": "15"}, "edit": [{"from_length": 6, "to_length": 6}], "rank": 11}, {"position": {"node_id": "16"}, "edit": [{"from_length": 1, "to_length": 1}], "rank": 12}, {"position": {"node_id": "18"}, "edit": [{"from_length": 10, "to_length": 10}], "rank": 13}, {"position": {"node_id": "19"}, "edit": [{"from_length": 1, "to_length": 1}], "rank": 14}, {"position": {"node_id": "21"}, "edit": [{"from_length": 3, "to_length": 3}], "rank": 15}, {"position": {"node_id": "23"}, "edit": [{"from_length": 1, "to_length": 1}], "rank": 16}, {"position": {"node_id": "24"}, "edit": [{"from_length": 10, "to_length": 10}], "rank": 17}, {"position": {"node_id": "25"}, "edit": [{"from_length": 5, "to_length": 5}], "rank": 18}, {"position": {"node_id": "26"}, "edit": [{"from_length": 1, "to_length": 1}], "rank": 19}, {"position": {"node_id": "28"}, "edit": [{"from_length": 26, "to_length": 26}], "rank": 20}, {"position": {"node_id": "29"}, "edit": [{"from_length": 2, "to_length": 2}], "rank": 21}, {"position": {"node_id": "30"}, "edit": [{"from_length": 1, "to_length": 1}], "rank": 22}, {"position": {"node_id": "32"}, "edit": [{"from_length": 29, "to_length": 29}], "rank": 23}, {"position": {"node_id": "33"}, "edit": [{"from_length": 32, "to_length": 32}], "rank": 24}, {"position": {"node_id": "34"}, "edit": [{"from_length": 32, "to_length": 32}], "rank": 25}, {"position": {"node_id": "35"}, "edit": [{"from_length": 32, "to_length": 32}], "rank": 26}, {"position": {"node_id": "36"}, "edit": [{"from_length": 8, "to_length": 8}], "rank": 27}, {"position": {"node_id": "38"}, "edit": [{"from_length": 1, "to_length": 1}], "rank": 28}, {"position": {"node_id": "39"}, "edit": [{"from_length": 23, "to_length": 23}], "rank": 29}, {"position": {"node_id": "40"}, "edit": [{"from_length": 7, "to_length": 7}], "rank": 30}]}, "score": 358, "identity": 1.0}
`;
+export const cycleGraph = {
+ edge: [
+ {
+ from: "60080783",
+ from_start: true,
+ to: "60080786",
+ to_end: true,
+ },
+ {
+ from: "60080783",
+ from_start: true,
+ to: "60080785",
+ },
+ {
+ from: "60080783",
+ to: "60080785",
+ },
+ {
+ from: "60080786",
+ to: "60080785",
+ },
+ {
+ from: "60080786",
+ to: "60080785",
+ to_end: true,
+ },
+ ],
+ node: [
+ {
+ id: "60080785",
+ sequence: "AC",
+ },
+ {
+ id: "60080783",
+ sequence: "TT",
+ },
+ {
+ id: "60080786",
+ sequence: "GT",
+ },
+ ],
+ path: [
+ {
+ mapping: [
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "1",
+ },
+ {
+ edit: [
+ {
+ from_length: 21,
+ to_length: 21,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "2",
+ },
+ ],
+ name: "GRCh38.chr14",
+ indexOfFirstBase: "0",
+ },
+ ],
+};
+
+
+export const cycleReads = [
+ {
+ identity: 1,
+ name: "Read0",
+ path: {
+ mapping: [
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "1",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "2",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "3",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "4",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "5",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "6",
+ },
+ ],
+ },
+ score: 110,
+ sequence: "GTGTTT",
+ },
+ {
+ identity: 1,
+ name: "Read1",
+ path: {
+ mapping: [
+ {
+ edit: [
+ {
+ from_length: 1,
+ to_length: 1,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ offset: 1,
+ },
+ rank: "1",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "2",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "3",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "4",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "5",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 1,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ offset: -2,
+ },
+ rank: "6",
+ },
+ ],
+ },
+ score: 110,
+ sequence: "TGTTT",
+ },
+ {
+ identity: 1,
+ name: "Read2",
+ path: {
+ mapping: [
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "1",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "2",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "3",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "4",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "5",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "6",
+ },
+ ],
+ },
+ score: 110,
+ sequence: "GTGTTT",
+ },
+ {
+ identity: 1,
+ name: "Read3",
+ path: {
+ mapping: [
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "1",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "2",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "3",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "4",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "5",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 3,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ offset: -1,
+ },
+ rank: "6",
+ },
+ ],
+ },
+ score: 110,
+ sequence: "GTACTT",
+ },
+ {
+ identity: 1,
+ name: "Read4",
+ path: {
+ mapping: [
+ {
+ edit: [
+ {
+ from_length: 1,
+ to_length: 1,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ offset: 1,
+ },
+ rank: "1",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "2",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "3",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "4",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "5",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 1,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "6",
+ },
+ ],
+ },
+ score: 110,
+ sequence: "TACTT",
+ },
+];
+
+export const cycle2Graph = {
+ edge: [
+ {
+ from: "60080783",
+ from_start: true,
+ to: "60080786",
+ to_end: true,
+ },
+ {
+ from: "60080783",
+ from_start: true,
+ to: "60080785",
+ },
+ {
+ from: "60080783",
+ to: "60080785",
+ },
+ {
+ from: "60080786",
+ to: "60080785",
+ },
+ {
+ from: "60080786",
+ to: "60080785",
+ to_end: true,
+ },
+ ],
+ node: [
+ {
+ id: "60080785",
+ sequence: "AC",
+ },
+ {
+ id: "60080783",
+ sequence: "TT",
+ },
+ {
+ id: "60080786",
+ sequence: "GT",
+ },
+ ],
+ path: [
+ {
+ mapping: [
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "1",
+ },
+ {
+ edit: [
+ {
+ from_length: 21,
+ to_length: 21,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "2",
+ },
+ ],
+ name: "GRCh38.chr14",
+ indexOfFirstBase: "0",
+ },
+ ],
+};
+
+
+export const cycle2Reads = [
+ {
+ identity: 1,
+ name: "Read0",
+ path: {
+ mapping: [
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "1",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "2",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "3",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "4",
+ },
+ ],
+ },
+ score: 110,
+ sequence: "GTGTTT",
+ },
+ {
+ identity: 1,
+ name: "Read1",
+ path: {
+ mapping: [
+ {
+ edit: [
+ {
+ from_length: 1,
+ to_length: 1,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ offset: 1,
+ },
+ rank: "1",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "2",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "3",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "5",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 1,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ offset: -2,
+ },
+ rank: "6",
+ },
+ ],
+ },
+ score: 110,
+ sequence: "TGTTT",
+ },
+ {
+ identity: 1,
+ name: "Read2",
+ path: {
+ mapping: [
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "1",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "2",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "3",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "4",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "5",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "6",
+ },
+ ],
+ },
+ score: 110,
+ sequence: "GTGTTT",
+ },
+ {
+ identity: 1,
+ name: "Read3",
+ path: {
+ mapping: [
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "1",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "2",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "3",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "4",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "5",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 3,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ offset: -1,
+ },
+ rank: "6",
+ },
+ ],
+ },
+ score: 110,
+ sequence: "GTACTT",
+ },
+ {
+ identity: 1,
+ name: "Read4",
+ path: {
+ mapping: [
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "2",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "3",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080786",
+ },
+ rank: "4",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 2,
+ },
+ ],
+ position: {
+ node_id: "60080785",
+ },
+ rank: "5",
+ },
+ {
+ edit: [
+ {
+ from_length: 2,
+ to_length: 1,
+ },
+ ],
+ position: {
+ node_id: "60080783",
+ },
+ rank: "6",
+ },
+ ],
+ },
+ score: 110,
+ sequence: "TACTT",
+ },
+];
+
export const reverseAlignmentGraph = {
edge: [
{
diff --git a/src/util/tubemap.js b/src/util/tubemap.js
index 4ae9fef3..8ba06ee2 100644
--- a/src/util/tubemap.js
+++ b/src/util/tubemap.js
@@ -488,7 +488,6 @@ function createTubeMap() {
generateLaneAssignment();
if (config.showExonsFlag === true && bed !== null) addTrackFeatures();
- generateNodeXCoords();
if (reads && config.showReads) {
generateReadOnlyNodeAttributes();
@@ -505,6 +504,8 @@ function createTubeMap() {
});
}
+ generateNodeXCoords();
+
generateSVGShapesFromPath(nodes, tracks);
if (DEBUG) {
console.log("Tracks:");
@@ -676,12 +677,39 @@ function placeReads() {
if (!element.hasOwnProperty("y")) {
// previous y value from pathIdx - 1 might not exist yet if that segment is also without node
// use previous y value from last segment with node instead
- let previousValidY = null;
let lastIndex = pathIdx - 1;
- while (previousValidY === null && lastIndex >= 0) {
- previousValidY = reads[idx].path[lastIndex].y;
+ let previousVisitToNode;
+ while ((previousVisitToNode?.node === null || !previousVisitToNode?.node) && lastIndex >= 0) {
+ previousVisitToNode = reads[idx].path[lastIndex];
lastIndex = lastIndex - 1;
}
+
+ let previousValidY = previousVisitToNode?.y;
+
+ // sometimes, elements without nodes are between 2 segments going to a node we've already visited, from the same direction
+ // this means we're looping back to a node we've already been to, and we should sort in reverse
+
+ // Find the next node in our path
+ let nextPathIndex = pathIdx + 1
+ let nextVisitToNode = reads[idx].path[nextPathIndex];
+ while ((nextVisitToNode?.node === null || !nextVisitToNode?.node) && nextPathIndex < reads[idx].path.length) {
+ nextVisitToNode = reads[idx].path[nextPathIndex];
+ nextPathIndex = nextPathIndex + 1;
+ }
+
+ // Specifically referring to segments between a cycle that's traversing from right to left
+ let betweenCycleReverseTraversal =
+ // A segment can be between a cycle if it there are nodes on both sides
+ (nextVisitToNode && previousVisitToNode) &&
+ // Make sure the visitToNode objects are what we expect
+ (typeof previousVisitToNode.order !== "undefined" && typeof nextVisitToNode.order !== "undefined" && typeof nextVisitToNode.isForward !== "undefined" && typeof previousVisitToNode.isForward !== "undefined") &&
+ // A segment is between a cycle if the next node it visits is behind the previous node it visited
+ ((previousVisitToNode.order > nextVisitToNode.order) ||
+ // A segment can also be between a cycle if it's visiting the same node it just visited in the same direction
+ (nextVisitToNode.order === previousVisitToNode.order && nextVisitToNode.isForward === previousVisitToNode.isForward));
+
+ reads[idx].path[pathIdx].betweenCycleReverseTraversal = betweenCycleReverseTraversal;
+
elementsWithoutNode.push({
readIndex: idx,
pathIndex: pathIdx,
@@ -841,31 +869,61 @@ function compareNoNodeReadsByPreviousY(a, b) {
const segmentA = reads[a.readIndex].path[a.pathIndex];
const segmentB = reads[b.readIndex].path[b.pathIndex];
if (segmentA.order === segmentB.order) {
- return a.previousY - b.previousY;
+ // We want to sort in reverse order when the segment is along the reverse-going part of a cycle.
+ // This ensures a loop that starts on the outside, stays on the outside,
+ // and rolls up in order with other loops.
+ if (segmentA?.betweenCycleReverseTraversal && segmentB?.betweenCycleReverseTraversal) {
+ return b.previousY - a.previousY;
+ } else {
+ return a.previousY - b.previousY;
+ }
}
return segmentA.order - segmentB.order;
}
// compare read segments by where they are going to
-function compareReadOutgoingSegmentsByGoingTo(a, b) {
- let pathIndexA = a[1];
- let pathIndexB = b[1];
- // let readA = reads[a[0]]
- // let nodeIndexA = readA.path[pathIndexA].node;
- let nodeA = nodes[reads[a[0]].path[pathIndexA].node];
- let nodeB = nodes[reads[b[0]].path[pathIndexB].node];
+function compareReadOutgoingSegmentsByGoingTo([readIndexA, pathIndexA], [readIndexB, pathIndexB]) {
+ // Expect two arrays both containing 2 integers.
+ // The first index of each array contains the read index
+ // The second index of each array contains the path index
+
+ // Segments are first sorted by the y value of their last node,
+ // then by the node they end on,
+ // then by length in final node
+ let previousValidYA = null;
+ let previousValidYB = null;
+ let lastPathIndexA = reads[readIndexA].path.length - 1;
+ let lastPathIndexB = reads[readIndexB].path.length - 1;
+ while ((previousValidYA === null || !previousValidYA) && lastPathIndexA >= 0) {
+ previousValidYA = reads[readIndexA].path[lastPathIndexA].y;
+ lastPathIndexA -= 1;
+ }
+ while ((previousValidYB === null || !previousValidYB) && lastPathIndexB >= 0) {
+ previousValidYB = reads[readIndexB].path[lastPathIndexB].y;
+ lastPathIndexB -= 1;
+ }
+
+ if (previousValidYA && previousValidYB) {
+ return previousValidYA - previousValidYB;
+ }
+
+ // Couldn't find a valid y value for at least one of the reads, sort by which node reads end on
+ let nodeA = nodes[reads[readIndexA].path[pathIndexA].node];
+ let nodeB = nodes[reads[readIndexB].path[pathIndexB].node];
+ // Follow the reads' paths until we find the node they diverge at
+ // Or, they go through all the same nodes and we do a tiebreaker at the end
while (nodeA !== null && nodeB !== null && nodeA === nodeB) {
- if (pathIndexA < reads[a[0]].path.length - 1) {
+ if (pathIndexA < reads[readIndexA].path.length - 1) {
pathIndexA += 1;
- while (reads[a[0]].path[pathIndexA].node === null) pathIndexA += 1; // skip null nodes in path
- nodeA = nodes[reads[a[0]].path[pathIndexA].node];
+ while (reads[readIndexA].path[pathIndexA].node === null) pathIndexA += 1; // skip null nodes in path
+ nodeA = nodes[reads[readIndexA].path[pathIndexA].node]; // the next node a is going to
} else {
nodeA = null;
}
- if (pathIndexB < reads[b[0]].path.length - 1) {
+ if (pathIndexB < reads[readIndexB].path.length - 1) {
pathIndexB += 1;
- while (reads[b[0]].path[pathIndexB].node === null) pathIndexB += 1; // skip null nodes in path
- nodeB = nodes[reads[b[0]].path[pathIndexB].node];
+ while (reads[readIndexB].path[pathIndexB].node === null) pathIndexB += 1; // skip null nodes in path
+ nodeB = nodes[reads[readIndexB].path[pathIndexB].node]; // the next node b is going to
} else {
nodeB = null;
}
@@ -876,10 +934,13 @@ function compareReadOutgoingSegmentsByGoingTo(a, b) {
}
if (nodeB !== null) return -1; // nodeB not null, nodeA null
// both nodes are null -> both end in the same node
- const beginDiff = reads[a[0]].firstNodeOffset - reads[b[0]].firstNodeOffset;
+ const beginDiff = reads[readIndexA].firstNodeOffset - reads[readIndexB].firstNodeOffset;
if (beginDiff !== 0) return beginDiff;
- // break tie: both reads cover the same nodes and begin at the same position -> compare by endPosition
- return reads[a[0]].finalNodeCoverLength - reads[b[0]].finalNodeCoverLength;
+
+ // break tie: both reads cover the same nodes and begin at the same position
+
+ // One or both reads didn't have a previously valid Y value, compare by the endPosition of the read
+ return reads[readIndexA].finalNodeCoverLength - reads[readIndexB].finalNodeCoverLength;
}
// compare read segments by (y-coord of) where they are coming from