Skip to content

Commit

Permalink
switch arc -> quadcurve
Browse files Browse the repository at this point in the history
  • Loading branch information
lbergelson committed Oct 4, 2023
1 parent fa85202 commit b1affef
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 70 deletions.
17 changes: 7 additions & 10 deletions src/main/java/org/broad/igv/ui/supdiagram/AlignmentArrow.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.broad.igv.feature.Strand;

import java.awt.*;
import java.awt.geom.Point2D;

/**
* An arrow shape that represents a single Supplementary Read in the supplementary alignment diagram
Expand Down Expand Up @@ -31,17 +32,13 @@ public AlignmentArrow(int midline, int height, int left, int right, Strand stran
invalidate();
}

public int getTip() {
if (strand == Strand.NEGATIVE) {
return xpoints[0];
}
return xpoints[3];
public Point2D getTip() {
int x = strand == Strand.NEGATIVE ? xpoints[0] : xpoints[3];
return new Point2D.Double(x, ypoints[0]);
}

public int getTail() {
if (strand == Strand.NEGATIVE) {
return xpoints[3];
}
return xpoints[0];
public Point2D getTail() {
int x = strand == Strand.NEGATIVE ? xpoints[3] : xpoints[0];
return new Point2D.Double(x, ypoints[0]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
Expand All @@ -23,7 +26,6 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collector;
import java.util.stream.Collectors;

class SupplementalAlignmentDiagram extends JComponent {
Expand All @@ -39,14 +41,17 @@ class SupplementalAlignmentDiagram extends JComponent {
public static final Color PRIMARY_BORDER_COLOR = Color.DARK_GRAY;
public static final int DEFAULT_WIDTH = 500;

private Rectangle2D chrDiagramBounds = null;
private Rectangle2D readDiagramBounds = null;

//This keeps track of the polygons on screen so it's possible to do bounds checks for mouse interaction
private final Map<AlignmentArrow, SupplementaryAlignment> elementsOnScreen = new LinkedHashMap<>();
private final Set<SupplementaryAlignment> selected = new LinkedHashSet<>();
private final SupplementaryAlignment.SupplementaryGroup toDraw;

public SupplementalAlignmentDiagram(final SupplementaryAlignment.SupplementaryGroup toDraw) {
this.toDraw = toDraw;

this.setBackground(Color.WHITE);
this.addMouseMotionListener(new IGVMouseInputAdapter() {

@Override
Expand Down Expand Up @@ -99,15 +104,21 @@ public void mouseClicked(final MouseEvent e) {
@Override
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
setBackground(Color.WHITE);
((Graphics2D) g).setComposite(getAlphaComposite());
g.setColor(Color.LIGHT_GRAY);
elementsOnScreen.clear();
final int mid = getHeight() / 3;
Map<SupplementaryAlignment, AlignmentArrow> saInPositionOrder = drawAlignmentsInCondensedChromosomeOrder((Graphics2D) g.create(), toDraw, selected, getWidth(), mid);
final int halfHeight = getHeight() / 2;
final int width = getWidth();
chrDiagramBounds =new Rectangle2D.Float(0,0, width, halfHeight);
readDiagramBounds = new Rectangle2D.Float(0, halfHeight, width, halfHeight);
Map<SupplementaryAlignment, AlignmentArrow> saInPositionOrder = drawAlignmentsInCondensedChromosomeOrder((Graphics2D) g.create(), toDraw, selected, width, mid);
drawArcs((Graphics2D) g.create(), toDraw, selected, mid, saInPositionOrder);
drawContigLabels((Graphics2D) g.create(), mid + 15, saInPositionOrder);
final int readOrderMidline = 2 * getHeight() / 3;
Map<SupplementaryAlignment, AlignmentArrow> saInReadOrder = drawAlignmentsInReadOrder((Graphics2D) g.create(), toDraw, selected, getWidth(), readOrderMidline);
Map<SupplementaryAlignment, AlignmentArrow> saInReadOrder = drawAlignmentsInReadOrder((Graphics2D) g.create(), toDraw, selected, width, readOrderMidline);
drawArcs((Graphics2D) g.create(), toDraw, selected, mid, saInReadOrder);
drawReadLengthLabel(((Graphics2D) g.create()), readOrderMidline + 15 , saInReadOrder);
saInPositionOrder.forEach((k, v) -> elementsOnScreen.put(v, k));
saInReadOrder.forEach((k, v) -> elementsOnScreen.put(v, k));
Expand All @@ -128,14 +139,27 @@ private void drawReadLengthLabel(final Graphics2D g, final int mid, final Map<Su
drawCenteredStringWithRangeLines(g, mid, "Length in bases = " + baseCount, left, right);
}

@Override
public String getToolTipText(final MouseEvent event) {
final String value;
if(chrDiagramBounds.contains(event.getPoint())){
value = "chr ordered";
} else if(readDiagramBounds.contains(event.getPoint())){
value = "read ordered";
} else {
value = "nothing";
}
log.info(value + " " + event.getPoint().toString());
return value;
}

private void drawArcs(final Graphics2D g, final SupplementaryAlignment.SupplementaryGroup toDraw, final Set<SupplementaryAlignment> selected, final int mid, final Map<SupplementaryAlignment, AlignmentArrow> saToArrowMap) {
toDraw.iterateInReadOrder()
.forEachRemaining(sa -> {
SupplementaryAlignment next = toDraw.getNextInRead(sa);
if (next != null) {
final boolean highlight = selected.contains(sa) || selected.contains(next);
drawArc(g, mid, saToArrowMap.get(sa), saToArrowMap.get(next), highlight, getWidth(), getHeight() - mid);
drawArc(g, saToArrowMap.get(sa), saToArrowMap.get(next), highlight, getWidth(), getHeight() - mid);
}
});
}
Expand Down Expand Up @@ -236,19 +260,23 @@ private static Map<SupplementaryAlignment, AlignmentArrow> drawAlignmentsInConde
//now handle each span group
int spanLength = span.getLengthOnReference();
int spanSpaceAvailable = (int)getSpaceToUse((double) perContigAvailableSpace - (distinctSpans.size() -1) * scaledAlignmentGap, spanLength, totalSpansLength );
for(SupplementaryAlignment sa: groupedBySpan.get(span)){
final List<SupplementaryAlignment> activeSpanGroup = groupedBySpan.get(span);
for(int i = 0; i < activeSpanGroup.size(); i++){
//each read in the span is placed relatively within the space
SupplementaryAlignment sa = activeSpanGroup.get(i);
int scaledReadStart = (int)getSpaceToUse(spanSpaceAvailable, sa.getStart() - span.getStart(), spanLength);
int scaledReadEnd = (int)getSpaceToUse(spanSpaceAvailable, sa.getEnd() - span.getStart(), spanLength);
//arrows that overlap will appear overlapping one another
AlignmentArrow readArrow = new AlignmentArrow(midline, ALIGNMENT_HEIGHT,
int heightOffset = ALIGNMENT_HEIGHT - (int)((2*ALIGNMENT_HEIGHT) * (1.0/(activeSpanGroup.size()+1.0))*(i+1.0));
final AlignmentArrow readArrow = new AlignmentArrow(midline + 2*heightOffset, ALIGNMENT_HEIGHT,
spanStart + scaledReadStart,
spanStart + scaledReadEnd,
sa.getStrand());

positions.put(sa, readArrow);
spanStart += spanSpaceAvailable + scaledAlignmentGap;
}

}
spanStart += spanSpaceAvailable + scaledAlignmentGap;
}
//move contig start forward
contigStart = contigEnd + scaledContigGap;
Expand Down Expand Up @@ -373,29 +401,30 @@ private static void drawCenteredStringWithRangeLines(final Graphics2D g, final i
}
}

private void drawArc(final Graphics2D g, final int mid, final AlignmentArrow currentPos, final AlignmentArrow nextPos, final boolean highlight, final int width, final int height) {
//todo, remove mid, it's totally unnecessary and confusing since the arrow has the coordinates
int from = currentPos.getTip();
int to = nextPos.getTail();
private void drawArc(final Graphics2D g, final AlignmentArrow currentPos, final AlignmentArrow nextPos, final boolean highlight, final int width, final int height) {
QuadCurve2D c = new QuadCurve2D.Float();
Point2D from = currentPos.getTip();
Point2D to = nextPos.getTail();
g.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER));
int centerX = (from + to) / 2;
int arcWidth = Math.abs(from - to);
double centerX = (from.getX() + to.getX()) / 2;
double arcWidth = Math.abs(from.getX() - to.getX());
final double ARC_HEIGHT_LIMIT = 0.8;
int arcHeight = (int) ((double) arcWidth / width * height * ARC_HEIGHT_LIMIT);
int centerY = mid - ALIGNMENT_HEIGHT / 2;
Point2D control = new Point2D.Double(centerX, arcHeight);
c.setCurve(from, control, to);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if (highlight) {
g.setColor(SupplementaryAlignmentDiagramDialog.ARC_HIGHLIGHT_COLOR);
} else {
g.setColor(Color.GRAY);
}
g.drawArc(centerX - arcWidth / 2, centerY - arcHeight / 2, arcWidth, arcHeight, 0, 180);
drawPoint(g, to, centerY);
g.draw(c);
drawPoint(g, to);
}


private static void drawPoint(Graphics2D g, int x, int y) {
g.draw(new Ellipse2D.Double(x - 1, y - 1, 2, 2));
private static void drawPoint(Graphics2D g, Point2D point) {
g.draw(new Ellipse2D.Double(point.getX() - 1, point.getY() - 1, 2, 2));
}

private static void writeContigName(final Graphics g, final String contig, final double x, final int y) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -42,57 +43,23 @@ public class SupplementaryAlignmentDiagramDialog extends JDialog {

public static final Color ARC_HIGHLIGHT_COLOR = Color.CYAN;

private final SupplementalAlignmentDiagram diagram;

public SupplementaryAlignmentDiagramDialog(Frame frame, Alignment alignment, Dimension dimension) {
super(frame);
setSize(dimension);
JPanel readPanel = new JPanel();
readPanel.setLayout(new BorderLayout());
SupplementalAlignmentDiagram diagram = new SupplementalAlignmentDiagram(new SupplementaryAlignment.SupplementaryGroup(alignment));
readPanel.add(diagram, BorderLayout.CENTER);
diagram = new SupplementalAlignmentDiagram(new SupplementaryAlignment.SupplementaryGroup(alignment));
this.add(diagram);

//todo make this only on top of the parent window, not all applications?
setAlwaysOnTop(true);
setTitle("SAs for " + alignment.getReadName());

setDefaultCloseOperation(DISPOSE_ON_CLOSE);

JButton help = new JButton("?");
help.setPreferredSize(new Dimension( 15,15));

String helpText =
"A diagram showing the connections between supplementary reads. Each arrow is one read in the group. \n" +
"The direction of the arrow indicates forward/reverse strand alignment. Arcs show which bases are " +
"physically adjacent to each other in the original molecule. \n" +
"\n" +
"The top diagram shows the reads laid out in order according to their alignment on the genome. \n" +
"The bottom diagram shows how they are laid out in the unaligned read. \n" +
"\n" +
"Mouse over a read to see it highlighted in both layouts. \n " +
"Click to navigate to that read. \n" +
"Shift+Click to add another split screen pane.";

help.addActionListener( a -> {
JTextArea textArea = new JTextArea(helpText);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
JScrollPane scrollPane = new JScrollPane(textArea);
JOptionPane pane = new JOptionPane(scrollPane, JOptionPane.PLAIN_MESSAGE);
JDialog dialog = pane.createDialog(null, "Supplementary Alignment Help");
dialog.setAlwaysOnTop(true);
dialog.setResizable(true);

dialog.setSize(400, 400);
dialog.setVisible(true);
});

JPanel helpPanel = new JPanel(new BorderLayout());
helpPanel.add(help, BorderLayout.EAST);
helpPanel.setPreferredSize(help.getPreferredSize());
readPanel.add(helpPanel, BorderLayout.SOUTH);
this.add(readPanel);

diagram.setVisible(true);
}


//for quick component testing
public static void main(String[] args){
final SAMRecordSetBuilder samRecords = new SAMRecordSetBuilder();
Expand Down

0 comments on commit b1affef

Please sign in to comment.