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

Conversation

ducku
Copy link
Collaborator

@ducku ducku commented Jan 28, 2024

Segments cycling on the first node had strange interactions since segments classified as "outgoing"(if its pathIndex == 0) had different methods of sorting.

Changes are made to how reads are placed outside of nodes when a cycle is detected, and inside of nodes when reads have the same source and destination Node.

  • A new tiebreaker is placed in sortByGoingTo for reads that start and end in the same node. Now they're placed based on the endmost node they were placed in, working its way back if there are no y values. Since these reads start and end in the same node, this should start the reads(in the first node) in the same order as the other nodes.

  • Segments outside of nodes now detects cycles, defined as the previous and next segments are in the node and going in the same direction. Segments outside of nodes and are between cycles now sort in reverse to keep the layout clean.

  • Extra space in between nodes are calculated after reads are added into the tracks variable.

Cycle1

Cycle2

Closes #321

Copy link
Member

@adamnovak adamnovak left a comment

Choose a reason for hiding this comment

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

This looks pretty good, and way better than before.

But I'm concerned that it's specialized for one-node cycles, when probably we need to support cycles of multiple nodes.

Also, what's going on in the first screenshot with the innermost three reads in the spiral below the node? They don't seem to be spiraling in the same order as the other ones, and so they end up crossing over each other for no apparent reason. It's not a big problem for these 3 reads, but I think a similar problem is happening with more reads in the second screenshot, and it makes the visualization messier there.

@@ -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?

src/util/tubemap.js Outdated Show resolved Hide resolved
Comment on lines 703 to 706
// 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.

@ducku
Copy link
Collaborator Author

ducku commented Feb 4, 2024

Changing to sort the first segment by existing y values first works better on this set of data, and I think it would work better overall.

I'm currently trying to make an example dataset showcasing this.

Cycle1
Cycle2

@adamnovak
Copy link
Member

That's looking pretty good!

I guess in the second image it would be better if those reads on the inside coming down stayed on the inside when turning to go left, instead of going all the way down and coming left on the outside. But this is much cleaner than before.

Copy link
Member

@adamnovak adamnovak left a comment

Choose a reason for hiding this comment

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

This looks like it ought to work, but I think some parts of the code could be simplified and some of the comments and variable names could be made clearer.

let lastIndex = pathIdx - 1;
while (previousValidY === null && lastIndex >= 0) {
previousValidY = reads[idx].path[lastIndex].y;
let previousPathWithNode;
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is a "path". The things in reads[idx].path[] are visits or path entries, so this should be named something more like previousVisitToNode. Or maybe previousVisitToAnyNode? Since it's not always to this node. Maybe just previousVisit?


// Find the next node in our path
let nextPathIndex = pathIdx + 1
let nextPathWithNode = reads[idx].path[nextPathIndex];
Copy link
Member

Choose a reason for hiding this comment

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

This would also make more sense as something like nextVisit?

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
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// if the next segment(to a node) and previousl segment(from a node) is going in the same direction
// if the next segment(to a node) and previous segment(from a node) are going in the same direction

Comment on lines 721 to 724
let sameDirection = previousNodeIsForward !== null && nextNodeIsForward !== null && previousNodeIsForward === nextNodeIsForward

// we're visiting the same node we just visited from the same direction
if (sameDirection) {
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we just put the conditions in the ifs, if we're only going to fill in the variable to immediately condition on it? It's useful to have the variable name for documentation, but we could also just have a comment.

Comment on lines 700 to 727
let nextNodeOrder = nextPathWithNode?.order;
let previousNodeOrder = previousPathWithNode?.order;

// if the previous segment and the next segment is going to the same node
let nextNodeJustVisited = previousNodeOrder !== null && nextNodeOrder !== null && previousNodeOrder === nextNodeOrder;


// 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;


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;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

All this stuff is one big boolean function to fill in betweenCycles; I feel like it might be able to be simplified somehow.

If either side doesn't exist or has order (or isForward) not set, the answer is false. Otherwise, we are true if the orders and the orientations are equal, or if the previous order exceeds the next order.

You could turn that into one expression with comments. If we don't have to deal with real nulls in there, that would be something like:

// If there is a next and previous, we can be between cycles
let betweenCycles = (nextPathWithNode && previousPathWithNode &&
  // If we go back to the same node in the same orientation
  ((nextPathWithNode.order == previousPathWithNode.order && nextPathWithNode.isForward == previousPathWithNode.isForward) ||
  // Or we go back to a previous node in any orientation
  (nextPathWithNode.order < previousPathWithNode.order)));

let previousNodeOrder = previousPathWithNode?.order;

// if the previous segment and the next segment is going to the same node
let nextNodeJustVisited = previousNodeOrder !== null && nextNodeOrder !== null && previousNodeOrder === nextNodeOrder;
Copy link
Member

Choose a reason for hiding this comment

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

Are there actual nulls stored in order and/or isForward on the visits? Or should these be checking for undefined from the optional chaining ?. instead?

Comment on lines 890 to 892
// 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.

let nextNodeBehind = previousNodeOrder !== null && nextNodeOrder !== null && previousPathWithNode.order > nextNodeOrder;


reads[idx].path[pathIdx].betweenCycle = false;
Copy link
Member

Choose a reason for hiding this comment

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

I don't know if the idea of "being between a cycle" means anything to me. All the visits along the path that the read takes from a node back to the same node "participate in" the cycle, but here we're specifically interested in the visits where we're in the part of the cycle that goes from right to left when laid out, I think, right? Is there another way to say this?

Comment on lines 903 to 910
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]) {

@adamnovak adamnovak merged commit e23ffed into vgteam:master Feb 26, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Layout of reads going in cycles is bad
2 participants