From 315636903a6a27b4108e37e4aa12e4fe214311af Mon Sep 17 00:00:00 2001 From: Louis Bergelson Date: Wed, 28 Jun 2023 16:28:58 -0400 Subject: [PATCH] Update with many changes to improve supplemental alignment display and navigation. Major rework of the previous alignment dialog popup to make it interactive and clearer. Use it to navigate between reads in a supplemenary alignment group. Click to move to the selected read. Shift + Click to add it as a split screen. When jumping to a selected read/mate the selected reads will be now be sorted to the top. Renamed several classes and moved them to the ui/supdiagram package Refactoring some methods around Frame creation, this could still be improved. --- src/main/java/module-info.java | 1 + .../java/org/broad/igv/jbrowse/Chord.java | 2 +- .../java/org/broad/igv/lists/GeneList.java | 19 +- .../broad/igv/sam/AlignmentDataManager.java | 4 +- .../org/broad/igv/sam/AlignmentTrack.java | 25 +- .../org/broad/igv/sam/AlignmentTrackMenu.java | 73 +-- .../java/org/broad/igv/sam/SAMAlignment.java | 11 +- .../broad/igv/sam/SupplementaryAlignment.java | 125 ++--- .../org/broad/igv/sam/SupplementaryGroup.java | 147 ++++++ .../org/broad/igv/track/TrackMenuUtils.java | 7 +- .../broad/igv/ui/AlignmentDiagramFrame.java | 234 --------- src/main/java/org/broad/igv/ui/IGV.java | 2 +- .../org/broad/igv/ui/TooltipTextFrame.java | 4 +- .../org/broad/igv/ui/panel/FrameManager.java | 62 ++- .../igv/ui/supdiagram/AlignmentArrow.java | 44 ++ .../SupplementalAlignmentDiagram.java | 460 ++++++++++++++++++ .../SupplementaryAlignmentDiagramDialog.java | 79 +++ 17 files changed, 877 insertions(+), 422 deletions(-) create mode 100644 src/main/java/org/broad/igv/sam/SupplementaryGroup.java delete mode 100644 src/main/java/org/broad/igv/ui/AlignmentDiagramFrame.java create mode 100644 src/main/java/org/broad/igv/ui/supdiagram/AlignmentArrow.java create mode 100644 src/main/java/org/broad/igv/ui/supdiagram/SupplementalAlignmentDiagram.java create mode 100644 src/main/java/org/broad/igv/ui/supdiagram/SupplementaryAlignmentDiagramDialog.java diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 15bb2a73f2..9f8d30a72f 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -6,6 +6,7 @@ exports org.broad.igv.logging; exports org.broad.igv.util.liftover; exports org.broad.igv.sam.smrt; + exports org.broad.igv.ui.supdiagram; requires com.google.common; requires commons.math3; diff --git a/src/main/java/org/broad/igv/jbrowse/Chord.java b/src/main/java/org/broad/igv/jbrowse/Chord.java index 36c1a8c15b..cdbfd7982c 100644 --- a/src/main/java/org/broad/igv/jbrowse/Chord.java +++ b/src/main/java/org/broad/igv/jbrowse/Chord.java @@ -65,7 +65,7 @@ public static List fromSAString(Alignment a) { c.refName = shortName(a.getChr()); c.start = a.getStart(); c.end = a.getEnd(); - c.mate = new Mate(shortName(sa.chr), sa.start, sa.start + sa.getLenOnRef()); + c.mate = new Mate(shortName(sa.chr), sa.start, sa.start + sa.getLengthOnReference()); chords.add(c); } } diff --git a/src/main/java/org/broad/igv/lists/GeneList.java b/src/main/java/org/broad/igv/lists/GeneList.java index 12b91ceedc..d68bca8b74 100644 --- a/src/main/java/org/broad/igv/lists/GeneList.java +++ b/src/main/java/org/broad/igv/lists/GeneList.java @@ -26,7 +26,6 @@ package org.broad.igv.lists; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -43,7 +42,11 @@ public class GeneList { private List loci; public GeneList(String name, String description, String group, List loci) { - init(name, description, group, loci); + this.group = group; + this.description = description; + this.name = name; + //We do this to guarantee that certain operations will be supported + this.loci = loci; } public GeneList(String name, List loci) { @@ -54,14 +57,6 @@ public GeneList() { this.group = GeneListManager.USER_GROUP; } - private void init(String name, String description, String group, List loci) { - this.group = group; - this.description = description; - this.name = name; - //We do this to guarantee that certain operations will be supported - this.loci = loci; - } - public String getName() { return name; } @@ -76,13 +71,13 @@ public int size() { public void add(String gene) { if (loci == null) { - loci = new ArrayList(1); + loci = new ArrayList<>(1); } try { //Can't guarantee that list will support this operation loci.add(gene); } catch (Exception e) { - loci = new ArrayList(loci); + loci = new ArrayList<>(loci); loci.add(gene); } } diff --git a/src/main/java/org/broad/igv/sam/AlignmentDataManager.java b/src/main/java/org/broad/igv/sam/AlignmentDataManager.java index 1aaba591d2..ac2813e0e2 100644 --- a/src/main/java/org/broad/igv/sam/AlignmentDataManager.java +++ b/src/main/java/org/broad/igv/sam/AlignmentDataManager.java @@ -265,7 +265,7 @@ public AlignmentInterval getLoadedInterval(ReferenceFrame frame) { } public AlignmentInterval getLoadedInterval(ReferenceFrame frame, boolean includeOverlaps) { - // Search for interval completely containining reference frame region + // Search for interval completely containing reference frame region for (AlignmentInterval interval : intervalCache) { if (interval.contains(frame.getCurrentRange())) { return interval; @@ -358,8 +358,10 @@ public void load(ReferenceFrame frame, currentlyLoading = null; } + IGVEventBus.getInstance().post(new DataLoadedEvent(frame)); + } /** diff --git a/src/main/java/org/broad/igv/sam/AlignmentTrack.java b/src/main/java/org/broad/igv/sam/AlignmentTrack.java index 4469b705c5..ffb22c5f0d 100644 --- a/src/main/java/org/broad/igv/sam/AlignmentTrack.java +++ b/src/main/java/org/broad/igv/sam/AlignmentTrack.java @@ -53,6 +53,7 @@ import org.broad.igv.ui.panel.IGVPopupMenu; import org.broad.igv.ui.panel.ReferenceFrame; import org.broad.igv.ui.util.MessageUtils; +import org.broad.igv.ui.util.UIUtilities; import org.broad.igv.util.ResourceLocator; import org.broad.igv.util.StringUtils; import org.broad.igv.util.blat.BlatClient; @@ -82,6 +83,14 @@ public class AlignmentTrack extends AbstractTrack implements IGVEventObserver { // Alignment colors static final Color DEFAULT_ALIGNMENT_COLOR = new Color(185, 185, 185); //200, 200, 200); + public static void sortSelectedReadsToTheTop(final Set selectedReadNames) { + //copy this in case it changes out from under us + Set selectedReadNameCopy = new HashSet<>(selectedReadNames); + //Run this on the event thread to make sure it happens after loading begins + UIUtilities.invokeOnEventThread(() -> + IGV.getInstance().sortAlignmentTracks(SortOption.NONE, null, null, false, selectedReadNameCopy)); + } + public enum ColorOption { INSERT_SIZE, READ_STRAND, @@ -188,7 +197,7 @@ enum OrientationType { private static final int GROUP_MARGIN = 5; private static final int TOP_MARGIN = 20; private static final int DS_MARGIN_0 = 2; - private static final int DOWNAMPLED_ROW_HEIGHT = 3; + private static final int DOWNSAMPLED_ROW_HEIGHT = 3; private static final int INSERTION_ROW_HEIGHT = 9; public enum BisulfiteContext { @@ -373,8 +382,7 @@ public void receiveEvent(Object event) { // Trim insertionInterval map to current frames - } else if (event instanceof AlignmentTrackEvent) { - AlignmentTrackEvent e = (AlignmentTrackEvent) event; + } else if (event instanceof AlignmentTrackEvent e) { AlignmentTrackEvent.Type eventType = e.getType(); switch (eventType) { case ALLELE_THRESHOLD: @@ -388,8 +396,7 @@ public void receiveEvent(Object event) { break; } - } else if (event instanceof DataLoadedEvent) { - final DataLoadedEvent dataLoaded = (DataLoadedEvent) event; + } else if (event instanceof DataLoadedEvent dataLoaded) { actionToPerformOnFrameLoad.computeIfPresent(dataLoaded.getReferenceFrame(), (k, v) -> { v.accept(k); return null; @@ -475,7 +482,7 @@ public int getHeight() { int nGroups = dataManager.getMaxGroupCount(); int h = Math.max(minHeight, getNLevels() * getRowHeight() + nGroups * GROUP_MARGIN + TOP_MARGIN - + DS_MARGIN_0 + DOWNAMPLED_ROW_HEIGHT); + + DS_MARGIN_0 + DOWNSAMPLED_ROW_HEIGHT); return Math.max(minimumHeight, h); } @@ -542,7 +549,7 @@ public void render(RenderContext context, Rectangle rect) { rect.y += DS_MARGIN_0; downsampleRect = new Rectangle(rect); - downsampleRect.height = DOWNAMPLED_ROW_HEIGHT; + downsampleRect.height = DOWNSAMPLED_ROW_HEIGHT; renderDownsampledIntervals(context, downsampleRect); alignmentsRect = new Rectangle(rect); @@ -694,7 +701,7 @@ private void renderAlignments(RenderContext context, Rectangle inputRect) { public void renderExpandedInsertion(InsertionMarker insertionMarker, RenderContext context, Rectangle inputRect) { boolean leaveMargin = getDisplayMode() != DisplayMode.SQUISHED; - inputRect.y += DS_MARGIN_0 + DOWNAMPLED_ROW_HEIGHT + DS_MARGIN_0; + inputRect.y += DS_MARGIN_0 + DOWNSAMPLED_ROW_HEIGHT + DS_MARGIN_0; final AlignmentInterval loadedInterval = dataManager.getLoadedInterval(context.getReferenceFrame(), true); PackedAlignments groups = dataManager.getGroups(loadedInterval, renderOptions); @@ -911,7 +918,7 @@ public boolean handleDataClick(TrackClickEvent te) { return false; } - void setSelectedAlignment(Alignment alignment) { + public void setSelectedAlignment(Alignment alignment) { Color c = readNamePalette.get(alignment.getReadName()); selectedReadNames.put(alignment.getReadName(), c); } diff --git a/src/main/java/org/broad/igv/sam/AlignmentTrackMenu.java b/src/main/java/org/broad/igv/sam/AlignmentTrackMenu.java index 1dfd1d3bce..4ac33bc0f2 100644 --- a/src/main/java/org/broad/igv/sam/AlignmentTrackMenu.java +++ b/src/main/java/org/broad/igv/sam/AlignmentTrackMenu.java @@ -1,18 +1,16 @@ package org.broad.igv.sam; import htsjdk.samtools.SAMTag; -import htsjdk.samtools.util.Locatable; import org.broad.igv.Globals; import org.broad.igv.feature.Locus; import org.broad.igv.feature.Range; import org.broad.igv.feature.Strand; import org.broad.igv.jbrowse.CircularViewUtilities; -import org.broad.igv.lists.GeneList; import org.broad.igv.logging.LogManager; import org.broad.igv.logging.Logger; -import org.broad.igv.prefs.Constants; import org.broad.igv.prefs.PreferencesManager; import org.broad.igv.sam.mods.BaseModficationFilter; +import org.broad.igv.sam.mods.BaseModificationKey; import org.broad.igv.sam.mods.BaseModificationUtils; import org.broad.igv.sashimi.SashimiPlot; import org.broad.igv.tools.PFMExporter; @@ -20,7 +18,7 @@ import org.broad.igv.track.Track; import org.broad.igv.track.TrackClickEvent; import org.broad.igv.track.TrackMenuUtils; -import org.broad.igv.ui.AlignmentDiagramFrame; +import org.broad.igv.ui.supdiagram.SupplementaryAlignmentDiagramDialog; import org.broad.igv.ui.IGV; import org.broad.igv.ui.InsertSizeSettingsDialog; import org.broad.igv.ui.panel.FrameManager; @@ -203,7 +201,7 @@ private void addShowChimericRegions(final AlignmentTrack alignmentTrack, final T try { List supplementaryAlignments = SupplementaryAlignment.parseFromSATag(saTag); alignmentTrack.setSelectedAlignment(clickedAlignment); - addNewLociToFrames(e.getFrame(), supplementaryAlignments, alignmentTrack.getSelectedReadNames().keySet()); + FrameManager.addNewLociToFrames(e.getFrame(), supplementaryAlignments, alignmentTrack.getSelectedReadNames().keySet()); } catch (final Exception ex) { MessageUtils.showMessage("Failed to handle SA tag: " + saTag + " due to " + ex.getMessage()); item.setEnabled(false); @@ -221,7 +219,7 @@ private void addShowDiagram(final TrackClickEvent e, final Alignment clickedAlig item.setEnabled(true); item.addActionListener(aEvt -> { try { - final AlignmentDiagramFrame frame = new AlignmentDiagramFrame(clickedAlignment, new Dimension(500, 100)); + final SupplementaryAlignmentDiagramDialog frame = new SupplementaryAlignmentDiagramDialog(IGV.getInstance().getMainFrame(), clickedAlignment, new Dimension(500, 250)); frame.setVisible(true); } catch (final Exception ex) { MessageUtils.showMessage("Failed to handle SA tag: " + clickedAlignment.getAttribute(SAMTag.SA.name()) + " due to " + ex.getMessage()); @@ -683,7 +681,7 @@ void addColorByMenuItem() { // Base modifications JRadioButtonMenuItem bmMenuItem; - Set allModifications = dataManager.getAllBaseModificationKeys().stream().map(bmKey -> bmKey.getModification()).collect(Collectors.toSet()); + Set allModifications = dataManager.getAllBaseModificationKeys().stream().map(BaseModificationKey::getModification).collect(Collectors.toSet()); if (allModifications.size() > 0) { BaseModficationFilter filter = renderOptions.getBasemodFilter(); boolean groupByStrand = alignmentTrack.getPreferences().getAsBoolean(BASEMOD_GROUP_BY_STRAND); @@ -1228,7 +1226,7 @@ private void gotoMate(final ReferenceFrame frame, Alignment alignment) { int newStart = (int) Math.max(0, (start + (alignment.getEnd() - alignment.getStart()) / 2 - range / 2)); int newEnd = newStart + (int) range; frame.jumpTo(chr, newStart, newEnd); - sortSelectedReadsToTheTop(alignmentTrack.getSelectedReadNames().keySet()); + AlignmentTrack.sortSelectedReadsToTheTop(alignmentTrack.getSelectedReadNames().keySet()); frame.recordHistory(); } else { MessageUtils.showMessage("Alignment does not have mate, or it is not mapped."); @@ -1246,70 +1244,13 @@ private void splitScreenMate(ReferenceFrame frame, Alignment alignment) { ReadMate mate = alignment.getMate(); if (mate != null && mate.isMapped()) { alignmentTrack.setSelectedAlignment(alignment); - addNewLociToFrames(frame, List.of(mate), alignmentTrack.getSelectedReadNames().keySet()); + FrameManager.addNewLociToFrames(frame, List.of(mate), alignmentTrack.getSelectedReadNames().keySet()); } else { MessageUtils.showMessage("Alignment does not have mate, or it is not mapped."); } } } - private static void addNewLociToFrames(final ReferenceFrame frame, final List toIncludeInSplit, final Set selectedReadNames) { - final List newLoci = toIncludeInSplit.stream() - .map(locatable -> getLocusStringForAlignment(frame, locatable)) - .collect(Collectors.toList()); - List loci = createLociList(frame, newLoci); - String listName = String.join(" ", loci); // TODO check the trailing " " was unnecessary - //Need to sort the frames by position - GeneList geneList = new GeneList(listName, loci); - geneList.sort(Comparator.comparing(Locus::fromString, SortOption.POSITION_COMPARATOR)); - IGV.getInstance().getSession().setCurrentGeneList(geneList); - IGV.getInstance().resetFrames(); - - /* - We want the sort to happen after the frame refresh / track loading begins. - This puts the sort onto the event thread so that it happens after loading has already started. - Since loading reads happens asynchronously on a different thread from the event thread, it is likely - that the loading won't be done by the time the sort fires. In that case the sort will be set as the - action to perform when the load is finished - See {@link AlignmentTrack#sortRows(SortOption, Double, String, boolean, Set)} - */ - sortSelectedReadsToTheTop(selectedReadNames); - } - - private static void sortSelectedReadsToTheTop(final Set selectedReadNames) { - //copy this in case it changes out from under us - Set selectedReadNameCopy = new HashSet<>(selectedReadNames); - UIUtilities.invokeOnEventThread(() -> - IGV.getInstance().sortAlignmentTracks(SortOption.NONE, null, null, false, selectedReadNameCopy)); - } - - private static List createLociList(final ReferenceFrame frame, final List lociToAdd) { - final List loci = new ArrayList<>(FrameManager.getFrames().size() + lociToAdd.size()); - if (FrameManager.isGeneListMode()) { - for (ReferenceFrame ref : FrameManager.getFrames()) { - //If the frame-name is a locus, we use it unaltered - //Don't want to reprocess, easy to get off-by-one - String name = ref.getName(); - loci.add(Locus.fromString(name) != null ? name : ref.getFormattedLocusString()); - } - } else { - loci.add(frame.getFormattedLocusString()); - } - loci.addAll(lociToAdd); - return loci; - } - - private static String getLocusStringForAlignment(final ReferenceFrame frame, final Locatable alignment) { - int adjustedMateStart = alignment.getStart() - 1; - - // Generate a locus string for the alignment. Keep the window width (in base pairs) == to the current range - Range range = frame.getCurrentRange(); - int length = range.getLength(); - int start = Math.max(0, adjustedMateStart - length / 2); - int end = start + length; - return Locus.getFormattedLocusString(alignment.getContig(), start, end); - } - /** * Get the most "specific" alignment at the specified location. Specificity refers to the smallest alignment diff --git a/src/main/java/org/broad/igv/sam/SAMAlignment.java b/src/main/java/org/broad/igv/sam/SAMAlignment.java index 3ae7d84dea..42d536c155 100644 --- a/src/main/java/org/broad/igv/sam/SAMAlignment.java +++ b/src/main/java/org/broad/igv/sam/SAMAlignment.java @@ -974,7 +974,7 @@ private String getMlTagString(SAMRecord.SAMTagAndValue tag) { private String getSupplAlignmentString() { StringBuilder sb = new StringBuilder(); - sb.append("SupplementaryAlignments"); + sb.append("Supplementary Alignments"); final List supplementaryAlignments = getSupplementaryAlignments(); final int insertionIndex = SupplementaryAlignment.getInsertionIndex(this, supplementaryAlignments); int i = 0; @@ -996,14 +996,13 @@ private String getSupplAlignmentString() { } private String getThisReadDescriptionForSAList() { - return "
" + - "" + return "
" + + "" + chr + ":" + Globals.DECIMAL_FORMAT.format(getAlignmentStart() + 1) + "-" + Globals.DECIMAL_FORMAT.format(getAlignmentEnd()) - + " (" + getReadStrand().toShortString() + ")" + - ""; + + " (" + this.getReadStrand().toShortString() + ") = " + Globals.DECIMAL_FORMAT.format(getLengthOnReference()) + "bp @MAPQ " + this.getMappingQuality() + " NM" + getAttribute(SAMTag.NM) + + "
"; } - public float getScore() { return getMappingQuality(); } diff --git a/src/main/java/org/broad/igv/sam/SupplementaryAlignment.java b/src/main/java/org/broad/igv/sam/SupplementaryAlignment.java index 8991b3e89d..9f4e97e880 100644 --- a/src/main/java/org/broad/igv/sam/SupplementaryAlignment.java +++ b/src/main/java/org/broad/igv/sam/SupplementaryAlignment.java @@ -1,11 +1,13 @@ package org.broad.igv.sam; import htsjdk.samtools.Cigar; +import htsjdk.samtools.CigarElement; import htsjdk.samtools.SAMTag; import htsjdk.samtools.TextCigarCodec; import htsjdk.samtools.util.Locatable; import org.broad.igv.Globals; import org.broad.igv.feature.Strand; +import org.broad.igv.feature.genome.ChromosomeNameComparator; import org.broad.igv.feature.genome.Genome; import org.broad.igv.feature.genome.GenomeManager; import org.broad.igv.logging.LogManager; @@ -17,7 +19,7 @@ import java.util.Comparator; import java.util.Iterator; import java.util.List; -import java.util.TreeSet; +import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -27,18 +29,15 @@ public class SupplementaryAlignment implements Locatable { public final String chr; public final int start; - public Strand getStrand() { - return strand; - } - private final Strand strand; private final Cigar cigar; public final int mapQ; public final int numMismatches; - //potentially expensive with very long reads, compute it lazily - private Integer lenOnRef = null; + //potentially expensive with very long reads, compute lazily + private Integer lenOnRef = null; //number of reference bases covered + private Integer numberOfAlignedBases = null; //number of bases in read which are aligned public SupplementaryAlignment(String chr, int start, Strand strand, Cigar cigar, int mapQ, int numMismatches){ this.chr = chr; @@ -49,6 +48,10 @@ public SupplementaryAlignment(String chr, int start, Strand strand, Cigar cigar, this.numMismatches = numMismatches; } + public Strand getStrand() { + return strand; + } + public static int getInsertionIndex(final Alignment alignment, final List supplementaryAlignments) { final SupplementaryAlignment alignmentAsSupplementary = new SupplementaryAlignment(null, 0, alignment.getReadStrand(), alignment.getCigar(), 0, 0); //We're just using this as a placeholder for sorting since Alignment @@ -107,15 +110,15 @@ public static SupplementaryNeighbors getAdjacentSupplementaryReads(final Alignme public String printString() { // chr6:43,143,415-43,149,942 (-) @ MAPQ 60 NM 763 // be sure to adjust start by + 1 because SATag is 1 based but IGV internal is 0 based - return chr + ":" + Globals.DECIMAL_FORMAT.format(start + 1) + "-" + Globals.DECIMAL_FORMAT.format(start + getLenOnRef()) - + " (" + strand.toShortString() + ") = " + Globals.DECIMAL_FORMAT.format(getLenOnRef()) + "bp @MAPQ " + mapQ + " NM" + numMismatches; + return chr + ":" + Globals.DECIMAL_FORMAT.format(start + 1) + "-" + Globals.DECIMAL_FORMAT.format(start + getLengthOnReference()) + + " (" + strand.toShortString() + ") = " + Globals.DECIMAL_FORMAT.format(getLengthOnReference()) + "bp @MAPQ " + mapQ + " NM" + numMismatches; } public static List parseFromSATag(String saTag){ return Arrays.stream(Globals.semicolonPattern.split(saTag)) .map(SupplementaryAlignment::fromSingleSaTagRecord) .sorted(LEADING_CLIP_COMPARATOR) - .collect(Collectors.toList()); + .toList(); } public static SupplementaryAlignment fromSingleSaTagRecord(String saTagRecord){ @@ -169,16 +172,44 @@ public int getStart() { @Override public int getEnd() { - return start + getLenOnRef(); + return start + getLengthOnReference(); } - public int getLenOnRef() { + @Override + public int getLengthOnReference() { if(lenOnRef == null) { lenOnRef = cigar.getReferenceLength(); } return cigar.getReferenceLength(); } + /** + * get the count of non-clipped bases which are in the read + * this differs from {@link #getLengthOnReference()} because it includes insertions bases but not deletions + * @return + */ + public int getNumberOfAlignedBases(){ + if( numberOfAlignedBases == null) { + int length = 0; + for(CigarElement element : cigar) { + switch (element.getOperator()) { + case M: + case I: + case EQ: + case X: + length += element.getLength(); + break; + default: + break; + } + } + numberOfAlignedBases = length; + } + return numberOfAlignedBases; + } + + + static class SupplementaryNeighbors { final Alignment alignment; final SupplementaryAlignment previous; @@ -204,74 +235,4 @@ public SupplementaryAlignment nextIgnoreOrientation (){ } - public static class SupplementaryGroup { - private final Alignment alignment; - private final TreeSet readOrder; - private final TreeSet positionOrder; - - private final Adapter adapter; - - public SupplementaryGroup(Alignment alignment){ - final List supplementaryAlignments; - if( alignment instanceof SAMAlignment){ - supplementaryAlignments = ((SAMAlignment) alignment).getSupplementaryAlignments(); - } else { - final Object rawSATag = alignment.getAttribute(SAMTag.SA.name()); - supplementaryAlignments = rawSATag == null ? null : new ArrayList<>(parseFromSATag(rawSATag.toString())); - } - - this.alignment = alignment; - this.adapter = new Adapter(alignment); - final List combined = new ArrayList<>(supplementaryAlignments); - combined.add(adapter); - readOrder = new TreeSet<>(LEADING_CLIP_COMPARATOR); - readOrder.addAll(combined); - positionOrder = new TreeSet<>(SortOption.POSITION_COMPARATOR); - positionOrder.addAll(combined); - } - - public SupplementaryAlignment getNextInRead(SupplementaryAlignment alignment){ - return readOrder.higher(alignment); - } - - public SupplementaryAlignment getPreviousInRead(SupplementaryAlignment alignment){ - return readOrder.lower(alignment); - } - public SupplementaryAlignment getNextPosition(SupplementaryAlignment alignment){ - return positionOrder.higher(alignment); - } - - public SupplementaryAlignment getPreviousPosition(SupplementaryAlignment alignment){ - return positionOrder.lower(alignment); - } - - public Adapter getAdapter() { - return adapter; - } - - public Iterator iterateInReadOrder(){ - return readOrder.iterator(); - } - - public Iterator iterateInPositionOrder(){ - return positionOrder.iterator(); - } - - public Stream streamInReadOrder(){ - return readOrder.stream(); - } - public Stream streamInPositionOrder(){ - return positionOrder.stream(); - } - - public int size(){ - return readOrder.size(); - } - - private static class Adapter extends SupplementaryAlignment{ - public Adapter(Alignment a) { - super(a.getChr(), a.getStart(), a.getReadStrand(), a.getCigar(), a.getMappingQuality(), a.getAttribute(SAMTag.NM.name()) == null ? 0 : (int)a.getAttribute(SAMTag.NM.name())); - } - } - } } \ No newline at end of file diff --git a/src/main/java/org/broad/igv/sam/SupplementaryGroup.java b/src/main/java/org/broad/igv/sam/SupplementaryGroup.java new file mode 100644 index 0000000000..c9b0b14e69 --- /dev/null +++ b/src/main/java/org/broad/igv/sam/SupplementaryGroup.java @@ -0,0 +1,147 @@ +package org.broad.igv.sam; + +import htsjdk.samtools.SAMTag; +import org.broad.igv.feature.genome.ChromosomeNameComparator; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class SupplementaryGroup { + private final List readOrder; + private final List positionOrder; + + private final Adapter primaryRead; + private final Alignment original; + + public SupplementaryGroup(Alignment alignment) { + final List supplementaryAlignments; + if (alignment instanceof SAMAlignment) { + supplementaryAlignments = ((SAMAlignment) alignment).getSupplementaryAlignments(); + } else { + final Object rawSATag = alignment.getAttribute(SAMTag.SA.name()); + supplementaryAlignments = rawSATag == null ? null : new ArrayList<>(SupplementaryAlignment.parseFromSATag(rawSATag.toString())); + } + + this.primaryRead = new Adapter(alignment); + this.original = alignment; + final List combined = (supplementaryAlignments == null || supplementaryAlignments.isEmpty()) + ? new ArrayList<>() + : new ArrayList<>(supplementaryAlignments); + + combined.add(primaryRead); + readOrder = new ArrayList<>(combined); + readOrder.sort(SupplementaryAlignment.LEADING_CLIP_COMPARATOR); + ; + + positionOrder = new ArrayList<>(combined); + positionOrder.sort(SortOption.POSITION_COMPARATOR); + } + + /** + * @return the number of non-clipped bases in the reads. This includes insertion bases but not deletions. + */ + public int getBaseCount() { + return streamInPositionOrder() + .mapToInt(SupplementaryAlignment::getNumberOfAlignedBases) + .sum(); + } + + /** + * @return the readname of this group + */ + public String getReadName() { + return original.getReadName(); + } + + /** + * @return all the unique contigs in this group, sorted + */ + public List getContigs() { + return streamInReadOrder() + .map(SupplementaryAlignment::getContig) + .filter(Objects::nonNull) + .sorted(ChromosomeNameComparator.get()) + .distinct() + .collect(Collectors.toList()); + } + + + /** + * @return the total number of reference bases aligned to this collection of reads + * this includes deletions but not insertions or clips + */ + public int getLengthOnReference() { + return streamInPositionOrder() + .mapToInt(SupplementaryAlignment::getLengthOnReference) + .sum(); + } + + + public SupplementaryAlignment getNextInRead(SupplementaryAlignment alignment) { + final int i = readOrder.indexOf(alignment); + return readOrder.size() > i + 1 ? readOrder.get(i + 1) : null; + } + + public SupplementaryAlignment getPreviousInRead(SupplementaryAlignment alignment) { + final int i = readOrder.indexOf(alignment); + return i > 0 ? readOrder.get(i - 1) : null; + } + + public SupplementaryAlignment getNextPosition(SupplementaryAlignment alignment) { + final int i = positionOrder.indexOf(alignment); + return positionOrder.size() > i + 1 ? readOrder.get(i + 1) : null; + } + + public SupplementaryAlignment getPreviousPosition(SupplementaryAlignment alignment) { + final int i = readOrder.indexOf(alignment); + return i > 0 ? readOrder.get(i - 1) : null; + } + + /** + * {@link SAMAlignment} and {@link SupplementaryAlignment} don't share any useful interface. We use + * and adapter for the read which owns the SA tag (which isn't included in the tag itself) in order to + * include it seamlessly. + * + * @return the adapter wrapping the original read which contains the SA tag this group was built from + */ + public SupplementaryAlignment getPrimaryAlignment() { + return primaryRead; + } + + /** + * @return the alignment this group was created from + */ + public Alignment unwrap() { + return original; + } + + public Iterator iterateInReadOrder() { + return readOrder.iterator(); + } + + public Iterator iterateInPositionOrder() { + return positionOrder.iterator(); + } + + public Stream streamInReadOrder() { + return readOrder.stream(); + } + + public Stream streamInPositionOrder() { + return positionOrder.stream(); + } + + public int size() { + return readOrder.size(); + } + + private static class Adapter extends SupplementaryAlignment { + public Adapter(Alignment a) { + super(a.getChr(), a.getStart(), a.getReadStrand(), a.getCigar(), a.getMappingQuality(), a.getAttribute(SAMTag.NM.name()) == null ? 0 : (int) a.getAttribute(SAMTag.NM.name())); + } + } +} diff --git a/src/main/java/org/broad/igv/track/TrackMenuUtils.java b/src/main/java/org/broad/igv/track/TrackMenuUtils.java index 7cf1e9601f..5e887829aa 100644 --- a/src/main/java/org/broad/igv/track/TrackMenuUtils.java +++ b/src/main/java/org/broad/igv/track/TrackMenuUtils.java @@ -1365,12 +1365,7 @@ public static JMenuItem getFeatureNameAttribute(final Collection selected public static JMenuItem getChangeFontSizeItem(final Collection selectedTracks) { // Change track height by attribute JMenuItem item = new JMenuItem("Change Font Size..."); - item.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent evt) { - changeFontSize(selectedTracks); - } - }); + item.addActionListener(evt -> changeFontSize(selectedTracks)); return item; } diff --git a/src/main/java/org/broad/igv/ui/AlignmentDiagramFrame.java b/src/main/java/org/broad/igv/ui/AlignmentDiagramFrame.java deleted file mode 100644 index c1df4c3feb..0000000000 --- a/src/main/java/org/broad/igv/ui/AlignmentDiagramFrame.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2007-2015 Broad Institute - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package org.broad.igv.ui; - -import htsjdk.samtools.SAMRecord; -import htsjdk.samtools.SAMRecordSetBuilder; -import htsjdk.samtools.SAMTag; -import htsjdk.samtools.util.Locatable; -import org.broad.igv.feature.Strand; -import org.broad.igv.feature.genome.ChromosomeNameComparator; -import org.broad.igv.sam.*; -import org.broad.igv.ui.color.ColorPalette; -import org.broad.igv.ui.color.ColorUtilities; -import org.broad.igv.util.ChromosomeColors; -import org.broad.igv.util.Pair; - -import javax.swing.*; -import java.awt.*; -import java.awt.geom.Ellipse2D; -import java.awt.geom.Point2D; -import java.awt.geom.QuadCurve2D; -import java.awt.geom.Rectangle2D; -import java.util.List; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - - -public class AlignmentDiagramFrame extends JFrame { - - public AlignmentDiagramFrame(Alignment alignment, Dimension dimension) { - - setSize(dimension); - - JPanel readPanel = new JPanel(); - BoxLayout boxLayout = new BoxLayout(readPanel, BoxLayout.Y_AXIS); - readPanel.setLayout(boxLayout); - ReadDiagram diagram = new ReadDiagram(alignment); - readPanel.add(diagram); - diagram.setVisible(true); - setAlwaysOnTop(true); - - this.add(readPanel); - - - } - - private static class ReadDiagram extends JComponent { - - public static final int BORDER_GAP = 30; - public static final int BETWEEN_ALIGNMENT_GAP = 15; - public static final int BETWEEN_CONTIG_GAP = 10; - public static final int ALIGNMENT_HEIGHT = 10; - - SupplementaryAlignment.SupplementaryGroup toDraw; - - public ReadDiagram(Alignment alignment) { - this.toDraw = new SupplementaryAlignment.SupplementaryGroup(alignment); - } - - - @Override - protected void paintComponent(final Graphics g) { - super.paintComponent(g); - g.drawString("Hello", 0, 0); - draw((Graphics2D) g, toDraw); - } - - private void draw(final Graphics2D g, SupplementaryAlignment.SupplementaryGroup toDraw) { - int totalWidth = getWidth(); - final List contigs = toDraw.streamInPositionOrder() - .map(SupplementaryAlignment::getContig) - .filter(Objects::nonNull) - .sorted(ChromosomeNameComparator.get()) - .distinct() - .collect(Collectors.toList()); - - final int totalAlignedBases = toDraw.streamInPositionOrder() - .mapToInt(Locatable::getLengthOnReference) - .sum(); - - String lastContig = contigs.get(0); - final int mid = getHeight() / 2; - double lastPosition = BORDER_GAP; - - float alpha = 0.75f; - int type = AlphaComposite.SRC_OVER; - Composite alignmentAlphaComposite = AlphaComposite.getInstance(type, alpha); - g.setComposite(alignmentAlphaComposite); - g.setColor(Color.LIGHT_GRAY); - - Map> positions = new LinkedHashMap<>(); - int arrowPxWidth = 5; - for (SupplementaryAlignment sa : (Iterable) toDraw::iterateInPositionOrder) { - // System.out.print(sa.toString()); - final int length = sa.getLengthOnReference(); - double fractionOfWhole = (double) length / totalAlignedBases; - double availableSpace = totalWidth - (2 * BORDER_GAP + (toDraw.size() - 1) * BETWEEN_ALIGNMENT_GAP + (contigs.size() - 1) * BETWEEN_CONTIG_GAP); - double spaceToUse = fractionOfWhole * availableSpace; - final String newContig = sa.getContig(); - if (lastPosition != BORDER_GAP && !Objects.equals(lastContig, newContig)) { - lastPosition += BETWEEN_CONTIG_GAP; - } - - lastContig = newContig; - int y = mid - ALIGNMENT_HEIGHT; - int h = ALIGNMENT_HEIGHT; - int start = (int) lastPosition; - int end = (int) (lastPosition + spaceToUse); - /* - 1 2 - 0 <|=======|> 3 - 5 4 - */ - final int startAdjusted = start - (sa.getStrand() == Strand.NEGATIVE ? arrowPxWidth : 0); - final int endAdjusted = end + (sa.getStrand() == Strand.POSITIVE ? arrowPxWidth : 0); - positions.put(sa, new Pair<>(startAdjusted, endAdjusted)); - int[] xPoly = {startAdjusted, start, end, endAdjusted, end, start}; - int[] yPoly = {y + h / 2, y, y, y + h / 2, y + h, y + h}; - Polygon blockShape = new Polygon(xPoly, yPoly, xPoly.length); - lastPosition = end; - lastPosition += BETWEEN_ALIGNMENT_GAP; - g.fill(blockShape); - g.draw(blockShape); - if (sa == toDraw.getAdapter()) { - Color original = g.getColor(); - g.setColor(Color.DARK_GRAY); - g.draw(blockShape); - g.setColor(original); - } - } - - toDraw.streamInReadOrder() - .forEachOrdered(sa -> { - SupplementaryAlignment next = toDraw.getNextInRead(sa); - if (next != null) { - final Pair currentPos = positions.get(sa); - final Pair nextPos = positions.get(next); - int from = sa.getStrand() == Strand.NEGATIVE ? currentPos.getFirst() : currentPos.getSecond(); - int to = next.getStrand() == Strand.NEGATIVE ? nextPos.getSecond() : nextPos.getFirst(); - g.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER)); - int centerX = (from + to) / 2; - int width = Math.abs(from - to); - int height = (int) ((double) width / getWidth() * getHeight()); - int centerY = mid - ALIGNMENT_HEIGHT / 2; - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g.setColor(Color.GRAY); - g.drawArc(centerX - width / 2, centerY - height / 2, width, height, 0, 180); - drawPoint(g, to, centerY); - } - }); - - positions.keySet(). - stream() - .collect(Collectors.groupingBy(SupplementaryAlignment::getContig)) - .forEach((contig, alignments) -> { - int start = positions.get(alignments.get(0)).getFirst(); - int end = positions.get(alignments.get(alignments.size() - 1)).getSecond(); - g.setColor(ChromosomeColors.getColor(contig)); - final FontMetrics fontMetrics = g.getFontMetrics(); - final int labelWidth = fontMetrics.stringWidth(contig); - // s----label-----e - final int lineY = mid + 15; - final int LABEL_GAP = 2; - if (labelWidth + 2 * LABEL_GAP < end - start) { - final int leftLineEnd = (start + end - labelWidth) / 2 - LABEL_GAP; - final int rightLineStart = (start + end + labelWidth) / 2 + LABEL_GAP; - g.drawLine(start, lineY - 2, start, lineY + 2); - g.drawLine(end, lineY - 2, end, lineY + 2); - g.drawLine(start, lineY, leftLineEnd, lineY); - g.drawString(contig, leftLineEnd + LABEL_GAP, lineY + fontMetrics.getHeight() / 3); - g.drawLine(rightLineStart, lineY, end, lineY); - } else { - g.drawString(contig, start, lineY + fontMetrics.getHeight() / 3); - } - }); - } - - private static void drawPoint(Graphics2D g, int x, int y) { - g.draw(new Ellipse2D.Double(x - 1, y - 1, 2, 2)); - } - - private static void writeContigName(final Graphics g, final String contig, final double x, final int y) { - Color originalColor = g.getColor(); - try { - g.setColor(ChromosomeColors.getColor(contig)); - g.drawString(contig, (int) x, y); - } finally { - g.setColor(originalColor); - } - } - } - - - - public static void main(String[] args){ - final SAMRecordSetBuilder samRecords = new SAMRecordSetBuilder(); - samRecords.addPair("read1", 13, 50255292, 50255400 ); - final List records = new ArrayList<>(samRecords.getRecords()); - - final SAMRecord record = records.get(0); - String read = "m141213_163442_00118_c100750562550000001823151707081585_s1_p0/6849/0_5821\t2064\t14\t50255292\t38\t1302S12M1I5M1I25M1D12M1I7M1I2M1I10M1D4M1I7M1I10M1I6M1I4M1D9M5I4M1I4M2I18M1I9M1I5M1I20M1I13M1I6M1I6M1I16M1I3M1I13M1D9M1I4M2I4M1D14M1I21M1I1M1D5M11I2M1I11M1D10M1I12M2I6M1D3M1D11M1D3M1I3M1I19M1I3M2I6M1I8M1D1M2I1M1I6M1I9M3I2M1I4M1I1M1I10M1D1M1I9M1D6M1D4M2D6M2D7M1D13M1I5M1D6M1D8M1I3M4I3M2D1M1D9M1D25M1I4M1D20M1I18M1I9M1D3M1I2M1I4M1I6M1I5M1I18M1I4M3I17M1I1M1I9M1I11M1I24M1I5M1D14M3I16M1I23M2I14M1I6M1I10M1I10M1I13M2I15M1D10M1I12M1I2M1I6M1I11M1I5M1I4M1I11M1I6M1I9M1I19M1I1M1I29M1D23M1D16M1I23M1D3M1I2M1D9M1I20M2I4M1D19M1I11M1D21M1I5M1I11M1I2M1D2M1D7M1D16M1I4M1I8M1D9M1D18M1I12M1I9M1D5M1D5M1I5M1I5M1I36M1D7M3145S\t*\t0\t0\tATTCAAGGCACCAGCAGATTCTTGTCTGAATAGGGCTTGCCCCTCAAAGATTGGTACCTATAAGGCTGAGGTTGGGTGGCTCATACCTGTAATCCCAGCACTTTGGCGAGGCCAAGGTCGGGGGGATCCTTGGCCCAGGAGATTCGACACCAGCCTGCGGCAACAAGGGAGACCTCAATCTTAAAAAAAAGATGGCACCTTATTGCAGTGTACCTCACGTGGCAACAGGGGCACTCATCCTCATTCATGACACAGAGGCCCCTCGTTGACTTCACTTCCTAAAGGCCACTCTTAAATACCATCCACACTGGGCTATTTAGGTTCCCATATATGAATGTGGGGATAATTAACTGTAGAACCATAGCAGCCGTAACTCATGAAAGAACATTAATAAGTTTAACACCTCAGTGAGTATTTGTTGAGTCGTAGGATTAACAGAACATAAGGTTATTTGTAACAAATTTTTTAAAAAGACCACCAAAAATGGTTTTAGTCATTACTAAACCAACAGTGTCAGCAGGAAAAAAAGAAAATAGTAATTACAGAATTCCCGTAAGACATTACCTGCCATAAACTGGAGCACACAATTCATAACTCAAGCTTGACATAAAAAAATGCTGGTGGCAAAAGTAAAATATTTTTATTGCCTCTATTTTAGCCGTTATTAAAAACATAAACAGGCTGGTGACATGGTGTCTTACAGCCTGTATCCCAACACTTTGGGAGGCCAAGGGCAGGCGGAATTCACGGAGATCAGGACGATTTCAAGACCAGCACTGGCCAACAATGGTTGAACCCCAATTCACCACTTAAAATACCAAAATAAGCCAGGCGTAGATGGCGCATTTGCTTAGTAATCCCAAGCTACTCAGAAGGCTGAGGCGGATAATTGCTTGAATCACGGGAGGTGGCAGGTTGCCAGTGAGCTGAGATTGTGCCACTTGCACTCCAGCCTGGGACAAGAGCCGAGCGACTGCCTCAAAAAAAAAAAAAAAAAAAAAAAAATTCAAAGAGAGGTAAATTTCATGTAAGAAGATATACAGCAAGGCGTGGGCTTGGCTGTACTAGTTAACAAAACAATATGTGTGTAGGCGATGTGTCTGTCTTAGCTGGAAAATATTCGTCAGTGGTTTTGTAACCAGGGTACTTGGCTCTTTAAATGGTGTACTCGATGTTCAACATTATGTACGACCATATTGGTGTATAGATCGTGAGTGTGTTAGGAGTGTGGATGAAATTTATTTGTGGGGTGTCTTCAACAGACTGTTTCCAATTATTCTCTGTGTTGGTTTTTTGTGTGTGTTGAGATGGAGTACTCGCATCTGTCGACCAGGCTGGAGTGCAATGCACAATCTCGGACTCACTGACACAGCTCTGCCTCCAGAGTTCACGACTATTCTGCCTTCAGCCTTCCCAGTAGCTGAGGGGGGACTAACAGAGGCGCTCACCACCACACCCAGGCTAATTTCTTTTGTTATTTTTTTAGTAGAGACAGCGGTTTCACCACGTATAGCCAGGCATGGTTCTCAATTTCCTGACCTTTGTTGATCTGCCTGCCTGGCCTCTCAAAAGATTGCTGGATTACAGGCGTGAAGCCACCGTGCCAGGCCTCTCATTCTTATCTTTTTTTTTATATTAACTCAGTTCCATCAGAATCCAAAGCCTCTCCGCTTAAAATGATACTTCCTAAAGCTCTGTAAACCAAATTGTTATCAGCTAATCGAAGTACTAACCATTCTATTTGGCTTAGCATAAGATACGTGTGTGGATGTGGTAGTGTCTCGTGGTGTGTGTTGGTTGTATTGTGATGGGGGGGTGTCACTTGTCTTGTGTGGTGTACTGTGGTGCCAGTGTGGTTCCTGACTCCAGGAGGGTAATCACCCTAATTAAGAAGCAGGAAGCCACAAAGTAGCCATACCTTAGTCATCATGTGGATCATCTTACAGCAAATCTCTGTTAACTTCATAACGTTATAAACCTCAAGGAACGGAGTATTCTATTTTCTTAATGTTGTCAGAGGCACCCTCTGTCCCACCTCTAGGTTTTCTGGGGCTGTTTCTTCACAGGTTTGGTCTTTGTTTTTCCTTGGTTCTTCCCCTTCTTCCCTTTTTTCTTCTTTGTTTGAACCTGACCAGACTTTAATTCAGTAGAGGAAAACATCAGTCCAGTGTTGTATTTTTCAATACTGTGGGAATATTTTTACAAGATGTATTGAATTGCCTGTCATACGGCATAGGCAGGTGGGCTAGATGGCCCATTTTGAAAAAGCAAAAGAAAAAAATTGTTTTTAGAATGTTATTTACCCCCAGCATACCTTCATGATAAGTTCACGGTCTTCTTCATCTGGTCTTTGTATTTTTCTTTCATTTTTTCATTTGACTCTTACAAATTCAGAAGATTTTGGTTGAATATTTCAGCAGCAAAAAAAATGTCAACAAATACTTTTGCAAGAAATGGTGTTACTTTATTATTCTCTGTCAAAATGCTTACTGAATAAGGCAACCATATATAATAAACATATTGAGGCTACAAGTTGGATAACTGAAGGTCCTTCATCTTCAGGAATTTACATAGGGTGGAGAGAAGATTACACAGAGAAGTTAAGAATACTTAACAAACATTAAAGAAACTGTTGAGGAAGAAATAAAGTAAAACAGTATTGGAAAAGCAGTCTAATCCTCCTGCCTCAGCCTCCGAGTAGCTGGGGACTACAAGCGCTGCTGCCATGCCCGGCTAATTTTCTGTTATTTTTTAGTAGAGATGGGGTTATTCCACTGTATTAGCCAGGATGGTCTCCAATCTCCTGACTTGTGATTGCCCGCCTCGCCTCCCAAATTGCTGCGGATTTAACAGCGATGAGCCACCACTGCACCGGCCAAAAGTTTTTAAAAAATATAGTCTCAAGAAAGCATAACCATACTGTTAGTTGTGTTTAACTACTAAAAAATGAGGGGGTCCAAAATTTGGACTTCTGAGCAACTAAACAAAATAGAAACATGATCGTTTATATGGATCTTTCCAATGAGCCTATTAACTTTTCTCCTTCTTTAAATCCAATGGTTTAATTTTCAATATTTTCATTTATCAAGTGATAACATTAAGTAAACAAAAATTAAAATCACATTTTTTACCTTAGGTTGTGAGCTGATTGTGTCCTCCAAAAGATAGTGATTGAGGACAATCTCCACGTTTACCTATGAAGTGTGGCCTTATTTGGTTGATAGTGTCTTTCAGATGTAAGCAAGTTAAAATGATATTCTATACTGGATTAGGGTGGGCCCTTAAATCCAACAACTTCTGACATTATGAGAAAGGCATGTTGGGAAGAACATCGAGACAGAAATACACAATTGAAACATGAAATTGTTGACAGAGCAGGACTTGCCATGCTCACTGTAGCCAGGAAATGCTGAGGACCACCCGCAGCCACAGGAAGCTCCACAGAGGACAAGAAGAATTTTCCTCTACCAAGAACCTTCAAGAAAAGCCAGGTTCTTGTGGTGGGCGTGCTTTCATTCATACTTGCAGGTCTTCAAAACTGTGAGGCAATAATTTCTGTTGCTTTTAAAGCCACTCGGTTTGTGGTCATTTGCCACGCGCCTAGGAATCAAAACTAACTATCAACTGGCCAACTTTCGGTTGTTTTTATTTATTTTTTATTTTCATTTTTATTTTATTTTTTTATTTGTTTGAGATGGAGTCTCCCTTGTTGCCCCAGCTGGAGTGCAGAGTGGGCTATGATCCTCAAGCTCACTTGCAACCCCATGGGCCTCCTGGGTTCAAGTGATTCTCCTTGCTCAGCTCCTGAGTAGCTGGGATATACAGGCACACGCCACCATGCGCGGCTAAATTTTTTGTATTTGAGTAGGACGAGGGGTTCACCTGTTGGCCCAAGGCTTTGGATTCTCGAAACTCCTGACGTCAAAAGTGGATCCGCCCACCTTGGCTTCCAAAAGTGCTGGATTTACAGGCGAAAAGCCACTGCACCCAGCCTGTTTTTAATTAACACCATTTTGGTTTGAAATGCTAGAAGGAATTGGGACATTTAACATATACTTTCATTAGAGAGTAATTTTTTTATTGTAAGATATTAATTTTCTGTTTTTCTTTTTCATCATCTCTTTTTTCTAAAGTTTTCAATGTTGAGCTTGTATATCTTTTTGCAATAACAAAAACTTTCATTTAAAAACACTATTGTCACCATTATTTAACATTTTTCTCGTAAGTTTCTAGCGGTAAGTGGCTAAGAAATGGATGTCGGGTTTGATTTTAACAGCTCCCTGGGTCGGGGAGAACAGGGTTGGACTTGAAATCACAGGAAACATGAATCTTCAGTCCCAAGCTGTGCCATTTCCAGCTGTATGATCTTGAGTAAGCCACTGAACTTTTCGTAAACCTCAAGTTTCCCCCACCTCCCTATGATGTTGTTTCTATTGGGCTCTTCAATTTTCTACCACCGTGTGCCCTTTAAAGAAGGATGAGAGCAAGAGGGAAACAAATGTCAAACATTAATTGCATTTTTAATACAGTTCACTGGAGGATGACCCTGGGGAAGAGGGATTGAGGGAAATCAATTCTGGTGATGAAAACAATAATCGAGAATTACAAGAAGGGTCACAGCTTTAAATCTGGATTAGAGAGAAAAATGCCTCTGCATTTTTCTTTGCCTTTGTGGGGTTCAGTCAGACTGTTACTTGATTCAATGTTTACTGTGATTCAAACAGTTCTGAGGGGAAATGCTTTGTGTGTCATCAACTATTACAGACACTGAGGGGACAGAGATGTTCCAGACATAACCCTGTTTTCCTTCATGTGGGCTTCTCTTGTGAGTGGGTGAAGGGTGGGGTCAAAGTACTAGATGGATTTTTTTTAAATTTTTTATATTGTACTTTAAGTTTTAGGGGTACATGTGCACAAATGTACAGGTTTGTTACATATATTTACATGTGCCCATGTTTGGTGTGCTGCAACCATTAACTCGTCAGTTAACATTTAGGGTTATAATCTCCTAATGCTATTCTACCCCGCTCCCGCACCCATAACAGGCTTCTGTTATTGATGTTTCCCTTTTCTGTGTCCATGTGTTTCTCATTGTTCAATTCCACCTGTTGAGTGAGACATGCCAGTGTTTTTGGTTTTTTGTCCTTGTTTGATGTTTGCGGAGAATGAATGGTTTTCCAGCTTCATCCATGTCCCTACAAAGACATGAAAATTCATTTAAGATGGATTTTTTTAAATGACAAAGCAGAATCAGAAAAAATGGCTAAGAGAGGATGAATTGGCTGAGCCTTCTTTTCCATGCATTGGTGTCACCTTTATAAAGGATTGTGAAAATGTCAAGGCAGGGCAGCTGATCTTTTATTTATTGGTTTTCATTACAGGTGGACCTGGCCAGAAGCCCTTGGGAAACTGGCGGTGCGCTACGGGTAATTTATTTCTAGGCACCTTCAAAGGAGAAAAGGGCAGTGTCCTCCCTTCTCAACTGGATATTGTGCTAGACCATAGTTGCATACCGCCCACAAGCCCCCAAGCATCAGCTGGCCTGGCATCCCTGCCCAGAAGGGCCCATTGCCACTGGTCTTTCCACAAAAACCTGAGCCTTAGGCCAAATTAAAAAGCCACTTGTAGGCATTTTTACCACTTGAATTTACAGCCAAGAGGAAAATCCAACCATGGGGAAAAAGATCCACTGGCTTTTGGCAATTTTCCAAACTTTAAAAACCCCCAAAAGGGCCAAAAAAGGCAAAAACTACCCCAAAGGCCAAAAAGGCTATTAGGAACATTACAAGGAAAAAAAGTCTTTGATATTTCTTTTCTTTGT\t*\tSA:Z:17,64042266,-,2676S17M1D10M1I14M1D26M1I2M1I20M2I1M1I23M1I11M1D8M1D10M1D16M1I3M1I3M1D1M1I4M1I11M1I2M1I6M1D10M1I11M1D3M1D11M1D8M3I2M1I14M1I7M4I12M1I3M1I9M1D13M1D23M1I4M1I13M1D12M1I38M1I11M1I5M1D11M1I17M1I14M1D12M1D8M1I2M1I3M1D12M1I2M2I8M1I19M1D1M2D11M1D25M1I2M1I21M1I28M1I4M1D4M1I1M2I4M1I22M1I5M1I3M1D3M1I9M2I6M1I9M1D2M1D4M1D11M1D2M1I18M1D2M1I13M1I23M1I17M1I7M1I2M1D4M1I21M1I25M1D12M1I3M1I29M1D2M1D1M1D6M1D8M1I8M1D17M1D13M1I54M1D5M1I4M1D12M2I1M1I3M1I5M1I3M1I7M1I9M1I1M2I25M1I2M1D5M1D18M1I27M1I2M1I8M1D9M1D3M1I11M1D6M1I2M1I4M2I3M2I5M1I13M2I4M1I30M1D3M1I9M1I30M2D4M1D4M1D9M1D5M1I5M1I3M2I6M1I15M1D20M1I3M1I13M5I18M2I10M1I7M1D1M1I38M1D39M1I5M1I6M1588S,36,188;14,50254065,-,2S19M1D7M1I2M1D8M2I9M1I8M1I1M1I6M1I2M1I2M1D32M1I12M1I7M1D5M1D9M1I15M1I7M1D11M1I5M1I28M1I13M1I12M1I15M1I2M1D1M1I6M1I19M1D2M1D1M1D5M1I8M1I9M1I22M1D3M2D6M1I6M1D5M1I9M1I2M1I1M1I22M2I10M1D12M1I6M1I24M1I4M1I5M2I4M1I5M4I2M1I6M1D8M1I10M2D7M1I8M1D18M1I7M1I4M1D5M1I9M1I2M1D6M1I12M1I18M1D27M1I1M1D7M1D1M1D11M1D15M1I13M1I1M1I3M1I9M1I6M1D23M1I9M1I1M1I3M1D12M1I1M2I12M1I9M1I4M1I2M1D6M1I1M1I33M1I9M2I5M1I8M1I20M1D18M1I10M1I6M1I22M1I16M1I6M3I3M2D9M5I23M1I3M1D10M1D21M1I5M1I7M1I8M1I14M7I8M4I1M1I15M1I5M3D31M1I15M1I26M4624S,38,166;8,123906302,+,157S3M1I2M1I8M1I2M1I2M1I3M1I4M1D14M1I2M2I3M1I1M1I7M1I5M1D13M2I3M1I7M1I2M1I9M2D5M1D11M1I2M1D10M2I4M1I3M1I5M1D5M1I4M1I3M1D4M1I5M1I45M1I4M1I12M1D3M1I1M1I4M2I27M1I3M1D4M1I3M1I5M1I2M1D4M1I6M1D13M1I12M1I9M1I11M1I18M2I4M1I12M1D7M1I40M2I3M1I1M1I8M1D26M1I6M1I15M1D3M2I16M2I7M1I5M1D8M1I6M1D16M1I22M1I6M1I3M1D1M1D3M1I10M1D6M1D10M1I1M1D16M1I3M1I1M1I3M1I35M1I5M1I33M1I13M1I22M2I22M1D5M1I1M1D4M1D21M1I15M1I3M1I5M1I9M1D3M1I10M1D13M1D2M1I4M1I3M1D8M2I5M1D2M3I3M1D20M2I10M1I16M1I5M1I1M2I4M2I2M1I14M1D22M1I7M1I4M1I13M1I1M1I5M1D5M1D22M1I7M1I13M1I17M1I15M1I26M1I1M1D11M2D8M1D20M1I7M1D4M1I8M1D8M4409S,30,166;8,126480844,-,4239S17M1D6M1I30M1I11M1D4M1I7M1I11M1I9M1I50M1I8M1I10M1411S,2,11;\tKB:f:24.508675\tSB:f:24.508675\tID:i:150690\tMD:Z:24C15G1^G31^C31^A101C24^T17^G36^G11T6^C28^C3^T11^G9A30A1^C19C0A3T4C1T2^A2T3A1T1^C3A2^A0C3^CC0A1T1A1^AA0T0A3T1^A0C14T0T1^A1C4^A0A0A2C9^GG1^A9^T29^C47^G13G92C2^C115A5^A125^C23^T11T10A16^G5^A33^A30^A39^A2^A7^A28^A9^C39^A5^A51^A7\tQE:i:2676\tXE:i:1426\tXI:f:0.8567\tNM:i:203\tXR:i:1374\tAS:i:1426\tQS:i:1302\tXS:i:0\tCV:f:23.60419\tSV:i:2"; - String saTag = "17,64042266,-,2676S17M1D10M1I14M1D26M1I2M1I20M2I1M1I23M1I11M1D8M1D10M1D16M1I3M1I3M1D1M1I4M1I11M1I2M1I6M1D10M1I11M1D3M1D11M1D8M3I2M1I14M1I7M4I12M1I3M1I9M1D13M1D23M1I4M1I13M1D12M1I38M1I11M1I5M1D11M1I17M1I14M1D12M1D8M1I2M1I3M1D12M1I2M2I8M1I19M1D1M2D11M1D25M1I2M1I21M1I28M1I4M1D4M1I1M2I4M1I22M1I5M1I3M1D3M1I9M2I6M1I9M1D2M1D4M1D11M1D2M1I18M1D2M1I13M1I23M1I17M1I7M1I2M1D4M1I21M1I25M1D12M1I3M1I29M1D2M1D1M1D6M1D8M1I8M1D17M1D13M1I54M1D5M1I4M1D12M2I1M1I3M1I5M1I3M1I7M1I9M1I1M2I25M1I2M1D5M1D18M1I27M1I2M1I8M1D9M1D3M1I11M1D6M1I2M1I4M2I3M2I5M1I13M2I4M1I30M1D3M1I9M1I30M2D4M1D4M1D9M1D5M1I5M1I3M2I6M1I15M1D20M1I3M1I13M5I18M2I10M1I7M1D1M1I38M1D39M1I5M1I6M1588S,36,188;14,50254065,-,2S19M1D7M1I2M1D8M2I9M1I8M1I1M1I6M1I2M1I2M1D32M1I12M1I7M1D5M1D9M1I15M1I7M1D11M1I5M1I28M1I13M1I12M1I15M1I2M1D1M1I6M1I19M1D2M1D1M1D5M1I8M1I9M1I22M1D3M2D6M1I6M1D5M1I9M1I2M1I1M1I22M2I10M1D12M1I6M1I24M1I4M1I5M2I4M1I5M4I2M1I6M1D8M1I10M2D7M1I8M1D18M1I7M1I4M1D5M1I9M1I2M1D6M1I12M1I18M1D27M1I1M1D7M1D1M1D11M1D15M1I13M1I1M1I3M1I9M1I6M1D23M1I9M1I1M1I3M1D12M1I1M2I12M1I9M1I4M1I2M1D6M1I1M1I33M1I9M2I5M1I8M1I20M1D18M1I10M1I6M1I22M1I16M1I6M3I3M2D9M5I23M1I3M1D10M1D21M1I5M1I7M1I8M1I14M7I8M4I1M1I15M1I5M3D31M1I15M1I26M4624S,38,166;8,123906302,+,157S3M1I2M1I8M1I2M1I2M1I3M1I4M1D14M1I2M2I3M1I1M1I7M1I5M1D13M2I3M1I7M1I2M1I9M2D5M1D11M1I2M1D10M2I4M1I3M1I5M1D5M1I4M1I3M1D4M1I5M1I45M1I4M1I12M1D3M1I1M1I4M2I27M1I3M1D4M1I3M1I5M1I2M1D4M1I6M1D13M1I12M1I9M1I11M1I18M2I4M1I12M1D7M1I40M2I3M1I1M1I8M1D26M1I6M1I15M1D3M2I16M2I7M1I5M1D8M1I6M1D16M1I22M1I6M1I3M1D1M1D3M1I10M1D6M1D10M1I1M1D16M1I3M1I1M1I3M1I35M1I5M1I33M1I13M1I22M2I22M1D5M1I1M1D4M1D21M1I15M1I3M1I5M1I9M1D3M1I10M1D13M1D2M1I4M1I3M1D8M2I5M1D2M3I3M1D20M2I10M1I16M1I5M1I1M2I4M2I2M1I14M1D22M1I7M1I4M1I13M1I1M1I5M1D5M1D22M1I7M1I13M1I17M1I15M1I26M1I1M1D11M2D8M1D20M1I7M1D4M1I8M1D8M4409S,30,166;8,126480844,-,4239S17M1D6M1I30M1I11M1D4M1I7M1I11M1I9M1I50M1I8M1I10M1411S,2,11"; - record.setAttribute(SAMTag.SA, saTag); - final Alignment samAlignment = new SAMAlignment(record); - - AlignmentDiagramFrame frame = new AlignmentDiagramFrame(samAlignment, new Dimension(500, 100)); - frame.setVisible(true); - - } -} diff --git a/src/main/java/org/broad/igv/ui/IGV.java b/src/main/java/org/broad/igv/ui/IGV.java index f9a9ac31f6..a43c8dcb57 100644 --- a/src/main/java/org/broad/igv/ui/IGV.java +++ b/src/main/java/org/broad/igv/ui/IGV.java @@ -549,7 +549,7 @@ final public void doViewPreferences() { try { PreferencesEditor.open(this.mainFrame); } catch (Exception e) { - log.error("Error openining preference dialog", e); + log.error("Error opening preference dialog", e); } } diff --git a/src/main/java/org/broad/igv/ui/TooltipTextFrame.java b/src/main/java/org/broad/igv/ui/TooltipTextFrame.java index 3994ebfbb3..5ddd57dc65 100644 --- a/src/main/java/org/broad/igv/ui/TooltipTextFrame.java +++ b/src/main/java/org/broad/igv/ui/TooltipTextFrame.java @@ -88,8 +88,8 @@ public TooltipTextFrame(String title, String text) throws HeadlessException { int w = (int) (1.2 * d.width); int h = (int) (1.25 * d.height); - h = h > 600 ? 600 : (h < 100 ? 100 : h); - w = w > 800 ? 800 : (w < 100 ? 100 : w); + h = h > 600 ? 600 : (Math.max(h, 100)); + w = w > 800 ? 800 : (Math.max(w, 100)); setSize(w, h); diff --git a/src/main/java/org/broad/igv/ui/panel/FrameManager.java b/src/main/java/org/broad/igv/ui/panel/FrameManager.java index df20359821..77454ff661 100644 --- a/src/main/java/org/broad/igv/ui/panel/FrameManager.java +++ b/src/main/java/org/broad/igv/ui/panel/FrameManager.java @@ -25,7 +25,7 @@ package org.broad.igv.ui.panel; -import com.jidesoft.utils.SortedList; +import htsjdk.samtools.util.Locatable; import org.broad.igv.event.GenomeChangeEvent; import org.broad.igv.event.IGVEventBus; import org.broad.igv.event.IGVEventObserver; @@ -37,6 +37,8 @@ import org.broad.igv.lists.GeneList; import org.broad.igv.prefs.Constants; import org.broad.igv.prefs.PreferencesManager; +import org.broad.igv.sam.AlignmentTrack; +import org.broad.igv.sam.SortOption; import org.broad.igv.track.RegionScoreType; import org.broad.igv.track.Track; import org.broad.igv.ui.IGV; @@ -45,6 +47,7 @@ import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author jrobinso @@ -260,7 +263,6 @@ public static void removeFrame(ReferenceFrame frame) { } public static void sortFrames(final Track t) { - frames.sort(Comparator.comparingDouble((ReferenceFrame f) -> t.getRegionScore(f.getChromosome().getName(), (int) f.getOrigin(), @@ -356,6 +358,62 @@ public static void addFrames(List newLociStrings) { IGV.getInstance().resetFrames(); } + /* + * todo: this shoold be combined with addFrames since they do confusingly similar things + * This takes an arbitrary number of new locations to add as well as a single existing frame to keep + * (possibly that's ignored if there are multiple reference frames due to how loci are processed in createLociList()) + * It doesn't merge overlapping frames like addFrames does which it probably should. + */ + + /** + * Open new frames displaying the locations included in toIncludeInSplit + * @param frame this is used to set the width of the new frames to be consistent + * @param toIncludeInSplit new locations to open frames for + * @param selectedReadNames a set of read names which will be sorted to the top of the open frames + */ + public static void addNewLociToFrames(final ReferenceFrame frame, final List toIncludeInSplit, final Set selectedReadNames) { + final Stream newLoci = toIncludeInSplit.stream().map(locatable -> getLocusStringScaledToFrame(frame, locatable)); + final Stream existingFrames = getFrames().stream().map(ref -> { + String name = ref.getName(); + return Locus.fromString(name) != null ? name : ref.getFormattedLocusString(); + }); + + //Can't use FRAME_COMPARATOR because that looks at the existing frames statically and they don't all exist yet + final Comparator comparator = Comparator.comparing(Locus::fromString, SortOption.POSITION_COMPARATOR); + + final List loci = Stream.concat(newLoci, existingFrames) + .sorted(comparator) + .distinct() + .collect(Collectors.toList()); + String listName = String.join(" ", loci); + //Need to sort the frames by position + GeneList geneList = new GeneList(listName, loci); + geneList.sort(comparator); + IGV.getInstance().getSession().setCurrentGeneList(geneList); + IGV.getInstance().resetFrames(); + + /* + We want the sort to happen after the frame refresh / track loading begins. + This puts the sort onto the event thread so that it happens after loading has already started. + Since loading reads happens asynchronously on a different thread from the event thread, it is likely + that the loading won't be done by the time the sort fires. In that case the sort will be set as the + action to perform when the load is finished + See {@link AlignmentTrack#sortRows(SortOption, Double, String, boolean, Set)} + */ + AlignmentTrack.sortSelectedReadsToTheTop(selectedReadNames); + } + + private static String getLocusStringScaledToFrame(final ReferenceFrame frame, final Locatable alignment) { + int adjustedMateStart = alignment.getStart() - 1; + + // Generate a locus string for the alignment. Keep the window width (in base pairs) == to the current range + Range range = frame.getCurrentRange(); + int length = range.getLength(); + int start = Math.max(0, adjustedMateStart - length / 2); + int end = start + length; + return alignment.getContig() + ":" + start + "-" + end; + } + @Override public void receiveEvent(Object event) { diff --git a/src/main/java/org/broad/igv/ui/supdiagram/AlignmentArrow.java b/src/main/java/org/broad/igv/ui/supdiagram/AlignmentArrow.java new file mode 100644 index 0000000000..7502b059df --- /dev/null +++ b/src/main/java/org/broad/igv/ui/supdiagram/AlignmentArrow.java @@ -0,0 +1,44 @@ +package org.broad.igv.ui.supdiagram; + +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 + */ +public class AlignmentArrow extends Polygon { + /** The width of the arrow tip in pixels */ + public static final int ARROW_PX_WIDTH = 5; + final Strand strand; + + public AlignmentArrow(int midline, int height, int left, int right, Strand strand) { + super(); + final int floor = (midline - height); + final int startAdjusted = left - (strand == Strand.NEGATIVE ? ARROW_PX_WIDTH : 0); + final int endAdjusted = right + (strand == Strand.POSITIVE ? ARROW_PX_WIDTH : 0); + /* + 1 2 + 0 <|=======|> 3 + 5 4 + */ + final int[] xPoly = {startAdjusted, left, right, endAdjusted, right, left}; + final int[] yPoly = {floor + height / 2, floor, floor, floor + height / 2, floor + height, floor + height}; + this.xpoints = xPoly; + this.ypoints = yPoly; + this.npoints = xPoly.length; + this.strand = strand; + invalidate(); + } + + public Point2D getTip() { + int x = strand == Strand.NEGATIVE ? xpoints[0] : xpoints[3]; + return new Point2D.Double(x, ypoints[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 new file mode 100644 index 0000000000..a8df5d0b06 --- /dev/null +++ b/src/main/java/org/broad/igv/ui/supdiagram/SupplementalAlignmentDiagram.java @@ -0,0 +1,460 @@ +package org.broad.igv.ui.supdiagram; + +import htsjdk.samtools.util.Interval; +import htsjdk.samtools.util.Locatable; +import org.broad.igv.logging.LogManager; +import org.broad.igv.logging.Logger; +import org.broad.igv.sam.AlignmentTrack; +import org.broad.igv.sam.SupplementaryAlignment; +import org.broad.igv.sam.SupplementaryGroup; +import org.broad.igv.ui.IGV; +import org.broad.igv.ui.panel.FrameManager; +import org.broad.igv.ui.util.IGVMouseInputAdapter; +import org.broad.igv.util.ChromosomeColors; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.geom.CubicCurve2D; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +class SupplementalAlignmentDiagram extends JPanel { + + public static final int LABEL_GAP = 2; + private static final Logger log = LogManager.getLogger(SupplementalAlignmentDiagram.class); + + public static final int BORDER_GAP = 30; + public static final int BETWEEN_ALIGNMENT_GAP = 15; + public static final int BETWEEN_CONTIG_GAP = 10; + public static final int ALIGNMENT_HEIGHT = 10; + public static final Color SELECTED_COLOR = Color.BLUE; + 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<>(); + private final SupplementaryGroup toDraw; + + public SupplementalAlignmentDiagram(final SupplementaryGroup toDraw) { + this.toDraw = toDraw; + this.setBackground(Color.WHITE); + this.addMouseMotionListener(new IGVMouseInputAdapter() { + + @Override + public void mouseMoved(final MouseEvent e) { + selected.clear(); + //dumb brute force search but there should only ever be a handful of these so it should be fine + for (AlignmentArrow arrow : elementsOnScreen.keySet()) { + if (arrow.contains(e.getPoint())) { + selected.add(elementsOnScreen.get(arrow)); + repaint(); + return; + } + } + repaint(); + } + }); + + this.addMouseListener(new IGVMouseInputAdapter() { + @Override + public void mouseClicked(final MouseEvent e) { + if (SwingUtilities.isLeftMouseButton(e)) { + IGV igv = null; // for testing we may not have an IGV instance available + try { + igv = IGV.getInstance(); + } catch (RuntimeException ex) { + log.info("Clicked " + selected.stream().map(SupplementaryAlignment::toString) + .collect(Collectors.joining("\n"))); + } + + if (igv != null && !selected.isEmpty()) { + final SupplementaryAlignment first = selected.stream().findFirst().get(); + final Set selectedReadNameSet = Set.of(toDraw.getReadName()); + + if (e.isShiftDown()) { //if shift is held add to the existing set of frames instead of replacing them + FrameManager.addNewLociToFrames(FrameManager.getDefaultFrame(), java.util.List.of(first), selectedReadNameSet); + } else { + igv.setDefaultFrame(first.getContig() + ":" + first.getStart() + "-" + first.getEnd()); + AlignmentTrack.sortSelectedReadsToTheTop(selectedReadNameSet); + } + igv.getAlignmentTracks().forEach( + t -> t.setSelectedAlignment(toDraw.unwrap()) + ); + } + } + } + }); + } + + + @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 halfHeight = getHeight() / 2; + final int width = getWidth(); + chrDiagramBounds = new Rectangle2D.Float(0, 10, width, halfHeight - 10); + drawContigOrder(g, chrDiagramBounds); + readDiagramBounds = new Rectangle2D.Float(0, halfHeight, width, halfHeight); + drawReadOrder(g, readDiagramBounds); + + if (!selected.isEmpty()) { + final SupplementaryAlignment first = this.selected.iterator().next(); + g.setColor(Color.BLACK); + g.drawString(String.format("%s:%d-%d", first.getContig(), first.getStart(), first.getEnd()), 30, getHeight() - g.getFontMetrics().getHeight() + 2); + } + } + + private void drawReadOrder(final Graphics g, final Rectangle2D bounds) { + g.setColor(Color.DARK_GRAY); + ((Graphics2D)g).draw(bounds); + g.drawString("Read Order", (int)bounds.getX()+5, (int)bounds.getY()+15); + Map saInReadOrder = drawAlignmentsInReadOrder((Graphics2D) g.create(), toDraw, selected, bounds); + drawArcs((Graphics2D) g.create(), toDraw, selected, saInReadOrder, bounds); + final int lowestArrowPoint = saInReadOrder.values().stream().mapToInt(a -> (int) a.getBounds2D().getMaxY()).max().getAsInt(); + drawReadLengthLabel(((Graphics2D) g.create()), lowestArrowPoint + 15 , saInReadOrder); + saInReadOrder.forEach((k, v) -> elementsOnScreen.put(v, k)); + g.setColor(Color.DARK_GRAY); + ((Graphics2D)g).draw(bounds); + } + + private void drawContigOrder(final Graphics g, final Rectangle2D bounds) { + g.setColor(Color.DARK_GRAY); + ((Graphics2D)g).draw(bounds); + g.drawString("Alignment Order", (int)bounds.getX()+5, (int)bounds.getY()+15); + Map saInPositionOrder = drawAlignmentsInCondensedChromosomeOrder((Graphics2D) g.create(), toDraw, selected, bounds); + drawArcs((Graphics2D) g.create(), toDraw, selected, saInPositionOrder, bounds); + final int lowestArrowPoint = saInPositionOrder.values().stream().mapToInt(a -> (int) a.getBounds2D().getMaxY()).max().getAsInt(); + drawContigLabels((Graphics2D) g.create(), lowestArrowPoint + 15, saInPositionOrder); + saInPositionOrder.forEach((k, v) -> elementsOnScreen.put(v, k)); + g.setColor(Color.DARK_GRAY); + ((Graphics2D)g).draw(bounds); + } + + private void drawReadLengthLabel(final Graphics2D g, final int mid, final Map saInReadOrder) { + java.util.List arrowsInOrder = new ArrayList<>(saInReadOrder.values()); + int left = (int)arrowsInOrder.get(0).getBounds().getMinX(); + int right = (int)arrowsInOrder.get(arrowsInOrder.size() - 1).getBounds().getMaxX(); + final int baseCount = toDraw.getBaseCount(); + g.setColor(Color.BLACK); + drawCenteredStringWithRangeLines(g, mid, "Length in bases = " + baseCount, left, right); + } + + private static void drawArcs(final Graphics2D g, + final SupplementaryGroup toDraw, + final Set selected, + final Map saToArrowMap, + final Rectangle2D bounds) { + toDraw.iterateInReadOrder() + .forEachRemaining(sa -> { + SupplementaryAlignment next = toDraw.getNextInRead(sa); + if (next != null) { + final boolean highlight = selected.contains(sa) || selected.contains(next); + drawArc(g, saToArrowMap.get(sa), saToArrowMap.get(next), highlight, bounds); + } + }); + } + + private static Composite getAlphaComposite() { + final float alpha = 0.75f; + final int type = AlphaComposite.SRC_OVER; + return AlphaComposite.getInstance(type, alpha); + } + + private static Map drawAlignmentsInReadOrder(final Graphics2D g, final SupplementaryGroup toDraw, + final Set selected, Rectangle2D bounds) { + int midline =(int) (bounds.getY() + .5 * bounds.getHeight()); + final Map positions = new LinkedHashMap<>(); + final int totalAlignedBases = toDraw.getBaseCount(); + final int scaledAlignmentGap = scale(2, BETWEEN_ALIGNMENT_GAP, bounds.getWidth()); + final int scaledBorderGap = scale(BORDER_GAP / 3, BORDER_GAP, bounds.getWidth()); + final double availableSpace = bounds.getWidth() - (2 * scaledBorderGap + (toDraw.size() - 1) * scaledAlignmentGap); + + double lastPosition = scaledBorderGap; + for (SupplementaryAlignment sa : (Iterable) toDraw::iterateInReadOrder) { + final double spaceToUse = getSpaceToUse(availableSpace, sa.getNumberOfAlignedBases(), totalAlignedBases); + final int end = (int) (lastPosition + spaceToUse); + AlignmentArrow readArrow = new AlignmentArrow(midline, ALIGNMENT_HEIGHT, (int)( bounds.getX() + lastPosition) , (int)( bounds.getX() + end), sa.getStrand()); + lastPosition = end + scaledAlignmentGap; + positions.put(sa, readArrow); + } + + drawArrows(g, selected, positions, toDraw.getPrimaryAlignment()); + return positions; + } + + private static Graphics2D getSelectedGraphics(final Graphics2D g) { + Graphics2D selectedGraphics = (Graphics2D) g.create(); + selectedGraphics.setStroke(new BasicStroke(3.0f)); + selectedGraphics.setColor(SELECTED_COLOR); + return selectedGraphics; + } + + private static Map> groupBySpanningInterval(List intervals){ + List currentGroup = null; + Map> output = new LinkedHashMap<>(); + Locatable spanning = null; + if(intervals.isEmpty()){ + return Collections.emptyMap(); + } + for( T loc : intervals){ + if(currentGroup == null || currentGroup.isEmpty()){ + currentGroup = new ArrayList<>(); + currentGroup.add(loc); + spanning = loc; + } else if( spanning.overlaps(loc)) { + currentGroup.add(loc); + spanning = new Interval(spanning.getContig(), Math.min(spanning.getStart(), loc.getStart()), Math.max(spanning.getEnd(), loc.getEnd())); + } else { + output.put(spanning, currentGroup); + currentGroup = new ArrayList<>(); + currentGroup.add(loc); + spanning = loc; + } + } + output.put(spanning, currentGroup); + return output; + } + + //Draw the alignments in chromosome order but give an indication of how close they are to each other i.e. within a 1kb window or not + //Handle overlapping alignments + private static Map drawAlignmentsInCondensedChromosomeOrder(final Graphics2D g, final SupplementaryGroup toDraw, + final Set selected, Rectangle2D bounds) { + + + final double midline = bounds.getY() + .5 * bounds.getHeight(); + final Map positions = new LinkedHashMap<>(); + final List contigs = toDraw.getContigs(); + final int scaledAlignmentGap = scale(2, BETWEEN_ALIGNMENT_GAP, bounds.getWidth()); + final int scaledContigGap = scale(2, BETWEEN_CONTIG_GAP, bounds.getWidth()); + final int scaledBorderGap = scale(BORDER_GAP / 3, BORDER_GAP, bounds.getWidth()); + + final var groupedBySpan = groupBySpanningInterval(toDraw.streamInPositionOrder().toList()); + final Map> byContig = groupedBySpan.keySet() + .stream() + .collect(Collectors.groupingBy(Locatable::getContig, LinkedHashMap::new, Collectors.toList())); + + + //tihs should probably vary per contig instead of being uniform + final double perContigAvailableSpace = (bounds.getWidth() - (2 * scaledBorderGap + (contigs.size() -1) * scaledContigGap))/((double)contigs.size()); + + int contigStart = scaledBorderGap; + for(var contigEntry: byContig.entrySet()){ + //find the available space for each contig and set the drawing head there + int contigEnd = (int)(contigStart + perContigAvailableSpace); + List distinctSpans = contigEntry.getValue(); + //find the reference length of all the span groups on this contig + int totalSpansLength = distinctSpans.stream().mapToInt(Locatable::getLengthOnReference).sum(); + int spanStart = contigStart; + for(Locatable span: distinctSpans){ + //now handle each span group + int spanLength = span.getLengthOnReference(); + int spanSpaceAvailable = (int)getSpaceToUse((double) perContigAvailableSpace - (distinctSpans.size() -1) * scaledAlignmentGap, spanLength, totalSpansLength ); + 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); + // height offset is used to try to layout overlapping arrows above instead of ontop of each other + int heightOffset = ALIGNMENT_HEIGHT - (int)((2*ALIGNMENT_HEIGHT) * (1.0/(activeSpanGroup.size()+1.0))*(i+1.0)); + final AlignmentArrow readArrow = new AlignmentArrow((int)midline + 2*heightOffset, ALIGNMENT_HEIGHT, + (int)bounds.getX() + spanStart + scaledReadStart, + (int)bounds.getX() + spanStart + scaledReadEnd, + sa.getStrand()); + + positions.put(sa, readArrow); + + } + spanStart += spanSpaceAvailable + scaledAlignmentGap; + } + //move contig start forward + contigStart = contigEnd + scaledContigGap; + } + + // | ... [ ]>-5kbp<[ ] ... [ ]> | |[ |> ... | + // Contigs all the same scale? + // Contigs proportional to actual size? < + //1 divide available space into contig zones + //2 go through all reads on contig and count necessary zones. + // edges if the read hits left / right edge + // merge overlappers into single zones + // discover close by / far away zones + + drawArrows(g, selected, positions, toDraw.getPrimaryAlignment()); + return positions; + } + + //This draws the aligments scaled according to their aligned length on the reference and in chromosome order + //TODO fix overlapping alignments + private static Map drawAlignmentsInPositionOrder(final Graphics2D g, final SupplementaryGroup toDraw, + final Set selected, final int width, final int midline) { + final Map positions = new LinkedHashMap<>(); + final List contigs = toDraw.getContigs(); + final int totalAlignedBases = toDraw.getLengthOnReference(); + final int scaledAlignmentGap = scale(2, BETWEEN_ALIGNMENT_GAP, width); + final int scaledContigGap = scale(2, BETWEEN_CONTIG_GAP, width); + final int scaledBorderGap = scale(BORDER_GAP / 3, BORDER_GAP, width); + final double availableSpace = width - (2 * scaledBorderGap + (toDraw.size() - 1) * scaledAlignmentGap + (contigs.size() - 1) * scaledContigGap); + + String lastContig = contigs.get(0); + double lastPosition = scaledBorderGap; + for (SupplementaryAlignment sa : (Iterable) toDraw::iterateInPositionOrder) { + + final double spaceToUse = getSpaceToUse(availableSpace, sa.getLengthOnReference(), totalAlignedBases); + final String newContig = sa.getContig(); + if (lastPosition != scaledBorderGap && !Objects.equals(lastContig, newContig)) { + lastPosition += scaledContigGap; + } + + lastContig = newContig; + final int end = (int) (lastPosition + spaceToUse); + + AlignmentArrow readArrow = new AlignmentArrow(midline, ALIGNMENT_HEIGHT, (int) lastPosition, end, sa.getStrand()); + positions.put(sa, readArrow); + lastPosition = end + scaledAlignmentGap; + } + + drawArrows(g, selected, positions, toDraw.getPrimaryAlignment()); + return positions; + } + + /** + * Scale a value between min/max based on the difference between width and DEFAULT_WIDTH + */ + private static int scale(final int min, final int max, final double width) { + if (width >= DEFAULT_WIDTH) { + return max; + } else { + final double scaleDown = width / (double) DEFAULT_WIDTH; + return Math.max((int) (max * scaleDown), min); + } + } + + private static void drawArrows(final Graphics2D g, final Set selected, final Map positions, final SupplementaryAlignment primary) { + //first draw background colors + positions.forEach((sa, readArrow) -> { + g.setColor(ChromosomeColors.getColor(sa.getContig())); + g.fill(readArrow); + }); + + //outline primary read + g.setColor(PRIMARY_BORDER_COLOR); + final AlignmentArrow primaryShape = positions.get(primary); + g.draw(primaryShape); + + //next draw borders on top to work in case of overlaps, this should come last so it doesn't get clobbered + //in the case of overlaps + for (Map.Entry pair : positions.entrySet()) { + SupplementaryAlignment sa = pair.getKey(); + AlignmentArrow readArrow = pair.getValue(); + g.setColor(ChromosomeColors.getColor(sa.getContig())); + Graphics2D g2 = selected.contains(sa) ? getSelectedGraphics(g) : g; + g2.draw(readArrow); + } + } + + private static double getSpaceToUse(final double availableSpace, final int numberOfBasePairs, final int totalAlignedBases) { + final double fractionOfWhole = (double) numberOfBasePairs / totalAlignedBases; + return fractionOfWhole * availableSpace; + } + + private static void drawContigLabels(final Graphics2D g, final int mid, final Map positions) { + positions.keySet(). + stream() + .collect(Collectors.groupingBy(SupplementaryAlignment::getContig)) + .forEach((contig, alignments) -> { + int start = (int) positions.get(alignments.get(0)).getBounds().getX(); + final Rectangle rightmostBounds = positions.get(alignments.get(alignments.size() - 1)).getBounds(); + int end = (int) (rightmostBounds.getX() + rightmostBounds.getWidth()); + g.setColor(ChromosomeColors.getColor(contig)); + drawCenteredStringWithRangeLines(g, mid, contig, start, end); + }); + } + + private static void drawCenteredStringWithRangeLines(final Graphics2D g, final int mid, final String string, final int start, final int end) { + final FontMetrics fontMetrics = g.getFontMetrics(); + final int labelWidth = fontMetrics.stringWidth(string); + // s----label-----e + final int lineY = mid; + final int height = fontMetrics.getHeight(); + if (labelWidth + 2 * LABEL_GAP < end - start) { + final int leftLineEnd = (start + end - labelWidth) / 2 - LABEL_GAP; + final int rightLineStart = (start + end + labelWidth) / 2 + LABEL_GAP; + g.drawLine(start, lineY - 2, start, lineY + 2); + g.drawLine(end, lineY - 2, end, lineY + 2); + g.drawLine(start, lineY, leftLineEnd, lineY); + g.drawString(string, leftLineEnd + LABEL_GAP, lineY + height / 3); + g.drawLine(rightLineStart, lineY, end, lineY); + } else { + g.drawString(string, start, lineY + height / 3); + } + } + + private static void drawArc(final Graphics2D g, final AlignmentArrow currentPos, final AlignmentArrow nextPos, final boolean highlight, Rectangle2D bounds) { + CubicCurve2D c = new CubicCurve2D.Double(); + Point2D from = currentPos.getTip(); + Point2D to = nextPos.getTail(); + g.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER)); + + + // Determine the distance between the two points + double distance = Math.abs(from.getX() - to.getX()); + + // Calculate the height proportional to the distance. We'll use 1/4 of the bounding box height + // as a baseline and then adjust based on the distance. + double minHeight = bounds.getY(); + double heightAdjustment =1; //.75 + .25 * (distance / bounds.getWidth()); + double maxHeight = bounds.getHeight() - 5; + double actualHeight = maxHeight * heightAdjustment; + double controlY = bounds.getY()+(bounds.getHeight() - actualHeight); + + // Create two control points for the Bezier curve. + Point2D control1 = new Point2D.Double(from.getX() + (to.getX() - from.getX()) / 4, controlY); + Point2D control2 = new Point2D.Double(from.getX() + 3 * (to.getX() - from.getX()) / 4, controlY); + + + c.setCurve(from, control1, control2, to); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + if (highlight) { + g.setColor(SupplementaryAlignmentDiagramDialog.ARC_HIGHLIGHT_COLOR); + } else { + g.setColor(Color.GRAY); + } + g.draw(c); + drawPoint(g, to); + } + + + 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) { + Color originalColor = g.getColor(); + try { + g.setColor(ChromosomeColors.getColor(contig)); + g.drawString(contig, (int) x, y); + } finally { + g.setColor(originalColor); + } + } +} diff --git a/src/main/java/org/broad/igv/ui/supdiagram/SupplementaryAlignmentDiagramDialog.java b/src/main/java/org/broad/igv/ui/supdiagram/SupplementaryAlignmentDiagramDialog.java new file mode 100644 index 0000000000..7304ed2250 --- /dev/null +++ b/src/main/java/org/broad/igv/ui/supdiagram/SupplementaryAlignmentDiagramDialog.java @@ -0,0 +1,79 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2007-2015 Broad Institute + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package org.broad.igv.ui.supdiagram; + +import htsjdk.samtools.SAMRecord; +import htsjdk.samtools.SAMRecordSetBuilder; +import htsjdk.samtools.SAMTag; +import org.broad.igv.sam.Alignment; +import org.broad.igv.sam.SAMAlignment; +import org.broad.igv.sam.SupplementaryGroup; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + + +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); + diagram = new SupplementalAlignmentDiagram(new 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); + + diagram.setVisible(true); + } + + + //for quick component testing + public static void main(String[] args){ + final SAMRecordSetBuilder samRecords = new SAMRecordSetBuilder(); + samRecords.addPair("read1", 13, 50255292, 50255400 ); + final List records = new ArrayList<>(samRecords.getRecords()); + + final SAMRecord record = records.get(0); + record.setAlignmentStart(10000); + + String read = "m141213_163442_00118_c100750562550000001823151707081585_s1_p0/6849/0_5821\t2064\t14\t50255292\t38\t1302S12M1I5M1I25M1D12M1I7M1I2M1I10M1D4M1I7M1I10M1I6M1I4M1D9M5I4M1I4M2I18M1I9M1I5M1I20M1I13M1I6M1I6M1I16M1I3M1I13M1D9M1I4M2I4M1D14M1I21M1I1M1D5M11I2M1I11M1D10M1I12M2I6M1D3M1D11M1D3M1I3M1I19M1I3M2I6M1I8M1D1M2I1M1I6M1I9M3I2M1I4M1I1M1I10M1D1M1I9M1D6M1D4M2D6M2D7M1D13M1I5M1D6M1D8M1I3M4I3M2D1M1D9M1D25M1I4M1D20M1I18M1I9M1D3M1I2M1I4M1I6M1I5M1I18M1I4M3I17M1I1M1I9M1I11M1I24M1I5M1D14M3I16M1I23M2I14M1I6M1I10M1I10M1I13M2I15M1D10M1I12M1I2M1I6M1I11M1I5M1I4M1I11M1I6M1I9M1I19M1I1M1I29M1D23M1D16M1I23M1D3M1I2M1D9M1I20M2I4M1D19M1I11M1D21M1I5M1I11M1I2M1D2M1D7M1D16M1I4M1I8M1D9M1D18M1I12M1I9M1D5M1D5M1I5M1I5M1I36M1D7M3145S\t*\t0\t0\tATTCAAGGCACCAGCAGATTCTTGTCTGAATAGGGCTTGCCCCTCAAAGATTGGTACCTATAAGGCTGAGGTTGGGTGGCTCATACCTGTAATCCCAGCACTTTGGCGAGGCCAAGGTCGGGGGGATCCTTGGCCCAGGAGATTCGACACCAGCCTGCGGCAACAAGGGAGACCTCAATCTTAAAAAAAAGATGGCACCTTATTGCAGTGTACCTCACGTGGCAACAGGGGCACTCATCCTCATTCATGACACAGAGGCCCCTCGTTGACTTCACTTCCTAAAGGCCACTCTTAAATACCATCCACACTGGGCTATTTAGGTTCCCATATATGAATGTGGGGATAATTAACTGTAGAACCATAGCAGCCGTAACTCATGAAAGAACATTAATAAGTTTAACACCTCAGTGAGTATTTGTTGAGTCGTAGGATTAACAGAACATAAGGTTATTTGTAACAAATTTTTTAAAAAGACCACCAAAAATGGTTTTAGTCATTACTAAACCAACAGTGTCAGCAGGAAAAAAAGAAAATAGTAATTACAGAATTCCCGTAAGACATTACCTGCCATAAACTGGAGCACACAATTCATAACTCAAGCTTGACATAAAAAAATGCTGGTGGCAAAAGTAAAATATTTTTATTGCCTCTATTTTAGCCGTTATTAAAAACATAAACAGGCTGGTGACATGGTGTCTTACAGCCTGTATCCCAACACTTTGGGAGGCCAAGGGCAGGCGGAATTCACGGAGATCAGGACGATTTCAAGACCAGCACTGGCCAACAATGGTTGAACCCCAATTCACCACTTAAAATACCAAAATAAGCCAGGCGTAGATGGCGCATTTGCTTAGTAATCCCAAGCTACTCAGAAGGCTGAGGCGGATAATTGCTTGAATCACGGGAGGTGGCAGGTTGCCAGTGAGCTGAGATTGTGCCACTTGCACTCCAGCCTGGGACAAGAGCCGAGCGACTGCCTCAAAAAAAAAAAAAAAAAAAAAAAAATTCAAAGAGAGGTAAATTTCATGTAAGAAGATATACAGCAAGGCGTGGGCTTGGCTGTACTAGTTAACAAAACAATATGTGTGTAGGCGATGTGTCTGTCTTAGCTGGAAAATATTCGTCAGTGGTTTTGTAACCAGGGTACTTGGCTCTTTAAATGGTGTACTCGATGTTCAACATTATGTACGACCATATTGGTGTATAGATCGTGAGTGTGTTAGGAGTGTGGATGAAATTTATTTGTGGGGTGTCTTCAACAGACTGTTTCCAATTATTCTCTGTGTTGGTTTTTTGTGTGTGTTGAGATGGAGTACTCGCATCTGTCGACCAGGCTGGAGTGCAATGCACAATCTCGGACTCACTGACACAGCTCTGCCTCCAGAGTTCACGACTATTCTGCCTTCAGCCTTCCCAGTAGCTGAGGGGGGACTAACAGAGGCGCTCACCACCACACCCAGGCTAATTTCTTTTGTTATTTTTTTAGTAGAGACAGCGGTTTCACCACGTATAGCCAGGCATGGTTCTCAATTTCCTGACCTTTGTTGATCTGCCTGCCTGGCCTCTCAAAAGATTGCTGGATTACAGGCGTGAAGCCACCGTGCCAGGCCTCTCATTCTTATCTTTTTTTTTATATTAACTCAGTTCCATCAGAATCCAAAGCCTCTCCGCTTAAAATGATACTTCCTAAAGCTCTGTAAACCAAATTGTTATCAGCTAATCGAAGTACTAACCATTCTATTTGGCTTAGCATAAGATACGTGTGTGGATGTGGTAGTGTCTCGTGGTGTGTGTTGGTTGTATTGTGATGGGGGGGTGTCACTTGTCTTGTGTGGTGTACTGTGGTGCCAGTGTGGTTCCTGACTCCAGGAGGGTAATCACCCTAATTAAGAAGCAGGAAGCCACAAAGTAGCCATACCTTAGTCATCATGTGGATCATCTTACAGCAAATCTCTGTTAACTTCATAACGTTATAAACCTCAAGGAACGGAGTATTCTATTTTCTTAATGTTGTCAGAGGCACCCTCTGTCCCACCTCTAGGTTTTCTGGGGCTGTTTCTTCACAGGTTTGGTCTTTGTTTTTCCTTGGTTCTTCCCCTTCTTCCCTTTTTTCTTCTTTGTTTGAACCTGACCAGACTTTAATTCAGTAGAGGAAAACATCAGTCCAGTGTTGTATTTTTCAATACTGTGGGAATATTTTTACAAGATGTATTGAATTGCCTGTCATACGGCATAGGCAGGTGGGCTAGATGGCCCATTTTGAAAAAGCAAAAGAAAAAAATTGTTTTTAGAATGTTATTTACCCCCAGCATACCTTCATGATAAGTTCACGGTCTTCTTCATCTGGTCTTTGTATTTTTCTTTCATTTTTTCATTTGACTCTTACAAATTCAGAAGATTTTGGTTGAATATTTCAGCAGCAAAAAAAATGTCAACAAATACTTTTGCAAGAAATGGTGTTACTTTATTATTCTCTGTCAAAATGCTTACTGAATAAGGCAACCATATATAATAAACATATTGAGGCTACAAGTTGGATAACTGAAGGTCCTTCATCTTCAGGAATTTACATAGGGTGGAGAGAAGATTACACAGAGAAGTTAAGAATACTTAACAAACATTAAAGAAACTGTTGAGGAAGAAATAAAGTAAAACAGTATTGGAAAAGCAGTCTAATCCTCCTGCCTCAGCCTCCGAGTAGCTGGGGACTACAAGCGCTGCTGCCATGCCCGGCTAATTTTCTGTTATTTTTTAGTAGAGATGGGGTTATTCCACTGTATTAGCCAGGATGGTCTCCAATCTCCTGACTTGTGATTGCCCGCCTCGCCTCCCAAATTGCTGCGGATTTAACAGCGATGAGCCACCACTGCACCGGCCAAAAGTTTTTAAAAAATATAGTCTCAAGAAAGCATAACCATACTGTTAGTTGTGTTTAACTACTAAAAAATGAGGGGGTCCAAAATTTGGACTTCTGAGCAACTAAACAAAATAGAAACATGATCGTTTATATGGATCTTTCCAATGAGCCTATTAACTTTTCTCCTTCTTTAAATCCAATGGTTTAATTTTCAATATTTTCATTTATCAAGTGATAACATTAAGTAAACAAAAATTAAAATCACATTTTTTACCTTAGGTTGTGAGCTGATTGTGTCCTCCAAAAGATAGTGATTGAGGACAATCTCCACGTTTACCTATGAAGTGTGGCCTTATTTGGTTGATAGTGTCTTTCAGATGTAAGCAAGTTAAAATGATATTCTATACTGGATTAGGGTGGGCCCTTAAATCCAACAACTTCTGACATTATGAGAAAGGCATGTTGGGAAGAACATCGAGACAGAAATACACAATTGAAACATGAAATTGTTGACAGAGCAGGACTTGCCATGCTCACTGTAGCCAGGAAATGCTGAGGACCACCCGCAGCCACAGGAAGCTCCACAGAGGACAAGAAGAATTTTCCTCTACCAAGAACCTTCAAGAAAAGCCAGGTTCTTGTGGTGGGCGTGCTTTCATTCATACTTGCAGGTCTTCAAAACTGTGAGGCAATAATTTCTGTTGCTTTTAAAGCCACTCGGTTTGTGGTCATTTGCCACGCGCCTAGGAATCAAAACTAACTATCAACTGGCCAACTTTCGGTTGTTTTTATTTATTTTTTATTTTCATTTTTATTTTATTTTTTTATTTGTTTGAGATGGAGTCTCCCTTGTTGCCCCAGCTGGAGTGCAGAGTGGGCTATGATCCTCAAGCTCACTTGCAACCCCATGGGCCTCCTGGGTTCAAGTGATTCTCCTTGCTCAGCTCCTGAGTAGCTGGGATATACAGGCACACGCCACCATGCGCGGCTAAATTTTTTGTATTTGAGTAGGACGAGGGGTTCACCTGTTGGCCCAAGGCTTTGGATTCTCGAAACTCCTGACGTCAAAAGTGGATCCGCCCACCTTGGCTTCCAAAAGTGCTGGATTTACAGGCGAAAAGCCACTGCACCCAGCCTGTTTTTAATTAACACCATTTTGGTTTGAAATGCTAGAAGGAATTGGGACATTTAACATATACTTTCATTAGAGAGTAATTTTTTTATTGTAAGATATTAATTTTCTGTTTTTCTTTTTCATCATCTCTTTTTTCTAAAGTTTTCAATGTTGAGCTTGTATATCTTTTTGCAATAACAAAAACTTTCATTTAAAAACACTATTGTCACCATTATTTAACATTTTTCTCGTAAGTTTCTAGCGGTAAGTGGCTAAGAAATGGATGTCGGGTTTGATTTTAACAGCTCCCTGGGTCGGGGAGAACAGGGTTGGACTTGAAATCACAGGAAACATGAATCTTCAGTCCCAAGCTGTGCCATTTCCAGCTGTATGATCTTGAGTAAGCCACTGAACTTTTCGTAAACCTCAAGTTTCCCCCACCTCCCTATGATGTTGTTTCTATTGGGCTCTTCAATTTTCTACCACCGTGTGCCCTTTAAAGAAGGATGAGAGCAAGAGGGAAACAAATGTCAAACATTAATTGCATTTTTAATACAGTTCACTGGAGGATGACCCTGGGGAAGAGGGATTGAGGGAAATCAATTCTGGTGATGAAAACAATAATCGAGAATTACAAGAAGGGTCACAGCTTTAAATCTGGATTAGAGAGAAAAATGCCTCTGCATTTTTCTTTGCCTTTGTGGGGTTCAGTCAGACTGTTACTTGATTCAATGTTTACTGTGATTCAAACAGTTCTGAGGGGAAATGCTTTGTGTGTCATCAACTATTACAGACACTGAGGGGACAGAGATGTTCCAGACATAACCCTGTTTTCCTTCATGTGGGCTTCTCTTGTGAGTGGGTGAAGGGTGGGGTCAAAGTACTAGATGGATTTTTTTTAAATTTTTTATATTGTACTTTAAGTTTTAGGGGTACATGTGCACAAATGTACAGGTTTGTTACATATATTTACATGTGCCCATGTTTGGTGTGCTGCAACCATTAACTCGTCAGTTAACATTTAGGGTTATAATCTCCTAATGCTATTCTACCCCGCTCCCGCACCCATAACAGGCTTCTGTTATTGATGTTTCCCTTTTCTGTGTCCATGTGTTTCTCATTGTTCAATTCCACCTGTTGAGTGAGACATGCCAGTGTTTTTGGTTTTTTGTCCTTGTTTGATGTTTGCGGAGAATGAATGGTTTTCCAGCTTCATCCATGTCCCTACAAAGACATGAAAATTCATTTAAGATGGATTTTTTTAAATGACAAAGCAGAATCAGAAAAAATGGCTAAGAGAGGATGAATTGGCTGAGCCTTCTTTTCCATGCATTGGTGTCACCTTTATAAAGGATTGTGAAAATGTCAAGGCAGGGCAGCTGATCTTTTATTTATTGGTTTTCATTACAGGTGGACCTGGCCAGAAGCCCTTGGGAAACTGGCGGTGCGCTACGGGTAATTTATTTCTAGGCACCTTCAAAGGAGAAAAGGGCAGTGTCCTCCCTTCTCAACTGGATATTGTGCTAGACCATAGTTGCATACCGCCCACAAGCCCCCAAGCATCAGCTGGCCTGGCATCCCTGCCCAGAAGGGCCCATTGCCACTGGTCTTTCCACAAAAACCTGAGCCTTAGGCCAAATTAAAAAGCCACTTGTAGGCATTTTTACCACTTGAATTTACAGCCAAGAGGAAAATCCAACCATGGGGAAAAAGATCCACTGGCTTTTGGCAATTTTCCAAACTTTAAAAACCCCCAAAAGGGCCAAAAAAGGCAAAAACTACCCCAAAGGCCAAAAAGGCTATTAGGAACATTACAAGGAAAAAAAGTCTTTGATATTTCTTTTCTTTGT\t*\tSA:Z:17,64042266,-,2676S17M1D10M1I14M1D26M1I2M1I20M2I1M1I23M1I11M1D8M1D10M1D16M1I3M1I3M1D1M1I4M1I11M1I2M1I6M1D10M1I11M1D3M1D11M1D8M3I2M1I14M1I7M4I12M1I3M1I9M1D13M1D23M1I4M1I13M1D12M1I38M1I11M1I5M1D11M1I17M1I14M1D12M1D8M1I2M1I3M1D12M1I2M2I8M1I19M1D1M2D11M1D25M1I2M1I21M1I28M1I4M1D4M1I1M2I4M1I22M1I5M1I3M1D3M1I9M2I6M1I9M1D2M1D4M1D11M1D2M1I18M1D2M1I13M1I23M1I17M1I7M1I2M1D4M1I21M1I25M1D12M1I3M1I29M1D2M1D1M1D6M1D8M1I8M1D17M1D13M1I54M1D5M1I4M1D12M2I1M1I3M1I5M1I3M1I7M1I9M1I1M2I25M1I2M1D5M1D18M1I27M1I2M1I8M1D9M1D3M1I11M1D6M1I2M1I4M2I3M2I5M1I13M2I4M1I30M1D3M1I9M1I30M2D4M1D4M1D9M1D5M1I5M1I3M2I6M1I15M1D20M1I3M1I13M5I18M2I10M1I7M1D1M1I38M1D39M1I5M1I6M1588S,36,188;chr14,50254065,-,2S19M1D7M1I2M1D8M2I9M1I8M1I1M1I6M1I2M1I2M1D32M1I12M1I7M1D5M1D9M1I15M1I7M1D11M1I5M1I28M1I13M1I12M1I15M1I2M1D1M1I6M1I19M1D2M1D1M1D5M1I8M1I9M1I22M1D3M2D6M1I6M1D5M1I9M1I2M1I1M1I22M2I10M1D12M1I6M1I24M1I4M1I5M2I4M1I5M4I2M1I6M1D8M1I10M2D7M1I8M1D18M1I7M1I4M1D5M1I9M1I2M1D6M1I12M1I18M1D27M1I1M1D7M1D1M1D11M1D15M1I13M1I1M1I3M1I9M1I6M1D23M1I9M1I1M1I3M1D12M1I1M2I12M1I9M1I4M1I2M1D6M1I1M1I33M1I9M2I5M1I8M1I20M1D18M1I10M1I6M1I22M1I16M1I6M3I3M2D9M5I23M1I3M1D10M1D21M1I5M1I7M1I8M1I14M7I8M4I1M1I15M1I5M3D31M1I15M1I26M4624S,38,166;chr8,123906302,+,157S3M1I2M1I8M1I2M1I2M1I3M1I4M1D14M1I2M2I3M1I1M1I7M1I5M1D13M2I3M1I7M1I2M1I9M2D5M1D11M1I2M1D10M2I4M1I3M1I5M1D5M1I4M1I3M1D4M1I5M1I45M1I4M1I12M1D3M1I1M1I4M2I27M1I3M1D4M1I3M1I5M1I2M1D4M1I6M1D13M1I12M1I9M1I11M1I18M2I4M1I12M1D7M1I40M2I3M1I1M1I8M1D26M1I6M1I15M1D3M2I16M2I7M1I5M1D8M1I6M1D16M1I22M1I6M1I3M1D1M1D3M1I10M1D6M1D10M1I1M1D16M1I3M1I1M1I3M1I35M1I5M1I33M1I13M1I22M2I22M1D5M1I1M1D4M1D21M1I15M1I3M1I5M1I9M1D3M1I10M1D13M1D2M1I4M1I3M1D8M2I5M1D2M3I3M1D20M2I10M1I16M1I5M1I1M2I4M2I2M1I14M1D22M1I7M1I4M1I13M1I1M1I5M1D5M1D22M1I7M1I13M1I17M1I15M1I26M1I1M1D11M2D8M1D20M1I7M1D4M1I8M1D8M4409S,30,166;chr8,126480844,-,4239S17M1D6M1I30M1I11M1D4M1I7M1I11M1I9M1I50M1I8M1I10M1411S,2,11;\tKB:f:24.508675\tSB:f:24.508675\tID:i:150690\tMD:Z:24C15G1^G31^C31^A101C24^T17^G36^G11T6^C28^C3^T11^G9A30A1^C19C0A3T4C1T2^A2T3A1T1^C3A2^A0C3^CC0A1T1A1^AA0T0A3T1^A0C14T0T1^A1C4^A0A0A2C9^GG1^A9^T29^C47^G13G92C2^C115A5^A125^C23^T11T10A16^G5^A33^A30^A39^A2^A7^A28^A9^C39^A5^A51^A7\tQE:i:2676\tXE:i:1426\tXI:f:0.8567\tNM:i:203\tXR:i:1374\tAS:i:1426\tQS:i:1302\tXS:i:0\tCV:f:23.60419\tSV:i:2"; + String saTag = "chr17,64042266,-,2676S17M1D10M1I14M1D26M1I2M1I20M2I1M1I23M1I11M1D8M1D10M1D16M1I3M1I3M1D1M1I4M1I11M1I2M1I6M1D10M1I11M1D3M1D11M1D8M3I2M1I14M1I7M4I12M1I3M1I9M1D13M1D23M1I4M1I13M1D12M1I38M1I11M1I5M1D11M1I17M1I14M1D12M1D8M1I2M1I3M1D12M1I2M2I8M1I19M1D1M2D11M1D25M1I2M1I21M1I28M1I4M1D4M1I1M2I4M1I22M1I5M1I3M1D3M1I9M2I6M1I9M1D2M1D4M1D11M1D2M1I18M1D2M1I13M1I23M1I17M1I7M1I2M1D4M1I21M1I25M1D12M1I3M1I29M1D2M1D1M1D6M1D8M1I8M1D17M1D13M1I54M1D5M1I4M1D12M2I1M1I3M1I5M1I3M1I7M1I9M1I1M2I25M1I2M1D5M1D18M1I27M1I2M1I8M1D9M1D3M1I11M1D6M1I2M1I4M2I3M2I5M1I13M2I4M1I30M1D3M1I9M1I30M2D4M1D4M1D9M1D5M1I5M1I3M2I6M1I15M1D20M1I3M1I13M5I18M2I10M1I7M1D1M1I38M1D39M1I5M1I6M1588S,36,188;14,50254065,-,2S19M1D7M1I2M1D8M2I9M1I8M1I1M1I6M1I2M1I2M1D32M1I12M1I7M1D5M1D9M1I15M1I7M1D11M1I5M1I28M1I13M1I12M1I15M1I2M1D1M1I6M1I19M1D2M1D1M1D5M1I8M1I9M1I22M1D3M2D6M1I6M1D5M1I9M1I2M1I1M1I22M2I10M1D12M1I6M1I24M1I4M1I5M2I4M1I5M4I2M1I6M1D8M1I10M2D7M1I8M1D18M1I7M1I4M1D5M1I9M1I2M1D6M1I12M1I18M1D27M1I1M1D7M1D1M1D11M1D15M1I13M1I1M1I3M1I9M1I6M1D23M1I9M1I1M1I3M1D12M1I1M2I12M1I9M1I4M1I2M1D6M1I1M1I33M1I9M2I5M1I8M1I20M1D18M1I10M1I6M1I22M1I16M1I6M3I3M2D9M5I23M1I3M1D10M1D21M1I5M1I7M1I8M1I14M7I8M4I1M1I15M1I5M3D31M1I15M1I26M4624S,38,166;8,123906302,+,157S3M1I2M1I8M1I2M1I2M1I3M1I4M1D14M1I2M2I3M1I1M1I7M1I5M1D13M2I3M1I7M1I2M1I9M2D5M1D11M1I2M1D10M2I4M1I3M1I5M1D5M1I4M1I3M1D4M1I5M1I45M1I4M1I12M1D3M1I1M1I4M2I27M1I3M1D4M1I3M1I5M1I2M1D4M1I6M1D13M1I12M1I9M1I11M1I18M2I4M1I12M1D7M1I40M2I3M1I1M1I8M1D26M1I6M1I15M1D3M2I16M2I7M1I5M1D8M1I6M1D16M1I22M1I6M1I3M1D1M1D3M1I10M1D6M1D10M1I1M1D16M1I3M1I1M1I3M1I35M1I5M1I33M1I13M1I22M2I22M1D5M1I1M1D4M1D21M1I15M1I3M1I5M1I9M1D3M1I10M1D13M1D2M1I4M1I3M1D8M2I5M1D2M3I3M1D20M2I10M1I16M1I5M1I1M2I4M2I2M1I14M1D22M1I7M1I4M1I13M1I1M1I5M1D5M1D22M1I7M1I13M1I17M1I15M1I26M1I1M1D11M2D8M1D20M1I7M1D4M1I8M1D8M4409S,30,166;8,126480844,-,4239S17M1D6M1I30M1I11M1D4M1I7M1I11M1I9M1I50M1I8M1I10M1411S,2,11"; + record.setAttribute(SAMTag.SA, "3,10000,+,100S50M50S,200,0;3,10000,-,50M250S,0,0"); + final Alignment samAlignment = new SAMAlignment(record); + String read2 = "primary\t16\t2\t10000\t100\t200S100M\t*\t0\t0\t*\t*\tSA:Z:3,10000,+,100S50M50S,200,*;3,20000,+,50M250S,0,*;\tRG:Z:x\n"; + SupplementaryAlignmentDiagramDialog frame = new SupplementaryAlignmentDiagramDialog(null, samAlignment, new Dimension(500, 150)); + frame.setVisible(true); + } +}