From b1affefb99fbe551136457bd9144e923e24d4722 Mon Sep 17 00:00:00 2001 From: Louis Bergelson Date: Wed, 4 Oct 2023 15:03:58 -0400 Subject: [PATCH] switch arc -> quadcurve --- .../igv/ui/supdiagram/AlignmentArrow.java | 17 ++--- .../SupplementalAlignmentDiagram.java | 69 +++++++++++++------ .../SupplementaryAlignmentDiagramDialog.java | 47 ++----------- 3 files changed, 63 insertions(+), 70 deletions(-) diff --git a/src/main/java/org/broad/igv/ui/supdiagram/AlignmentArrow.java b/src/main/java/org/broad/igv/ui/supdiagram/AlignmentArrow.java index 3350ea0229..7502b059df 100644 --- a/src/main/java/org/broad/igv/ui/supdiagram/AlignmentArrow.java +++ b/src/main/java/org/broad/igv/ui/supdiagram/AlignmentArrow.java @@ -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 @@ -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]); } } diff --git a/src/main/java/org/broad/igv/ui/supdiagram/SupplementalAlignmentDiagram.java b/src/main/java/org/broad/igv/ui/supdiagram/SupplementalAlignmentDiagram.java index eca9a38214..8cfb339d6f 100644 --- a/src/main/java/org/broad/igv/ui/supdiagram/SupplementalAlignmentDiagram.java +++ b/src/main/java/org/broad/igv/ui/supdiagram/SupplementalAlignmentDiagram.java @@ -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; @@ -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 { @@ -39,6 +41,9 @@ 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 elementsOnScreen = new LinkedHashMap<>(); private final Set selected = new LinkedHashSet<>(); @@ -46,7 +51,7 @@ class SupplementalAlignmentDiagram extends JComponent { public SupplementalAlignmentDiagram(final SupplementaryAlignment.SupplementaryGroup toDraw) { this.toDraw = toDraw; - + this.setBackground(Color.WHITE); this.addMouseMotionListener(new IGVMouseInputAdapter() { @Override @@ -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 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 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 saInReadOrder = drawAlignmentsInReadOrder((Graphics2D) g.create(), toDraw, selected, getWidth(), readOrderMidline); + Map 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)); @@ -128,6 +139,19 @@ private void drawReadLengthLabel(final Graphics2D g, final int mid, final Map selected, final int mid, final Map saToArrowMap) { toDraw.iterateInReadOrder() @@ -135,7 +159,7 @@ private void drawArcs(final Graphics2D g, final SupplementaryAlignment.Supplemen 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); } }); } @@ -236,19 +260,23 @@ private static Map 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 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; @@ -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) { diff --git a/src/main/java/org/broad/igv/ui/supdiagram/SupplementaryAlignmentDiagramDialog.java b/src/main/java/org/broad/igv/ui/supdiagram/SupplementaryAlignmentDiagramDialog.java index fc0dcbb292..26f7211805 100644 --- a/src/main/java/org/broad/igv/ui/supdiagram/SupplementaryAlignmentDiagramDialog.java +++ b/src/main/java/org/broad/igv/ui/supdiagram/SupplementaryAlignmentDiagramDialog.java @@ -34,6 +34,7 @@ import javax.swing.*; import java.awt.*; +import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.List; @@ -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();