diff --git a/src/main/java/org/broad/igv/feature/AbstractFeature.java b/src/main/java/org/broad/igv/feature/AbstractFeature.java index 9a61227ef6..6d4d95c1f3 100644 --- a/src/main/java/org/broad/igv/feature/AbstractFeature.java +++ b/src/main/java/org/broad/igv/feature/AbstractFeature.java @@ -86,6 +86,11 @@ public String getName() { return name; } + public String getDisplayName(String property) { + String nm = getAttribute(property); + return nm == null ? getName() : nm; + } + public boolean hasExons() { return false; } diff --git a/src/main/java/org/broad/igv/feature/NamedFeature.java b/src/main/java/org/broad/igv/feature/NamedFeature.java index 33e46b8d8a..17976f3f5e 100644 --- a/src/main/java/org/broad/igv/feature/NamedFeature.java +++ b/src/main/java/org/broad/igv/feature/NamedFeature.java @@ -34,4 +34,8 @@ public interface NamedFeature extends Feature { String getName(); + + default String getDisplayName(String property) { + return getName(); + } } diff --git a/src/main/java/org/broad/igv/feature/genome/load/DotGenomeLoader.java b/src/main/java/org/broad/igv/feature/genome/load/DotGenomeLoader.java index debd3b9bcc..289c6882c7 100644 --- a/src/main/java/org/broad/igv/feature/genome/load/DotGenomeLoader.java +++ b/src/main/java/org/broad/igv/feature/genome/load/DotGenomeLoader.java @@ -5,7 +5,6 @@ import org.broad.igv.feature.*; import org.broad.igv.feature.genome.Genome; import org.broad.igv.feature.genome.Sequence; -import org.broad.igv.feature.genome.SequenceWrapper; import org.broad.igv.feature.genome.fasta.FastaBlockCompressedSequence; import org.broad.igv.feature.genome.fasta.FastaIndexedSequence; import org.broad.igv.feature.gff.GFFFeatureSource; @@ -70,7 +69,7 @@ private static FeatureTrack createGeneTrack(Genome genome, BufferedReader reader if (props != null) { geneFeatureTrack.setProperties(parser.getTrackProperties()); } - geneFeatureTrack.setUrl(annotationURL); + geneFeatureTrack.setFeatureInfoURL(annotationURL); } } return geneFeatureTrack; diff --git a/src/main/java/org/broad/igv/feature/tribble/EncodePeakCodec.java b/src/main/java/org/broad/igv/feature/tribble/EncodePeakCodec.java index c6e56fca97..b37efb38ac 100644 --- a/src/main/java/org/broad/igv/feature/tribble/EncodePeakCodec.java +++ b/src/main/java/org/broad/igv/feature/tribble/EncodePeakCodec.java @@ -110,7 +110,7 @@ public BasicFeature decode(String nextLine) { strand = Strand.NONE; } feature.setStrand(strand); - + // Store the remaining features in description string */ Map attributes = new LinkedHashMap<>(); if (tokens.length > 6) attributes.put("signalValue", tokens[6]); diff --git a/src/main/java/org/broad/igv/prefs/Constants.java b/src/main/java/org/broad/igv/prefs/Constants.java index 846745d31a..0b0ac3f23e 100644 --- a/src/main/java/org/broad/igv/prefs/Constants.java +++ b/src/main/java/org/broad/igv/prefs/Constants.java @@ -68,6 +68,8 @@ private Constants() { // Generic track options public static final String BYPASS_FILE_AUTO_DISCOVERY = "BYPASS_FILE_AUTO_DISCOVERY"; public static final String TRACK_ATTRIBUTE_NAME_KEY = "TRACK_ATTRIBUTE_NAME_KEY"; + + public static final String FEATURE_NAME_PROPERTY = "FEATURE_NAME_PROPERTY"; public static final String INITIAL_TRACK_HEIGHT = "15"; public static final String COLOR_SCALE_KEY = "COLOR_SCALE_"; public static final String TRACK_HEIGHT_KEY = "IGV.track.height"; diff --git a/src/main/java/org/broad/igv/renderer/IGVFeatureRenderer.java b/src/main/java/org/broad/igv/renderer/IGVFeatureRenderer.java index af3f760636..720ce439cc 100644 --- a/src/main/java/org/broad/igv/renderer/IGVFeatureRenderer.java +++ b/src/main/java/org/broad/igv/renderer/IGVFeatureRenderer.java @@ -140,7 +140,7 @@ public void render(List featureList, boolean alternateExonColor = (track instanceof FeatureTrack && ((FeatureTrack) track).isAlternateExonColor()); Color trackPosColor = track.getColor(); Color trackNegColor = alternateExonColor ? track.getColor() : track.getAltColor(); - + String featureNameProperty = track.getLabelField(); for (IGVFeature feature : featureList) { @@ -204,7 +204,7 @@ public void render(List featureList, hasExons = bf.hasExons(); } - // Add directional arrows and exons, if there is room. + // Add directional arrows and exons, if there is room. int pixelYCenter = trackRectangle.y + NORMAL_STRAND_Y_OFFSET / 2; if (hasExons) { @@ -234,7 +234,7 @@ public void render(List featureList, Color peakColor = c == Color.cyan ? Color.red : Color.cyan; g2D.setColor(peakColor); int pw = Math.min(4, pixelWidth / 5); - g2D.fillRect(peakPixelPosition - pw/2, pixelYCenter - thinBlockHeight/2 - 1, pw, thinBlockHeight + 2); + g2D.fillRect(peakPixelPosition - pw / 2, pixelYCenter - thinBlockHeight / 2 - 1, pw, thinBlockHeight + 2); g2D.setColor(c); } } catch (NumberFormatException e) { @@ -246,7 +246,10 @@ public void render(List featureList, // Draw name , if there is room if (displayMode != Track.DisplayMode.SQUISHED && track.isShowFeatureNames()) { - String name = feature.getName(); + + String name = featureNameProperty != null ? + feature.getDisplayName(featureNameProperty) : feature.getName(); + if (name != null) { // Limit name display length if (name.length() >= MAX_NAME_LENGTH) { @@ -270,7 +273,7 @@ public void render(List featureList, int verticalSpaceRequiredForText = textBaselineY - (int) trackRectangleY; if (verticalSpaceRequiredForText <= trackRectangle.height) { - lastNamePixelEnd = drawFeatureName(feature, track.getDisplayMode(), nameStart, nameEnd, + lastNamePixelEnd = drawFeatureName(name, track.getDisplayMode(), nameStart, nameEnd, lastNamePixelEnd, fontGraphics, textBaselineY); } } @@ -544,7 +547,7 @@ protected void drawStrandArrows(Strand strand, // Don't draw strand arrows for very small regions // Limit drawing to visible region, we don't really know the viewport pEnd, - if ((endX - startX) < 6) { + if ((endX - startX) < 6) { return; } @@ -579,7 +582,7 @@ protected void drawStrandArrows(Strand strand, } - final private int drawFeatureName(IGVFeature feature, + final private int drawFeatureName(String name, Track.DisplayMode mode, int pixelStart, int pixelEnd, @@ -587,11 +590,6 @@ final private int drawFeatureName(IGVFeature feature, Graphics2D g2D, int textBaselineY) { - String name = feature.getName(); - if (name == null) { - return lastFeatureEndedAtPixelX; - } - FontMetrics fm = g2D.getFontMetrics(); int fontSize = fm.getFont().getSize(); int nameWidth = fm.stringWidth(name); @@ -623,7 +621,7 @@ final private int drawFeatureName(IGVFeature feature, * @param locationScale * @param yOffset * @param trackRectangle - * @param idx exon index + * @param idx exon index */ public void labelAminoAcids(int pStart, Graphics2D fontGraphics, double theOrigin, RenderContext context, IGVFeature gene, double locationScale, @@ -632,8 +630,8 @@ public void labelAminoAcids(int pStart, Graphics2D fontGraphics, double theOrigi Genome genome = GenomeManager.getInstance().getCurrentGenome(); Exon exon = gene.getExons().get(idx); - Exon prevExon = idx == 0 ? null : gene.getExons().get(idx-1); - Exon nextExon = (idx+1) < gene.getExons().size() ?gene.getExons().get(idx+1) : null; + Exon prevExon = idx == 0 ? null : gene.getExons().get(idx - 1); + Exon nextExon = (idx + 1) < gene.getExons().size() ? gene.getExons().get(idx + 1) : null; AminoAcidSequence aaSequence = exon.getAminoAcidSequence(genome, prevExon, nextExon); @@ -641,7 +639,7 @@ public void labelAminoAcids(int pStart, Graphics2D fontGraphics, double theOrigi Rectangle aaRect = new Rectangle(pStart, yOffset - blockHeight / 2, 1, blockHeight); int aaSeqStartPosition = aaSequence.getStart(); - boolean odd = exon.getAminoAcidNumber(exon.getCdStart()) % 2 == 1; + boolean odd = exon.getAminoAcidNumber(exon.getCdStart()) % 2 == 1; for (CodonAA acid : aaSequence.getSequence()) { if (acid != null) { @@ -704,20 +702,20 @@ public String getDisplayName() { protected Color getFeatureColor(IGVFeature feature, Track track, Color defaultPosColor, Color defaultNegColor) { // Set color used to draw the feature - Color color = null; + Color color = null; // If an alt color is explicitly set use it for negative strand features; - if(feature.getStrand() == Strand.NEGATIVE) { + if (feature.getStrand() == Strand.NEGATIVE) { color = track.getExplicitAltColor(); } // If color is explicitly set use it - if(color == null) { - color = track.getExplicitColor(); + if (color == null) { + color = track.getExplicitColor(); } // No explicitly set color, try the feature itself - if(color == null && track.isItemRGB()) { + if (color == null && track.isItemRGB()) { color = feature.getColor(); } diff --git a/src/main/java/org/broad/igv/track/AbstractTrack.java b/src/main/java/org/broad/igv/track/AbstractTrack.java index e62ee8f304..8a41d94699 100644 --- a/src/main/java/org/broad/igv/track/AbstractTrack.java +++ b/src/main/java/org/broad/igv/track/AbstractTrack.java @@ -75,7 +75,7 @@ public abstract class AbstractTrack implements Track { private String attributeKey; private String name; - private String url; + private String featureInfoURL; private boolean itemRGB = true; private boolean useScore; @@ -150,12 +150,12 @@ public void setRendererClass(Class rc) { // Ignore by default } - public String getUrl() { - return url; + public String getFeatureInfoURL() { + return featureInfoURL; } - public void setUrl(String url) { - this.url = url; + public void setFeatureInfoURL(String featureInfoURL) { + this.featureInfoURL = featureInfoURL; } public void setUseScore(boolean useScore) { @@ -193,11 +193,6 @@ public void setSampleId(String sampleId) { this.sampleId = sampleId; } - @Override - public boolean isFilterable() { - return true; // True by default - } - public void renderName(Graphics2D g2D, Rectangle trackRectangle, Rectangle visibleRectangle) { Rectangle rect = getDisplayableRect(trackRectangle, visibleRectangle); @@ -503,10 +498,6 @@ public int getHeight() { return (height < 0) ? getDefaultHeight() : height; } - public boolean hasDataRange() { - return dataRange != null; - } - public DataRange getDataRange() { if (dataRange == null) { // Use the color scale if there is one @@ -679,7 +670,7 @@ public void setProperties(TrackProperties properties) { setWindowFunction(properties.getWindowingFunction()); } if (properties.getUrl() != null) { - setUrl(properties.getUrl()); + setFeatureInfoURL(properties.getUrl()); } Map attributes = properties.getAttributes(); diff --git a/src/main/java/org/broad/igv/track/FeatureTrack.java b/src/main/java/org/broad/igv/track/FeatureTrack.java index b2ad8b9dea..2988452b70 100644 --- a/src/main/java/org/broad/igv/track/FeatureTrack.java +++ b/src/main/java/org/broad/igv/track/FeatureTrack.java @@ -59,8 +59,6 @@ import java.util.List; import java.util.stream.Collectors; -import static org.broad.igv.feature.FeatureUtils.FEATURE_CENTER_COMPARATOR; - /** * Track which displays features, typically showing regions of the genome * in a qualitative way. Features are rendered using the specified FeatureRenderer. @@ -119,6 +117,7 @@ public class FeatureTrack extends AbstractTrack implements IGVEventObserver { private String trackLine = null; private boolean groupByStrand = false; + private String labelField; public FeatureTrack() { @@ -445,7 +444,7 @@ public String getValueStringAt(String chr, double position, int mouseX, int mous private String getFeatureURL(IGVFeature igvFeature) { String url = igvFeature.getURL(); if (url == null) { - String trackURL = getUrl(); + String trackURL = getFeatureInfoURL(); if (trackURL != null && igvFeature.getName() != null) { String encodedID = StringUtils.encodeURL(igvFeature.getName()); url = trackURL.replaceAll("\\$\\$", encodedID); @@ -454,6 +453,14 @@ private String getFeatureURL(IGVFeature igvFeature) { return url; } + @Override + public String getLabelField() { + return labelField; + } + + public void setLabelField(String labelField) { + this.labelField = labelField; + } /** * Get all features which overlap the specified locus @@ -1036,7 +1043,9 @@ public boolean isGroupByStrand() { @Override public void marshalXML(Document document, Element element) { element.setAttribute("groupByStrand", String.valueOf(groupByStrand)); - + if (labelField != null) { + element.setAttribute("featureNameProperty", labelField); + } super.marshalXML(document, element); } @@ -1046,6 +1055,10 @@ public void unmarshalXML(Element element, Integer version) { super.unmarshalXML(element, version); + if (element.hasAttribute("featureNameProperty")) { + this.labelField = element.getAttribute("featureNameProperty"); + } + this.groupByStrand = "true".equals(element.getAttribute("groupByStrand")); NodeList tmp = element.getElementsByTagName("SequenceMatchSource"); diff --git a/src/main/java/org/broad/igv/track/MutationTrack.java b/src/main/java/org/broad/igv/track/MutationTrack.java index 8aeae84452..7c1d971e42 100644 --- a/src/main/java/org/broad/igv/track/MutationTrack.java +++ b/src/main/java/org/broad/igv/track/MutationTrack.java @@ -62,11 +62,6 @@ public MutationTrack(ResourceLocator locator, String id, FeatureSource source) { public MutationTrack() { } - @Override - public boolean isFilterable() { - return true; // Mutation tracks, unlike most FeatureTrack types, can be filtered - } - @Override public void overlay(RenderContext context, Rectangle rect) { if (!context.getChr().equals(Globals.CHR_ALL) || diff --git a/src/main/java/org/broad/igv/track/Track.java b/src/main/java/org/broad/igv/track/Track.java index 994ea57117..2732bd2e79 100644 --- a/src/main/java/org/broad/igv/track/Track.java +++ b/src/main/java/org/broad/igv/track/Track.java @@ -84,7 +84,9 @@ enum DisplayMode { * * @return */ - boolean isFilterable(); + default boolean isFilterable() { + return true; + } /** @@ -128,7 +130,7 @@ void renderAttributes(Graphics2D graphics, Rectangle trackRectangle, Rectangle v String getSample(); - void setUrl(String url); + void setFeatureInfoURL(String featureInfoURL); ResourceLocator getResourceLocator(); @@ -182,8 +184,6 @@ default boolean isNumeric() { */ void setDataRange(DataRange axisDefinition); - boolean hasDataRange(); - DataRange getDataRange(); Color getColor(); @@ -276,6 +276,16 @@ default Color getExplicitAltColor() { default void setShowFeatureNames(boolean b) {} + /** + * Return the java property or attribute for the feature display name. Default is "null", in which case the + * feature "name" property will be used. + * + * @return + */ + default String getLabelField() { + return null; + } + default void repaint() { IGV.getInstance().repaint(this); } diff --git a/src/main/java/org/broad/igv/track/TrackLoader.java b/src/main/java/org/broad/igv/track/TrackLoader.java index f5ffd9ac0e..9ada57f3a5 100644 --- a/src/main/java/org/broad/igv/track/TrackLoader.java +++ b/src/main/java/org/broad/igv/track/TrackLoader.java @@ -247,7 +247,10 @@ public List load(ResourceLocator locator, Genome genome) throws DataLoadE for (Track track : newTracks) { if (locator.getFeatureInfoURL() != null) { - track.setUrl(locator.getFeatureInfoURL()); + track.setFeatureInfoURL(locator.getFeatureInfoURL()); + } + if (locator.getLabelField() != null && track instanceof FeatureTrack) { + ((FeatureTrack) track).setLabelField(locator.getLabelField()); } if (tp != null) { track.setProperties(tp); diff --git a/src/main/java/org/broad/igv/track/TrackMenuUtils.java b/src/main/java/org/broad/igv/track/TrackMenuUtils.java index 72a0e0bc7c..01c21e2d34 100644 --- a/src/main/java/org/broad/igv/track/TrackMenuUtils.java +++ b/src/main/java/org/broad/igv/track/TrackMenuUtils.java @@ -25,7 +25,6 @@ package org.broad.igv.track; -import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import htsjdk.tribble.Feature; @@ -61,7 +60,6 @@ import org.broad.igv.util.ResourceLocator; import org.broad.igv.util.StringUtils; import org.broad.igv.util.blat.BlatClient; -import org.broad.igv.util.collections.CollUtils; import org.broad.igv.util.extview.ExtendViewClient; import javax.swing.*; @@ -421,7 +419,6 @@ private static void addFeatureItems(JPopupMenu featurePopupMenu, final Collectio addDisplayModeItems(tracks, featurePopupMenu); - if (tracks.size() == 1) { Track t = tracks.iterator().next(); Feature f = t.getFeatureAtMousePosition(te); @@ -475,7 +472,7 @@ private static void addFeatureItems(JPopupMenu featurePopupMenu, final Collectio featurePopupMenu.addSeparator(); featurePopupMenu.add(getShowFeatureNames(tracks)); - + featurePopupMenu.add(getFeatureNameAttribute(tracks)); } /** @@ -745,17 +742,7 @@ private static void changeStatType(String statType, Collection selectedTr public static JMenuItem getTrackRenameItem(final Collection selectedTracks) { // Change track height by attribute JMenuItem item = new JMenuItem("Rename Track..."); - item.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent evt) { - UIUtilities.invokeOnEventThread(new Runnable() { - - public void run() { - renameTrack(selectedTracks); - } - }); - } - }); + item.addActionListener(evt -> UIUtilities.invokeOnEventThread(() -> renameTrack(selectedTracks))); if (selectedTracks.size() > 1) { item.setEnabled(false); } @@ -1123,7 +1110,6 @@ public static void changeFontSize(final Collection selectedTracks) { IGV.getInstance().repaint(selectedTracks); } - public static Integer getIntegerInput(String parameter, int value) { while (true) { @@ -1425,6 +1411,27 @@ public static JMenuItem getShowFeatureNames(final Collection selectedTrac return item; } + public static JMenuItem getFeatureNameAttribute(final Collection selectedTracks) { + + JMenuItem item = new JMenuItem("Set Feature Name Property..."); + item.addActionListener(evt -> { + String currentVal = selectedTracks.iterator().next().getLabelField(); + if (currentVal == null) currentVal = ""; + final String newVal = JOptionPane.showInputDialog(IGV.getInstance().getMainFrame(), "Feature Name Property: ", currentVal); + if (newVal == null) { + return; // Dialog canceled + } + selectedTracks.stream().forEach(t -> { + if (t instanceof FeatureTrack) { + ((FeatureTrack) t).setLabelField(newVal); + } + }); + IGV.getInstance().repaint(selectedTracks); + }); + + return item; + } + public static JMenuItem getChangeFontSizeItem(final Collection selectedTracks) { // Change track height by attribute JMenuItem item = new JMenuItem("Change Font Size..."); diff --git a/src/main/java/org/broad/igv/ui/ResourceTree.java b/src/main/java/org/broad/igv/ui/ResourceTree.java index 17538982a7..f123d0fe5d 100644 --- a/src/main/java/org/broad/igv/ui/ResourceTree.java +++ b/src/main/java/org/broad/igv/ui/ResourceTree.java @@ -242,6 +242,7 @@ public static void buildLocatorTree(DefaultMutableTreeNode treeNode, Element xml locator.setIndexPath(getAttribute(xmlNode, INDEX.getText())); locator.setSampleId(sampleId); locator.setFeatureInfoURL(getAttribute(xmlNode, URL.getText())); + locator.setLabelField(getAttribute(xmlNode, LABEL_FIELD.getText())); locator.setDescription(getAttribute(xmlNode, DESCRIPTION.getText())); locator.setTrackLine(getAttribute(xmlNode, TRACK_LINE.getText())); locator.setName(name); diff --git a/src/main/java/org/broad/igv/util/ResourceLocator.java b/src/main/java/org/broad/igv/util/ResourceLocator.java index d6eb756b08..26d79165ab 100644 --- a/src/main/java/org/broad/igv/util/ResourceLocator.java +++ b/src/main/java/org/broad/igv/util/ResourceLocator.java @@ -93,6 +93,8 @@ public class ResourceLocator { */ String featureInfoURL; + String labelField; + /** * Descriptive text */ @@ -428,6 +430,14 @@ public String getFeatureInfoURL() { return featureInfoURL; } + public String getLabelField() { + return labelField; + } + + public void setLabelField(String labelField) { + this.labelField = labelField; + } + public void setFeatureInfoURL(String featureInfoURL) { this.featureInfoURL = featureInfoURL; } @@ -557,14 +567,6 @@ public static String indexFile(ResourceLocator locator) { } } - public void setAttribute(String key, Object value) { - this.attributes.put(key, value); - } - - public Object getAttribute(String key) { - return attributes.get(key); - } - public void setIndexed(boolean indexed) { this.indexed = indexed; } @@ -612,6 +614,7 @@ public enum AttributeType { SAMPLE_ID("sampleId"), NAME("name"), URL("url"), + LABEL_FIELD("labelField"), RESOURCE_TYPE("resourceType"), TRACK_LINE("trackLine"), COVERAGE("coverage"),