From a9554ebe66dfadfc26f7c967b124e4f42d0c1eee Mon Sep 17 00:00:00 2001 From: ducku Date: Sun, 11 Feb 2024 18:58:30 -0800 Subject: [PATCH] Segment cycle detection improvement and new exmaple data --- src/components/ExampleSelectButtons.js | 11 +- src/components/TubeMapContainer.js | 17 +- src/enums.js | 1 + src/util/demo-data.js | 444 +++++++++++++++++++++++++ src/util/tubemap.js | 39 ++- 5 files changed, 493 insertions(+), 19 deletions(-) diff --git a/src/components/ExampleSelectButtons.js b/src/components/ExampleSelectButtons.js index 0d283e03..7f62ea7a 100644 --- a/src/components/ExampleSelectButtons.js +++ b/src/components/ExampleSelectButtons.js @@ -87,7 +87,16 @@ class ExampleSelectButtons extends Component { this.handleClick(dataOriginTypes.EXAMPLE_8, "plainColors") } > - Multiple Nodes Cycle + Multiple Nodes Cycle 1 + + ); diff --git a/src/components/TubeMapContainer.js b/src/components/TubeMapContainer.js index 49eb6d91..31f219e2 100644 --- a/src/components/TubeMapContainer.js +++ b/src/components/TubeMapContainer.js @@ -272,7 +272,6 @@ class TubeMapContainer extends Component { 0, 1 // Examples always have reads as track 1 ); - console.log("READS", reads); break; case dataOriginTypes.EXAMPLE_8: vg = data.cycleGraph; @@ -285,8 +284,20 @@ class TubeMapContainer extends Component { 0, 1 // Examples always have reads as track 1 ); - - console.log("READS", reads); + + 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 03fbad3a..fdcb2441 100644 --- a/src/enums.js +++ b/src/enums.js @@ -8,5 +8,6 @@ export const dataOriginTypes = { 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 baea1635..98a4a6a8 100644 --- a/src/util/demo-data.js +++ b/src/util/demo-data.js @@ -605,6 +605,450 @@ export const cycleReads = [ }, ]; +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 d5cf2fd0..c6c16e4a 100644 --- a/src/util/tubemap.js +++ b/src/util/tubemap.js @@ -678,13 +678,15 @@ 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 previousPathWithNode; + while ((previousPathWithNode?.node === null || !previousPathWithNode?.node) && lastIndex >= 0) { + previousPathWithNode = reads[idx].path[lastIndex]; lastIndex = lastIndex - 1; } + let previousValidY = previousPathWithNode?.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 @@ -696,30 +698,37 @@ function placeReads() { nextPathIndex = nextPathIndex + 1; } - let nextNodeOrder = nextPathWithNode?.order; - let previousNodeOrders = reads[idx].path.map(p => p.order).slice(0, pathIdx); - + let previousNodeOrder = previousPathWithNode?.order; // if the previous segment and the next segment is going to the same node - let nextNodeVisited = previousNodeOrders !== null && nextNodeOrder !== null && previousNodeOrders.includes(nextNodeOrder); + let nextNodeJustVisited = previousNodeOrder !== null && nextNodeOrder !== null && previousNodeOrder === nextNodeOrder; - reads[idx].path[pathIdx].betweenCycle = false; - if (nextNodeVisited) { - // check the forward status of the last time we visited the node we're about to visit - let previousNodeIsForward = reads[idx].path[previousNodeOrders.lastIndexOf(nextNodeOrder)]?.isForward; - let nextNodeIsForward = nextPathWithNode?.isForward + + // a segment can also be in a loop if the next node is behind on the map + let nextNodeBehind = previousNodeOrder !== null && nextNodeOrder !== null && previousPathWithNode.order > nextNodeOrder; - // if the next segment and the one we've previously visited is going in the same direction - let sameDirection = previousNodeIsForward !== null && nextNodeIsForward !== null && previousNodeIsForward === nextNodeIsForward - // we've already visited the next node and we're going the same direction as the last time we've visited it + reads[idx].path[pathIdx].betweenCycle = false; + if (nextNodeBehind) { + // the segment has to loop regardless of which direction the read goes in + reads[idx].path[pathIdx].betweenCycle = true; + } else if (nextNodeJustVisited) { + // the segment doesn't have to loop if going in opposite direction from the node + let previousNodeIsForward = previousPathWithNode?.isForward; + let nextNodeIsForward = nextPathWithNode?.isForward; + + // if the next segment(to a node) and previousl segment(from a node) is going in the same direction + let sameDirection = previousNodeIsForward !== null && nextNodeIsForward !== null && previousNodeIsForward === nextNodeIsForward + + // we're visiting the same node we just visited from the same direction if (sameDirection) { reads[idx].path[pathIdx].betweenCycle = true; } } + elementsWithoutNode.push({ readIndex: idx, pathIndex: pathIdx,