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); + } +}