/*
* 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;
}
}