/* * 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. */ /* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.broad.igv.sam; import org.apache.log4j.Logger; import org.broad.igv.Globals; import org.broad.igv.data.CoverageDataSource; import org.broad.igv.event.IGVEventBus; import org.broad.igv.feature.FeatureUtils; import org.broad.igv.feature.LocusScore; import org.broad.igv.feature.genome.Genome; import org.broad.igv.goby.GobyCountArchiveDataSource; import org.broad.igv.prefs.IGVPreferences; import org.broad.igv.prefs.PreferencesManager; import org.broad.igv.renderer.BarChartRenderer; import org.broad.igv.renderer.DataRange; import org.broad.igv.renderer.DataRenderer; import org.broad.igv.renderer.GraphicUtils; import org.broad.igv.session.IGVSessionReader; import org.broad.igv.session.SubtlyImportant; import org.broad.igv.tdf.TDFDataSource; import org.broad.igv.tdf.TDFReader; import org.broad.igv.track.*; import org.broad.igv.ui.DataRangeDialog; import org.broad.igv.ui.IGV; import org.broad.igv.ui.color.ColorUtilities; import org.broad.igv.event.AlignmentTrackEvent; import org.broad.igv.ui.panel.FrameManager; import org.broad.igv.ui.panel.IGVPopupMenu; import org.broad.igv.ui.panel.ReferenceFrame; import org.broad.igv.ui.util.FileDialogUtils; 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 javax.swing.*; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlType; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collection; import java.util.List; import static org.broad.igv.prefs.Constants.*; /** * @author jrobinso */ @XmlType(factoryMethod = "getNextTrack") public class CoverageTrack extends AbstractTrack implements ScalableTrack { private static Logger log = Logger.getLogger(CoverageTrack.class); public static final int TEN_MB = 10000000; static DecimalFormat locationFormatter = new DecimalFormat(); char[] nucleotides = {'a', 'c', 'g', 't', 'n'}; public static Color lightBlue = new Color(0, 0, 150); private static Color coverageGrey = new Color(175, 175, 175); public static final Color negStrandColor = new Color(140, 140, 160); public static final Color posStrandColor = new Color(160, 140, 140); public static final boolean DEFAULT_AUTOSCALE = true; public static final boolean DEFAULT_SHOW_REFERENCE = false; // User settable state -- these attributes should be stored in the session file @XmlAttribute private float snpThreshold; //TODO This appears to not be used anywhere, remove? @XmlAttribute private boolean showReference; private AlignmentTrack alignmentTrack; private AlignmentDataManager dataManager; private CoverageDataSource dataSource; private DataRenderer dataSourceRenderer; private IntervalRenderer intervalRenderer; private IGVPreferences prefs; private JMenuItem dataRangeItem; private Genome genome; private boolean removed = false; /** * Whether to autoscale across all ReferenceFrames * Default is true because we usually do, SashimiPlot does not */ private boolean globalAutoScale = true; private AlignmentTrack.RenderOptions renderOptions = null; @SubtlyImportant public CoverageTrack() { } /** * Copy constructor. Used for Sashimi plot. * * @param track */ public CoverageTrack(CoverageTrack track) { this(track.getResourceLocator(), track.getName(), track.alignmentTrack, track.genome); if (track.dataManager != null) this.setDataManager(track.dataManager); if (track.dataSource != null) this.setDataSource(track.dataSource); } public CoverageTrack(ResourceLocator locator, String name, AlignmentTrack alignmentTrack, Genome genome) { super(locator, locator.getPath() + "_coverage", name); super.setDataRange(new DataRange(0, 0, 60)); this.alignmentTrack = alignmentTrack; this.genome = genome; intervalRenderer = new IntervalRenderer(); setMaximumHeight(40); setColor(coverageGrey); prefs = PreferencesManager.getPreferences(); snpThreshold = prefs.getAsFloat(SAM_ALLELE_THRESHOLD); autoScale = DEFAULT_AUTOSCALE; showReference = DEFAULT_SHOW_REFERENCE; //TODO logScale = prefs. } public void setDataManager(AlignmentDataManager dataManager) { this.dataManager = dataManager; } public void setDataSource(CoverageDataSource dataSource) { this.dataSource = dataSource; dataSourceRenderer = new BarChartRenderer(); setDataRange(new DataRange(0, 0, 1.5f * (float) dataSource.getDataMax())); } @Override public boolean isReadyToPaint(ReferenceFrame frame) { if (frame.getChrName().equals(Globals.CHR_ALL) || frame.getScale() > dataManager.getMinVisibleScale()) { return true; // Nothing to paint } else { return dataManager.isLoaded(frame); } } @Override public void load(ReferenceFrame referenceFrame) { dataManager.load(referenceFrame, renderOptions, true); } public void setSnpThreshold(float snpThreshold) { this.snpThreshold = snpThreshold; } public float getSnpThreshold() { return snpThreshold; } public boolean isShowReference() { return showReference; } public void setRenderOptions(AlignmentTrack.RenderOptions renderOptions) { this.renderOptions = renderOptions; } AlignmentTrack.RenderOptions getRenderOptions() { return this.renderOptions; } public boolean isRemoved() { return removed; } @Override public void dispose() { super.dispose(); removed = true; if(dataManager != null) { dataManager.dumpAlignments(); IGVEventBus.getInstance().unsubscribe(dataManager); } dataManager = null; dataSource = null; alignmentTrack = null; setVisible(false); } public void render(RenderContext context, Rectangle rect) { if (context.getScale() > dataManager.getMinVisibleScale() && dataSource == null) { Rectangle visibleRect = context.getVisibleRect().intersection(rect); Graphics2D g = context.getGraphic2DForColor(Color.gray); GraphicUtils.drawCenteredText("Zoom in to see coverage.", visibleRect, g); return; } drawData(context, rect); drawBorder(context, rect); if (dataSourceRenderer != null) { dataSourceRenderer.renderBorder(this, context, rect); dataSourceRenderer.renderAxis(this, context, rect); } else { DataRenderer.drawScale(this.getDataRange(), context, rect); } } public void drawData(RenderContext context, Rectangle rect) { float maxRange = PreferencesManager.getPreferences().getAsFloat(SAM_MAX_VISIBLE_RANGE); float minVisibleScale = (maxRange * 1000) / 700; if (context.getScale() < minVisibleScale && !context.getChr().equals(Globals.CHR_ALL)) { //Show coverage calculated from intervals if zoomed in enough AlignmentInterval interval = null; if (dataManager != null) { dataManager.load(context.getReferenceFrame(), renderOptions, true); interval = dataManager.getLoadedInterval(context.getReferenceFrame()); } if (interval != null) { if (interval.contains(context.getChr(), (int) context.getOrigin(), (int) context.getEndLocation())) { //if (autoScale) rescale(context.getReferenceFrame()); intervalRenderer.paint(context, rect, interval.getCounts()); return; } } } //Not rendered yet. Use precomputed scores, if available List<LocusScore> scores = getInViewScores(context.getReferenceFrame()); if (scores != null) { dataSourceRenderer.renderScores(this, scores, context, rect); } } private List<LocusScore> getInViewScores(ReferenceFrame frame) { List<LocusScore> inViewScores = null; if (dataSource != null) { String chr = frame.getChrName(); int start = (int) frame.getOrigin(); int end = (int) frame.getEnd(); int zoom = frame.getZoom(); inViewScores = dataSource.getSummaryScoresForRange(chr, start, end, zoom); // Trim // Trim scores int startIdx = FeatureUtils.getIndexBefore(start, inViewScores); int endIdx = inViewScores.size() - 1; // Starting guess int tmp = Math.max(0, FeatureUtils.getIndexBefore(end, inViewScores)); for (int i = tmp; i < inViewScores.size(); i++) { if (inViewScores.get(i).getStart() > end) { endIdx = i - 1; break; } } endIdx = Math.max(startIdx + 1, endIdx); if(inViewScores.size() > 1) { return startIdx == 0 && endIdx == inViewScores.size() - 1 ? inViewScores : inViewScores.subList(startIdx, endIdx); } else { return inViewScores; } } return inViewScores; } @Override public Range getInViewRange(ReferenceFrame frame) { if (dataManager == null || frame.getScale() > dataManager.getMinVisibleScale()) { List<LocusScore> scores = getInViewScores(frame); if (scores != null && scores.size() > 0) { float min = scores.get(0).getScore(); float max = min; for (int i = 1; i < scores.size(); i++) { LocusScore score = scores.get(i); float value = score.getScore(); min = Math.min(value, min); max = Math.max(value, max); } return new Range(min, max); } else { return null; } } else { AlignmentInterval interval = dataManager.getLoadedInterval(frame); if (interval == null) return null; int origin = (int) frame.getOrigin(); int end = (int) frame.getEnd() + 1; int intervalMax = interval.getMaxCount(origin, end); return new Range(0, Math.max(10, intervalMax)); } } /** * Draw border and scale * * @param context * @param rect */ private void drawBorder(RenderContext context, Rectangle rect) { context.getGraphic2DForColor(Color.gray).drawLine( rect.x, rect.y + rect.height, rect.x + rect.width, rect.y + rect.height); } public void drawScale(RenderContext context, Rectangle rect) { DataRenderer.drawScale(getDataRange(), context, rect); } public boolean isLogNormalized() { return false; } public String getValueStringAt(String chr, double position, int mouseX, int mouseY, ReferenceFrame frame) { float maxRange = PreferencesManager.getPreferences().getAsFloat(SAM_MAX_VISIBLE_RANGE); float minVisibleScale = (maxRange * 1000) / 700; StringBuffer buf = new StringBuffer(); if (!chr.equals("All")) { String posString = chr + ":" + locationFormatter.format(Math.floor(position + 1)); buf.append(posString + "<br>"); buf.append("<hr>"); } if (frame.getScale() < minVisibleScale) { AlignmentInterval interval = dataManager.getLoadedInterval(frame); if (interval != null && interval.contains(chr, (int) position, (int) position)) { AlignmentCounts counts = interval.getCounts(); if (counts != null) { buf.append(counts.getValueStringAt((int) position)); } } } else { buf.append(getPrecomputedValueString(chr, position, frame)); } return buf.toString(); } private String getPrecomputedValueString(String chr, double position, ReferenceFrame frame) { if (dataSource == null) { return ""; } int zoom = Math.max(0, frame.getZoom()); List<LocusScore> scores = dataSource.getSummaryScoresForRange(chr, (int) position - 10, (int) position + 10, zoom); // give a 2 pixel window, otherwise very narrow features will be missed. double bpPerPixel = frame.getScale(); double minWidth = 2 * bpPerPixel; /* * */ if (scores == null) { return ""; } else { LocusScore score = (LocusScore) FeatureUtils.getFeatureAt(position, 0, scores); return score == null ? "" : "Mean count: " + score.getScore(); } } public float getRegionScore(String chr, int start, int end, int zoom, RegionScoreType type, String frameName) { return 0; } public void rescale(ReferenceFrame iframe) { List<ReferenceFrame> frameList = new ArrayList<ReferenceFrame>(); if (iframe != null) frameList.add(iframe); if (globalAutoScale) { frameList.addAll(FrameManager.getFrames()); } if (autoScale && dataManager != null) { int max = 10; for (ReferenceFrame frame : frameList) { AlignmentInterval interval = dataManager.getLoadedInterval(frame); if (interval == null) continue; int origin = (int) frame.getOrigin(); int end = (int) frame.getEnd() + 1; int intervalMax = interval.getMaxCount(origin, end); max = intervalMax > max ? intervalMax : max; } DataRange newRange = new DataRange(0, max); newRange.setType(getDataRange().getType()); super.setDataRange(newRange); } } /** * Class to render coverage track, including mismatches. * <p/> * NOTE: This class has been extensively optimized with the aid of a profiler, attempts to "clean up" this code * should be done with frequent profiling, or it will likely have detrimental performance impacts. */ class IntervalRenderer { private void paint(RenderContext context, Rectangle rect, AlignmentCounts alignmentCounts) { Color color = getColor(); Graphics2D graphics = context.getGraphic2DForColor(color); DataRange range = getDataRange(); double maxRange = range.isLog() ? Math.log10(range.getMaximum() + 1) : range.getMaximum(); final double rectX = rect.getX(); final double rectMaxX = rect.getMaxX(); final double rectY = rect.getY(); final double rectMaxY = rect.getMaxY(); final double rectHeight = rect.getHeight(); final double origin = context.getOrigin(); final double colorScaleMax = getColorScale().getMaximum(); final double scale = context.getScale(); boolean bisulfiteMode = getRenderOptions().getColorOption() == AlignmentTrack.ColorOption.BISULFITE; // First pass, coverage int lastpX = -1; int start = alignmentCounts.getStart(); int step = alignmentCounts.getBucketSize(); int nPoints = alignmentCounts.getNumberOfPoints(); boolean isSparse = alignmentCounts instanceof SparseAlignmentCounts; for (int idx = 0; idx < nPoints; idx++) { int pos = isSparse ? ((SparseAlignmentCounts) alignmentCounts).getPosition(idx) : start + idx * step; int pX = (int) (rectX + (pos - origin) / scale); if (pX > rectMaxX) { break; // We're done, data is position sorted so we're beyond the right-side of the view } int dX = (int) (rectX + (pos + step - origin) / scale) - pX; dX = dX < 1 ? 1 : dX; if (pX + dX > lastpX) { int pY = (int) rectMaxY - 1; int totalCount = alignmentCounts.getTotalCount(pos); double tmp = range.isLog() ? Math.log10(totalCount + 1) / maxRange : totalCount / maxRange; int height = (int) (tmp * rectHeight); height = Math.min(height, rect.height - 1); int topY = (pY - height); if (dX > 3) { dX--; // Create a little space between bars when there is room. } if (height > 0) { graphics.fillRect(pX, topY, dX, height); } lastpX = pX + dX; } } // Second pass - mark mismatches if (alignmentCounts.hasBaseCounts()) { lastpX = -1; BisulfiteCounts bisulfiteCounts = alignmentCounts.getBisulfiteCounts(); final int intervalEnd = alignmentCounts.getEnd(); final int intervalStart = alignmentCounts.getStart(); byte[] refBases = null; // Dont try to compute mismatches for intervals > 10 MB if ((intervalEnd - intervalStart) < TEN_MB) { refBases = genome.getSequence(context.getChr(), intervalStart, intervalEnd); } start = alignmentCounts.getStart(); nPoints = alignmentCounts.getNumberOfPoints(); isSparse = alignmentCounts instanceof SparseAlignmentCounts; for (int idx = 0; idx < nPoints; idx++) { int pos = isSparse ? ((SparseAlignmentCounts) alignmentCounts).getPosition(idx) : start + idx; BisulfiteCounts.Count bc = null; if (bisulfiteMode && bisulfiteCounts != null) { bc = bisulfiteCounts.getCount(pos); } int pX = (int) (rectX + (pos - origin) / scale); if (pX > rectMaxX) { break; // We're done, data is position sorted so we're beyond the right-side of the view } int dX = (int) (rectX + (pos + 1 - origin) / scale) - pX; dX = dX < 1 ? 1 : dX; if (pX + dX > lastpX) { // Test to see if any single nucleotide mismatch (nucleotide other than the reference) // has a quality weight > 20% of the total boolean mismatch = false; if (refBases != null) { int refIdx = pos - intervalStart; if (refIdx >= 0 && refIdx < refBases.length) { if (bisulfiteMode) { mismatch = (bc != null && (bc.methylatedCount + bc.unmethylatedCount) > 0); } else { byte ref = refBases[refIdx]; mismatch = alignmentCounts.isConsensusMismatch(pos, ref, context.getChr(), snpThreshold); } } } if (!mismatch) { continue; } int pY = (int) rectMaxY - 1; int totalCount = alignmentCounts.getTotalCount(pos); double tmp = range.isLog() ? Math.log10(totalCount + 1) / maxRange : totalCount / maxRange; int height = (int) (tmp * rectHeight); height = Math.min(height, rect.height - 1); if (dX > 3) { dX--; // Create a little space between bars when there is room. } if (height > 0) { if (bisulfiteMode) { if (bc != null) { drawBarBisulfite(context, pos, rect, totalCount, maxRange, pY, pX, dX, bc, range.isLog()); } } else { drawBar(context, pos, rect, totalCount, maxRange, pY, pX, dX, alignmentCounts, range.isLog()); } } lastpX = pX + dX; } } } } } /** * Draw a colored bar to represent a mismatch to the reference. The height is proportional to the % of * reads with respect to the total. If "showAllSnps == true" the bar is shaded by avg read quality. * * @param context * @param pos * @param rect * @param max * @param pY * @param pX * @param dX * @param interval * @return */ int drawBar(RenderContext context, int pos, Rectangle rect, double totalCount, double max, int pY, int pX, int dX, AlignmentCounts interval, boolean isLog) { for (char nucleotide : nucleotides) { int count = interval.getCount(pos, (byte) nucleotide); Color c = AlignmentRenderer.nucleotideColors.get(nucleotide); Graphics2D tGraphics = context.getGraphic2DForColor(c); double tmp = isLog ? (count / totalCount) * Math.log10(totalCount + 1) / max : count / max; int height = (int) (tmp * rect.getHeight()); height = Math.min(pY - rect.y, height); int baseY = pY - height; if (height > 0) { tGraphics.fillRect(pX, baseY, dX, height); } pY = baseY; } return pX + dX; } int drawBarBisulfite(RenderContext context, int pos, Rectangle rect, double totalCount, double maxRange, int pY, int pX0, int dX, BisulfiteCounts.Count count, boolean isLog) { // If bisulfite mode, we expand the rectangle to make it more visible. This code is copied from // AlignmentRenderer int pX = pX0; if (dX < 3) { int expansion = dX; pX -= expansion; dX += (2 * expansion); } double nMethylated = count.methylatedCount; double unMethylated = count.unmethylatedCount; Color c = Color.red; Graphics2D tGraphics = context.getGraphic2DForColor(c); //Not all reads at a position are informative, color by % of informative reads // double totalInformative = count.methylatedCount + count.unmethylatedCount; // double mult = totalCount / totalInformative; // nMethylated *= mult; // unMethylated *= mult; double tmp = isLog ? (nMethylated / totalCount) * Math.log10(totalCount + 1) / maxRange : nMethylated / maxRange; int height = (int) (tmp * rect.getHeight()); height = Math.min(pY - rect.y, height); int baseY = pY - height; if (height > 0) { tGraphics.fillRect(pX, baseY, dX, height); } pY = baseY; c = Color.blue; tGraphics = context.getGraphic2DForColor(c); tmp = isLog ? (unMethylated / totalCount) * Math.log10(totalCount + 1) / maxRange : unMethylated / maxRange; height = (int) (tmp * rect.getHeight()); height = Math.min(pY - rect.y, height); baseY = pY - height; if (height > 0) { tGraphics.fillRect(pX, baseY, dX, height); } return pX + dX; } /** * Strand-specific * * @param context * @param pos * @param rect * @param maxCount * @param pY * @param pX * @param dX * @param isPositive * @param interval * @return */ void drawStrandBar(RenderContext context, int pos, Rectangle rect, double maxCount, int pY, int pX, int dX, boolean isPositive, AlignmentCounts interval) { for (char nucleotide : nucleotides) { Color c = AlignmentRenderer.nucleotideColors.get(nucleotide); Graphics2D tGraphics = context.getGraphic2DForColor(c); int count = isPositive ? interval.getPosCount(pos, (byte) nucleotide) : interval.getNegCount(pos, (byte) nucleotide); int height = (int) Math.round(count * rect.getHeight() / maxCount); height = isPositive ? Math.min(pY - rect.y, height) : Math.min(rect.y + rect.height - pY, height); int baseY = (int) (isPositive ? (pY - height) : pY); if (height > 0) { tGraphics.fillRect(pX, baseY, dX, height); } pY = isPositive ? baseY : baseY + height; } } static float[] colorComps = new float[3]; private Color getShadedColor(int qual, Color backgroundColor, Color color) { float alpha = 0; int minQ = prefs.getAsInt(SAM_BASE_QUALITY_MIN); ColorUtilities.getRGBColorComponents(color); if (qual < minQ) { alpha = 0.1f; } else { int maxQ = prefs.getAsInt(SAM_BASE_QUALITY_MAX); alpha = Math.max(0.1f, Math.min(1.0f, 0.1f + 0.9f * (qual - minQ) / (maxQ - minQ))); } // Round alpha to nearest 0.1, for effeciency; alpha = ((int) (alpha * 10 + 0.5f)) / 10.0f; if (alpha >= 1) { return color; } else { return ColorUtilities.getCompositeColor(backgroundColor, color, alpha); } } /** * Override to return a specialized popup menu * * @return */ @Override public IGVPopupMenu getPopupMenu(TrackClickEvent te) { Collection<Track> tmp = new ArrayList<Track>(); tmp.add(this); IGVPopupMenu popupMenu = TrackMenuUtils.getPopupMenu(tmp, getName(), te); popupMenu.addSeparator(); this.addSnpTresholdItem(popupMenu); popupMenu.addSeparator(); addLoadCoverageDataItem(popupMenu); popupMenu.addSeparator(); addCopyDetailsItem(popupMenu, te); popupMenu.addSeparator(); addShowItems(popupMenu); return popupMenu; } private void addCopyDetailsItem(IGVPopupMenu popupMenu, TrackClickEvent te) { JMenuItem copyDetails = new JMenuItem("Copy Details to Clipboard"); copyDetails.setEnabled(false); if (te.getFrame() != null) { final String details = getValueStringAt(te.getFrame().getChrName(), te.getChromosomePosition(), te.getMouseEvent().getX(), te.getMouseEvent().getY(), te.getFrame()); copyDetails.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (details != null) { String deets = details.replace("<br>", System.getProperty("line.separator")); StringUtils.copyTextToClipboard(deets); } } }); copyDetails.setEnabled(details != null); } popupMenu.add(copyDetails); } public static JMenuItem addDataRangeItem(final Frame parentFrame, JPopupMenu menu, final Collection<? extends Track> selectedTracks) { JMenuItem maxValItem = new JMenuItem("Set Data Range"); maxValItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (selectedTracks.size() > 0) { DataRange prevAxisDefinition = selectedTracks.iterator().next().getDataRange(); DataRangeDialog dlg = new DataRangeDialog(parentFrame, prevAxisDefinition); dlg.setHideMid(true); dlg.setVisible(true); if (!dlg.isCanceled()) { float min = Math.min(dlg.getMin(), dlg.getMax()); float max = Math.max(dlg.getMin(), dlg.getMax()); float mid = dlg.getBase(); if (mid < min) mid = min; else if (mid > max) mid = max; DataRange dataRange = new DataRange(min, mid, max); dataRange.setType(dlg.getDataRangeType()); for (Track track : selectedTracks) { track.setDataRange(dataRange); track.setAutoScale(false); } parentFrame.repaint(); } } } }); if (menu != null) menu.add(maxValItem); return maxValItem; } public JMenuItem addSnpTresholdItem(JPopupMenu menu) { JMenuItem maxValItem = new JMenuItem("Set allele frequency threshold..."); maxValItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { String value = JOptionPane.showInputDialog("Allele frequency threshold: ", Float.valueOf(snpThreshold)); if (value == null) { return; } try { float tmp = Float.parseFloat(value); snpThreshold = tmp; IGV.getInstance().revalidateTrackPanels(); } catch (Exception exc) { //log } } }); menu.add(maxValItem); return maxValItem; } public void addShowItems(JPopupMenu menu) { if (alignmentTrack != null) { final JMenuItem alignmentItem = new JCheckBoxMenuItem("Show Alignment Track"); alignmentItem.setSelected(alignmentTrack.isVisible()); alignmentItem.setEnabled(!alignmentTrack.isRemoved()); alignmentItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { alignmentTrack.setVisible(alignmentItem.isSelected()); } }); menu.add(alignmentItem); final SpliceJunctionTrack spliceJunctionTrack = alignmentTrack.getSpliceJunctionTrack(); if (spliceJunctionTrack != null) { final JMenuItem junctionItem = new JCheckBoxMenuItem("Show Splice Junction Track"); junctionItem.setSelected(spliceJunctionTrack.isVisible()); junctionItem.setEnabled(!spliceJunctionTrack.isRemoved()); junctionItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { spliceJunctionTrack.setVisible(junctionItem.isSelected()); } }); menu.add(junctionItem); } final JMenuItem coverageItem = new JMenuItem("Hide Track"); coverageItem.setEnabled(!isRemoved()); coverageItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { UIUtilities.invokeOnEventThread(new Runnable() { public void run() { setVisible(false); if(IGV.hasInstance()) IGV.getInstance().getMainPanel().revalidate(); } }); } }); menu.add(coverageItem); } } public void addLoadCoverageDataItem(JPopupMenu menu) { // Change track height by attribute final JMenuItem item = new JCheckBoxMenuItem("Load pre-computed coverage data..."); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { final IGVPreferences prefs = PreferencesManager.getPreferences(); File initDirectory = prefs.getLastTrackDirectory(); File file = FileDialogUtils.chooseFile("Select coverage file", initDirectory, FileDialog.LOAD); if (file != null) { prefs.setLastTrackDirectory(file.getParentFile()); String path = file.getAbsolutePath(); if (path.endsWith(".tdf") || path.endsWith(".tdf")) { TDFReader reader = TDFReader.getReader(file.getAbsolutePath()); TDFDataSource ds = new TDFDataSource(reader, 0, getName() + " coverage", genome); setDataSource(ds); IGV.getInstance().revalidateTrackPanels(); } else if (path.endsWith(".counts")) { CoverageDataSource ds = new GobyCountArchiveDataSource(file); setDataSource(ds); IGV.getInstance().revalidateTrackPanels(); } else { MessageUtils.showMessage("Coverage data must be in .tdf format"); } } } }); item.setEnabled(dataSource == null); menu.add(item); } @SubtlyImportant private static CoverageTrack getNextTrack() { return (CoverageTrack) IGVSessionReader.getNextTrack(); } public void setGlobalAutoScale(boolean globalAutoScale) { this.globalAutoScale = globalAutoScale; } }