Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Read Cycle Layout #389

Merged
merged 12 commits into from
Feb 26, 2024
91 changes: 76 additions & 15 deletions src/util/tubemap.js
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ function createTubeMap() {
generateLaneAssignment();

if (config.showExonsFlag === true && bed !== null) addTrackFeatures();
generateNodeXCoords();
//generateNodeXCoords();
adamnovak marked this conversation as resolved.
Show resolved Hide resolved

if (reads && config.showReads) {
generateReadOnlyNodeAttributes();
Expand All @@ -505,6 +505,8 @@ function createTubeMap() {
});
}

generateNodeXCoords();

generateSVGShapesFromPath(nodes, tracks);
if (DEBUG) {
console.log("Tracks:");
Expand Down Expand Up @@ -682,10 +684,32 @@ function placeReads() {
previousValidY = reads[idx].path[lastIndex].y;
lastIndex = lastIndex - 1;
}

// sometimes, elements without nodes are between 2 segments going to the same node, from the same direction
// this means we're looping back to the same node, which constitutes a different sorting priority
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure "constitutes" is the right word here. Do you mean we want to use a different sorting in this case?


let previousNodeOrder = reads[idx].path[pathIdx - 1]?.order;
let nextNodeOrder = reads[idx].path[pathIdx + 1]?.order;

let previousNodeIsForward = reads[idx].path[pathIdx - 1]?.isForward;
let nextNodeIsForward = reads[idx].path[pathIdx + 1]?.isForward

// if the previous segment and the next segment is going to the same node
let sameNode = previousNodeOrder !== null && nextNodeOrder !== null && previousNodeOrder === nextNodeOrder;
// if the previous semgne tnad the next segment is going in the same direction
let sameDirection = previousNodeIsForward !== null && nextNodeIsForward !== null && previousNodeIsForward === nextNodeIsForward

let betweenCycle = false;
// if the previous segment and the next segment is going to the same node
if (sameNode && sameDirection) {
betweenCycle = true;
}
Copy link
Member

@adamnovak adamnovak Jan 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might want to use this reverse sort order logic in more cases than just the one where we visit the same node in the same orientation before and after the no-node section.

I think we want to have the same behavior even if that one node that we loop around is instead divided up into two or more nodes that we loop around as a group. Like if the read visits nodes 1, 2, 1, 2, 1, 2. I'm not sure of the best way to detect that we are visiting no node because we need to get back to an earlier X to complete a cycle, though.


elementsWithoutNode.push({
readIndex: idx,
pathIndex: pathIdx,
previousY: previousValidY,
betweenCycle: betweenCycle,
});
}
});
Expand Down Expand Up @@ -841,31 +865,48 @@ 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 of the reverse when the segment is between a cycle
// this ensures a loop that starts on the outside, stays on the outside
// and loops are rolled in order
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment could be better written, I think. Maybe:

Suggested change
// we want to sort of the reverse when the segment is between a cycle
// this ensures a loop that starts on the outside, stays on the outside
// and loops are rolled in order
// 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 (a.betweenCycle && b.betweenCycle) {
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) {
// 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
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];
let readIndexA = a[0];
let readIndexB = b[0];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we use argument destructuring here?

Suggested change
function compareReadOutgoingSegmentsByGoingTo(a, b) {
// 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
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];
let readIndexA = a[0];
let readIndexB = b[0];
function compareReadOutgoingSegmentsByGoingTo([readIndexA, pathIndexA], [readIndexB, pathIndexB]) {


// Segments are first sorted by the the node they end on,
// then by a coming-from tiebreaker,
// then by length in final node
// TODO: Perhaps we should make an exception to first sort by a coming-from when a cycle is involved
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;
}
Expand All @@ -876,10 +917,30 @@ 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
// if we've placed some incoming reads first, we can sort the reads based on where they were placed last

let previousValidYA = null;
let previousValidYB = null;
let lastPathIndexA = pathIndexA - 1;
let lastPathIndexB = pathIndexB - 1;
while (previousValidYA === null && pathIndexA >= 0) {
previousValidYA = reads[readIndexA].path[lastPathIndexA].y;
lastPathIndexA -= 1;
}
while (previousValidYB === null && pathIndexB >= 0) {
previousValidYB = reads[readIndexB].path[lastPathIndexB].y;
lastPathIndexB -= 1;
}
if (previousValidYA && previousValidYB) {
return previousValidYA - previousValidYB;
}

// 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
Expand Down
Loading