From 62ca2e0e4b2b60ac71ba778807e36dedb5dd7be8 Mon Sep 17 00:00:00 2001 From: jrobinso <933148+jrobinso@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:16:15 -0800 Subject: [PATCH 1/3] Fix minor issues with insertion markers. (1) markers not in sync with "hide small indels option. (2) expanded color (blue) too dark for dark mode. --- .../org/igv/sam/AlignmentDataManager.java | 2 +- .../java/org/igv/sam/AlignmentTileLoader.java | 34 +++++----- .../java/org/igv/sam/AlignmentTrackMenu.java | 18 ++++++ .../java/org/igv/sam/InsertionManager.java | 7 -- src/main/java/org/igv/ui/IGV.java | 10 ++- src/main/java/org/igv/ui/panel/MainPanel.java | 4 ++ .../java/org/igv/ui/panel/RulerPanel.java | 64 ++++++++++++++----- .../org/igv/sam/AlignmentTileLoaderTest.java | 5 +- 8 files changed, 102 insertions(+), 42 deletions(-) diff --git a/src/main/java/org/igv/sam/AlignmentDataManager.java b/src/main/java/org/igv/sam/AlignmentDataManager.java index 2f548958f8..84a39565dc 100644 --- a/src/main/java/org/igv/sam/AlignmentDataManager.java +++ b/src/main/java/org/igv/sam/AlignmentDataManager.java @@ -319,7 +319,7 @@ AlignmentInterval loadInterval(String chr, int start, int end, AlignmentTrack.Re SpliceJunctionHelper spliceJunctionHelper = new SpliceJunctionHelper(); AlignmentTileLoader.AlignmentTile t = getLoader().loadTile(seqName, start, end, spliceJunctionHelper, - downsampleOptions, peStats, bisulfiteContext, renderOptions); + downsampleOptions, peStats, bisulfiteContext, renderOptions, getPreferences()); List alignments = t.getAlignments(); List downsampledIntervals = t.getDownsampledIntervals(); this.updateBaseModfications(alignments); diff --git a/src/main/java/org/igv/sam/AlignmentTileLoader.java b/src/main/java/org/igv/sam/AlignmentTileLoader.java index 127df0272f..b3b9c40a8c 100644 --- a/src/main/java/org/igv/sam/AlignmentTileLoader.java +++ b/src/main/java/org/igv/sam/AlignmentTileLoader.java @@ -2,17 +2,18 @@ import htsjdk.samtools.SAMFileHeader; import htsjdk.samtools.util.CloseableIterator; -import org.igv.event.IGVEvent; -import org.igv.logging.*; import org.igv.Globals; +import org.igv.event.IGVEvent; +import org.igv.event.IGVEventBus; +import org.igv.event.IGVEventObserver; +import org.igv.event.StopEvent; +import org.igv.logging.LogManager; +import org.igv.logging.Logger; import org.igv.prefs.IGVPreferences; import org.igv.prefs.PreferencesManager; import org.igv.sam.reader.AlignmentReader; import org.igv.sam.reader.ReadGroupFilter; import org.igv.ui.IGV; -import org.igv.event.IGVEventBus; -import org.igv.event.IGVEventObserver; -import org.igv.event.StopEvent; import org.igv.ui.util.MessageUtils; import org.igv.util.ObjectCache; import org.igv.util.RuntimeUtils; @@ -100,21 +101,21 @@ AlignmentTile loadTile(String chr, AlignmentDataManager.DownsampleOptions downsampleOptions, Map peStats, AlignmentTrack.BisulfiteContext bisulfiteContext, - AlignmentTrack.RenderOptions renderOptions) { + AlignmentTrack.RenderOptions renderOptions, + IGVPreferences preferences) { - final IGVPreferences prefMgr = PreferencesManager.getPreferences(); - boolean filterFailedReads = prefMgr.getAsBoolean(SAM_FILTER_FAILED_READS); - boolean filterSecondaryAlignments = prefMgr.getAsBoolean(SAM_FILTER_SECONDARY_ALIGNMENTS); - boolean filterSupplementaryAlignments = prefMgr.getAsBoolean(SAM_FILTER_SUPPLEMENTARY_ALIGNMENTS); + boolean filterFailedReads = preferences.getAsBoolean(SAM_FILTER_FAILED_READS); + boolean filterSecondaryAlignments = preferences.getAsBoolean(SAM_FILTER_SECONDARY_ALIGNMENTS); + boolean filterSupplementaryAlignments = preferences.getAsBoolean(SAM_FILTER_SUPPLEMENTARY_ALIGNMENTS); ReadGroupFilter filter = ReadGroupFilter.getFilter(); boolean filterDuplicates = renderOptions != null ? renderOptions.getDuplicatesOption() == AlignmentTrack.DuplicatesOption.FILTER - : prefMgr.getAsBoolean(SAM_FILTER_DUPLICATES); + : preferences.getAsBoolean(SAM_FILTER_DUPLICATES); - int qualityThreshold = prefMgr.getAsInt(SAM_QUALITY_THRESHOLD); - int alignmentScoreTheshold = prefMgr.getAsInt(SAM_ALIGNMENT_SCORE_THRESHOLD); + int qualityThreshold = preferences.getAsInt(SAM_QUALITY_THRESHOLD); + int alignmentScoreTheshold = preferences.getAsInt(SAM_ALIGNMENT_SCORE_THRESHOLD); - boolean reducedMemory = prefMgr.getAsBoolean(SAM_REDUCED_MEMORY_MODE); + boolean reducedMemory = preferences.getAsBoolean(SAM_REDUCED_MEMORY_MODE); AlignmentTile t = new AlignmentTile(start, end, spliceJunctionHelper, downsampleOptions, bisulfiteContext, reducedMemory); @@ -254,8 +255,8 @@ AlignmentTile loadTile(String chr, // Compute peStats if (peStats != null) { // TODO -- something smarter re the percentiles. For small samples these will revert to min and max - double minPercentile = prefMgr.getAsFloat(SAM_MIN_INSERT_SIZE_PERCENTILE); - double maxPercentile = prefMgr.getAsFloat(SAM_MAX_INSERT_SIZE_PERCENTILE); + double minPercentile = preferences.getAsFloat(SAM_MIN_INSERT_SIZE_PERCENTILE); + double maxPercentile = preferences.getAsFloat(SAM_MAX_INSERT_SIZE_PERCENTILE); for (PEStats stats : peStats.values()) { stats.computeInsertSize(minPercentile, maxPercentile); stats.computeExpectedOrientation(); @@ -274,7 +275,6 @@ AlignmentTile loadTile(String chr, } t.finish(); - // TODO -- make this optional (on a preference) InsertionManager.getInstance().processAlignments(chr, t.alignments); diff --git a/src/main/java/org/igv/sam/AlignmentTrackMenu.java b/src/main/java/org/igv/sam/AlignmentTrackMenu.java index 4a74871e2b..3057aab7c3 100644 --- a/src/main/java/org/igv/sam/AlignmentTrackMenu.java +++ b/src/main/java/org/igv/sam/AlignmentTrackMenu.java @@ -140,9 +140,27 @@ class AlignmentTrackMenu extends IGVPopupMenu { } renderOptions.setHideSmallIndels(smallIndelsItem.isSelected()); alignmentTrack.repaint(); + IGV.getInstance().repaintHeaderPanels(); })); add(smallIndelsItem); + // Indel size setting + JMenuItem indelSizeItem = new JMenuItem("Set indel size threshold..."); + indelSizeItem.setEnabled(renderOptions.isHideSmallIndels()); + indelSizeItem.addActionListener(aEvt -> UIUtilities.invokeOnEventThread(() -> { + String sith = MessageUtils.showInputDialog("Small indel threshold: ", String.valueOf(renderOptions.getSmallIndelThreshold())); + if (sith != null) { + try { + renderOptions.setSmallIndelThreshold(Integer.parseInt(sith)); + alignmentTrack.repaint(); + IGV.getInstance().repaintHeaderPanels(); + } catch (NumberFormatException exc) { + log.error("Error setting small indel threshold - not an integer", exc); + } + } + })); + add(indelSizeItem); + // Paired end items if (dataManager.isPairedEnd()) { addSeparator(); diff --git a/src/main/java/org/igv/sam/InsertionManager.java b/src/main/java/org/igv/sam/InsertionManager.java index a315bc2d5c..04a6229843 100644 --- a/src/main/java/org/igv/sam/InsertionManager.java +++ b/src/main/java/org/igv/sam/InsertionManager.java @@ -65,17 +65,10 @@ public synchronized void processAlignments(String chr, List alignment insertionMaps.put(chr, insertionMap); } - int minLength = 0; - final IGVPreferences preferences = PreferencesManager.getPreferences(Constants.THIRD_GEN); - if(preferences.getAsBoolean(SAM_HIDE_SMALL_INDEL)) { - minLength = preferences.getAsInt(SAM_SMALL_INDEL_BP_THRESHOLD); - } - for (Alignment a : alignments) { AlignmentBlock[] blocks = a.getInsertions(); if (blocks != null) { for (AlignmentBlock block : blocks) { - if (block.getBasesLength() < minLength) continue; Integer key = block.getStart(); InsertionMarker insertionMarker = insertionMap.get(key); if (insertionMarker == null) { diff --git a/src/main/java/org/igv/ui/IGV.java b/src/main/java/org/igv/ui/IGV.java index 9eff7e20d0..4b54a54ea5 100644 --- a/src/main/java/org/igv/ui/IGV.java +++ b/src/main/java/org/igv/ui/IGV.java @@ -1767,7 +1767,6 @@ private static void startCommandsServer(Main.IGVArgs igvArgs, IGVPreferences pre } } - /** * Swing worker class to startup IGV */ @@ -2157,6 +2156,15 @@ public void repaint(Track track) { } } + /** + * Repaint all header panels. Used to force update of insertion markers. + */ + public void repaintHeaderPanels() { + getMainPanel().repaintHeaderPanels(); + } + + + private volatile boolean isLoading = false; private Set pendingTracks = null; diff --git a/src/main/java/org/igv/ui/panel/MainPanel.java b/src/main/java/org/igv/ui/panel/MainPanel.java index 20579c2eba..2dacda34c7 100644 --- a/src/main/java/org/igv/ui/panel/MainPanel.java +++ b/src/main/java/org/igv/ui/panel/MainPanel.java @@ -577,5 +577,9 @@ public int getSnapshotHeight(boolean batch) { } } + public void repaintHeaderPanels() { + headerPanelContainer.repaint(); + } + } diff --git a/src/main/java/org/igv/ui/panel/RulerPanel.java b/src/main/java/org/igv/ui/panel/RulerPanel.java index 0d68a59b9d..f8e85ce0ed 100644 --- a/src/main/java/org/igv/ui/panel/RulerPanel.java +++ b/src/main/java/org/igv/ui/panel/RulerPanel.java @@ -8,12 +8,13 @@ */ package org.igv.ui.panel; -import org.igv.logging.*; import org.igv.Globals; import org.igv.feature.Chromosome; import org.igv.feature.genome.ChromosomeCoordinate; import org.igv.feature.genome.Genome; import org.igv.feature.genome.GenomeManager; +import org.igv.logging.LogManager; +import org.igv.logging.Logger; import org.igv.prefs.PreferencesManager; import org.igv.sam.AlignmentTrack; import org.igv.sam.InsertionManager; @@ -35,7 +36,7 @@ import java.util.List; import java.util.function.Consumer; -import static org.igv.prefs.Constants.*; +import static org.igv.prefs.Constants.DEFAULT_GENOME; /** * @author jrobinso @@ -48,28 +49,31 @@ public class RulerPanel extends JPanel { private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat(); private static final int INSERTION_ROW_HEIGHT = 9; + + public static final String WHOLE_GENOME_TOOLTIP = "Click on a chromosome number to jump to that chromosome," + + "
or click and drag to zoom in."; + + public static final String CHROM_TOOLTIP = "Click and drag to zoom in."; + private final boolean darkMode; - // TODO -- get from preferences boolean drawSpan = true; private Font tickFont = FontManager.getFont(10); private Font spanFont = FontManager.getFont(Font.BOLD, 12); + private boolean showInsertions = false; private List clickLinks = new ArrayList(); private static Color dragColor = new Color(.5f, .5f, 1f, .3f); private static Color zoomBoundColor = new Color(0.5f, 0.5f, 0.5f); - private Color expandedInsertionColor = Color.BLUE; - private Color collapsedInsertionColor = Color.BLACK; - private Color zoomedoutInsertionColor = ColorUtilities.modifyAlpha(Color.LIGHT_GRAY, 50); + private Color expandedInsertionColor = Globals.isDarkMode() ? Color.BLUE.brighter() : Color.BLUE; + private Color collapsedInsertionColor = Globals.isDarkMode() ? Color.WHITE : Color.BLACK; + private Color zoomedoutInsertionColor = Globals.isDarkMode() ? Color.darkGray : ColorUtilities.modifyAlpha(collapsedInsertionColor, 20); boolean dragging = false; int dragStart; int dragEnd; - public static final String WHOLE_GENOME_TOOLTIP = "Click on a chromosome number to jump to that chromosome," + - "
or click and drag to zoom in."; - public static final String CHROM_TOOLTIP = "Click and drag to zoom in."; ReferenceFrame frame; @@ -88,7 +92,7 @@ protected void paintComponent(Graphics g) { super.paintComponent(g); - if(darkMode) { + if (darkMode) { setBackground(UIManager.getColor("Panel.background")); expandedInsertionColor = Color.CYAN; collapsedInsertionColor = Color.WHITE; @@ -132,7 +136,10 @@ private void render(Graphics g) { Collection tracks = IGV.getInstance().getAlignmentTracks(); int maxVizWindow = tracks.size() == 0 ? 0 : tracks.stream().mapToInt(t -> t.getVisibilityWindow()).max().getAsInt(); if (!frame.getChrName().equals(Globals.CHR_ALL) && (frame.getEnd() - frame.getOrigin()) <= maxVizWindow) { + showInsertions = true; drawInsertionMarkers(g); + } else { + showInsertions = false; } } } @@ -300,6 +307,13 @@ private void drawInsertionMarkers(Graphics g) { if (insertionMarkers == null) return; + int minLength = Integer.MAX_VALUE; + boolean hideSmallIndels = false; + for (AlignmentTrack track : IGV.getInstance().getAlignmentTracks()) { + hideSmallIndels = hideSmallIndels || track.getRenderOptions().isHideSmallIndels(); + minLength = Math.min(minLength, track.getRenderOptions().getSmallIndelThreshold()); + } + InsertionMarker expanded = frame.getExpandedInsertion(); int w = (int) ((1.41 * INSERTION_ROW_HEIGHT) / 2); @@ -307,6 +321,10 @@ private void drawInsertionMarkers(Graphics g) { for (InsertionMarker insertionMarker : insertionMarkers) { + if (hideSmallIndels && insertionMarker.size < minLength) { + continue; + } + int x0; int x1; Polygon p; @@ -332,9 +350,10 @@ private void drawInsertionMarkers(Graphics g) { p = new Polygon(new int[]{x0 - w, x1 + w, x0}, new int[]{y, y, y + INSERTION_ROW_HEIGHT}, 3); + double expandedInsertionWidth = insertionMarker.size / frame.getScale(); - if (expandedInsertionWidth > 5) { + if (expandedInsertionWidth > 1) { c = collapsedInsertionColor; String tooltipText = "Click to expand insertion (" + (insertionMarker.size + "bases)"); Rectangle clickArea = p.getBounds(); @@ -349,7 +368,7 @@ private void drawInsertionMarkers(Graphics g) { } } - if (x1 + w >= 0 && x0 - w <= getWidth()) { + if (x1 + w >= 0 && x0 - w <= getWidth() && c != null) { g.setColor(c); g.fillPolygon(p); } @@ -419,9 +438,12 @@ public void mouseMoved(MouseEvent e) { return; } } + if(inInsertionArea(e)) { + // In insetion area + return; + } setCursor(Cursor.getDefaultCursor()); setToolTipText(isWholeGenomeView() ? WHOLE_GENOME_TOOLTIP : CHROM_TOOLTIP); - } @Override @@ -437,6 +459,9 @@ public void mouseEntered(MouseEvent e) { @Override public void mouseDragged(MouseEvent e) { + if(inInsertionArea(e)) { + return; + } if (Math.abs(e.getPoint().getX() - dragStart) > 1) { dragEnd = e.getX(); dragging = true; @@ -449,12 +474,13 @@ public void mousePressed(final MouseEvent e) { if (e.isPopupTrigger()) { // ignore } else { - dragStart = e.getX(); + if(!inInsertionArea(e)){ + dragStart = e.getX(); + } mouseDown = e; } } - @Override public void mouseReleased(MouseEvent e) { if (e.isPopupTrigger()) { @@ -472,6 +498,10 @@ public void mouseReleased(MouseEvent e) { } } + private boolean inInsertionArea(MouseEvent e) { + return showInsertions && e.getY() >= getHeight() - INSERTION_ROW_HEIGHT - 1; + } + private double distance(MouseEvent e1, MouseEvent e2) { double dx = e1.getX() - e2.getX(); double dy = e1.getY() - e2.getY(); @@ -497,6 +527,10 @@ public void doMouseClick(MouseEvent evt) { clickHandled = true; } } + if(showInsertions && e.getY() >= getHeight() - INSERTION_ROW_HEIGHT) { + // In insetion area + return; + } if (!clickHandled && !isWholeGenomeView()) { double newLocation = frame.getChromosomePosition(e); frame.centerOnLocation(newLocation); diff --git a/src/test/java/org/igv/sam/AlignmentTileLoaderTest.java b/src/test/java/org/igv/sam/AlignmentTileLoaderTest.java index 0e08ca42be..22b8d7f8bd 100644 --- a/src/test/java/org/igv/sam/AlignmentTileLoaderTest.java +++ b/src/test/java/org/igv/sam/AlignmentTileLoaderTest.java @@ -2,6 +2,7 @@ import org.igv.AbstractHeadlessTest; import org.igv.prefs.Constants; +import org.igv.prefs.IGVPreferences; import org.igv.prefs.PreferencesManager; import org.igv.sam.reader.AlignmentReader; import org.igv.sam.reader.AlignmentReaderFactory; @@ -79,7 +80,9 @@ private AlignmentTileLoader.AlignmentTile tstKeepPairsDownsample(String path, St AlignmentDataManager.DownsampleOptions downsampleOptions = new AlignmentDataManager.DownsampleOptions(true, 50, actMaxDepth); - AlignmentTileLoader.AlignmentTile tile = loader.loadTile(sequence, start, end, null, downsampleOptions, null, null, null); + IGVPreferences preferences = new IGVPreferences(); + AlignmentTileLoader.AlignmentTile tile = loader.loadTile(sequence, start, end, null, + downsampleOptions, null, null, null, preferences); List alignments = tile.getAlignments(); int count = 0; Map pairedReads = new HashMap(); From 6c476779782bb8f590591f1d8d1f53a373037712 Mon Sep 17 00:00:00 2001 From: Jim Robinson <933148+jrobinso@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:33:24 -0800 Subject: [PATCH 2/3] Update src/main/java/org/igv/ui/panel/RulerPanel.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/org/igv/ui/panel/RulerPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/igv/ui/panel/RulerPanel.java b/src/main/java/org/igv/ui/panel/RulerPanel.java index f8e85ce0ed..4ae22f29e6 100644 --- a/src/main/java/org/igv/ui/panel/RulerPanel.java +++ b/src/main/java/org/igv/ui/panel/RulerPanel.java @@ -439,7 +439,7 @@ public void mouseMoved(MouseEvent e) { } } if(inInsertionArea(e)) { - // In insetion area + // In insertion area return; } setCursor(Cursor.getDefaultCursor()); From 78449946e7e2266d7174cac2e64afa8e11667aec Mon Sep 17 00:00:00 2001 From: Jim Robinson <933148+jrobinso@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:33:42 -0800 Subject: [PATCH 3/3] Update src/main/java/org/igv/ui/panel/RulerPanel.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/org/igv/ui/panel/RulerPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/igv/ui/panel/RulerPanel.java b/src/main/java/org/igv/ui/panel/RulerPanel.java index 4ae22f29e6..bf9c2af4f7 100644 --- a/src/main/java/org/igv/ui/panel/RulerPanel.java +++ b/src/main/java/org/igv/ui/panel/RulerPanel.java @@ -528,7 +528,7 @@ public void doMouseClick(MouseEvent evt) { } } if(showInsertions && e.getY() >= getHeight() - INSERTION_ROW_HEIGHT) { - // In insetion area + // In insertion area return; } if (!clickHandled && !isWholeGenomeView()) {