From: gmungoc
Date: Thu, 16 Apr 2020 15:11:23 +0000 (+0100)
Subject: JAL-3567 report mapped location for virtual features in tooltip etc
X-Git-Tag: Develop-2_11_2_0-d20201215~33^2~5
X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=5a631296dd1dcc1df7b50487a647c27333696c74;p=jalview.git
JAL-3567 report mapped location for virtual features in tooltip etc
---
diff --git a/src/jalview/appletgui/APopupMenu.java b/src/jalview/appletgui/APopupMenu.java
index 76f2705..6299c62 100644
--- a/src/jalview/appletgui/APopupMenu.java
+++ b/src/jalview/appletgui/APopupMenu.java
@@ -20,6 +20,25 @@
*/
package jalview.appletgui;
+import java.awt.CheckboxMenuItem;
+import java.awt.Frame;
+import java.awt.Menu;
+import java.awt.MenuItem;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.Vector;
+
import jalview.analysis.AAFrequency;
import jalview.analysis.AlignmentAnnotationUtils;
import jalview.analysis.AlignmentUtils;
@@ -57,25 +76,6 @@ import jalview.schemes.ZappoColourScheme;
import jalview.util.MessageManager;
import jalview.util.UrlLink;
-import java.awt.CheckboxMenuItem;
-import java.awt.Frame;
-import java.awt.Menu;
-import java.awt.MenuItem;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.ItemEvent;
-import java.awt.event.ItemListener;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.Vector;
-
public class APopupMenu extends java.awt.PopupMenu
implements ActionListener, ItemListener
{
@@ -900,7 +900,7 @@ public class APopupMenu extends java.awt.PopupMenu
contents.append(MessageManager
.formatMessage("label.annotation_for_displayid", new Object[]
{ seq.getDisplayId(true) }));
- new SequenceAnnotationReport(null).createSequenceAnnotationReport(
+ new SequenceAnnotationReport(false).createSequenceAnnotationReport(
contents, seq, true, true, ap.seqPanel.seqCanvas.fr);
contents.append("
");
}
diff --git a/src/jalview/datamodel/MappedFeatures.java b/src/jalview/datamodel/MappedFeatures.java
index 0fa03cf..a42f34a 100644
--- a/src/jalview/datamodel/MappedFeatures.java
+++ b/src/jalview/datamodel/MappedFeatures.java
@@ -1,14 +1,15 @@
package jalview.datamodel;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
import jalview.io.gff.Gff3Helper;
import jalview.schemes.ResidueProperties;
+import jalview.util.MapList;
import jalview.util.MappingUtils;
import jalview.util.StringUtils;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
/**
* A data bean to hold a list of mapped sequence features (e.g. CDS features
* mapped from protein), and the mapping between the sequences. It also provides
@@ -18,22 +19,31 @@ import java.util.Set;
*/
public class MappedFeatures
{
+ /*
+ * VEP CSQ:HGVSp (if present) is a short-cut to the protein variant consequence
+ */
private static final String HGV_SP = "HGVSp";
private static final String CSQ = "CSQ";
/*
- * the mapping from one sequence to another
+ * the sequence the mapped features are on
*/
- public final Mapping mapping;
+ private final SequenceI linkedSeq;
- /**
- * the sequence mapped from
+ /*
+ * the mapping between sequences;
+ * NB this could be in either sense
*/
- public final SequenceI fromSeq;
+ private final Mapping mapping;
/*
- * features on the sequence mapped to that overlap the mapped positions
+ * if true, mapping is from the linked sequence, else to the linked sequence
+ */
+ private boolean mappingIsFromLinkedSequence;
+
+ /*
+ * features on linkedSeq that overlap the mapped positions
*/
public final List features;
@@ -74,7 +84,8 @@ public class MappedFeatures
char res, List theFeatures)
{
mapping = theMapping;
- fromSeq = from;
+ linkedSeq = from;
+ mappingIsFromLinkedSequence = mapping.to != linkedSeq;
toPosition = pos;
toResidue = res;
features = theFeatures;
@@ -90,13 +101,13 @@ public class MappedFeatures
{
codonPos = codonPositions;
baseCodon = new char[3];
- int cdsStart = fromSeq.getStart();
+ int cdsStart = linkedSeq.getStart();
baseCodon[0] = Character
- .toUpperCase(fromSeq.getCharAt(codonPos[0] - cdsStart));
+ .toUpperCase(linkedSeq.getCharAt(codonPos[0] - cdsStart));
baseCodon[1] = Character
- .toUpperCase(fromSeq.getCharAt(codonPos[1] - cdsStart));
+ .toUpperCase(linkedSeq.getCharAt(codonPos[1] - cdsStart));
baseCodon[2] = Character
- .toUpperCase(fromSeq.getCharAt(codonPos[2] - cdsStart));
+ .toUpperCase(linkedSeq.getCharAt(codonPos[2] - cdsStart));
}
else
{
@@ -233,4 +244,47 @@ public class MappedFeatures
return vars.toString();
}
+
+ /**
+ * Answers the name of the linked sequence holding any mapped features
+ *
+ * @return
+ */
+ public String getLinkedSequenceName()
+ {
+ return linkedSeq == null ? null : linkedSeq.getName();
+ }
+
+ /**
+ * Answers the mapped ranges (as one or more [start, end] positions) which
+ * correspond to the given [begin, end] range of the linked sequence.
+ *
+ *
+ * Example: MappedFeatures with CDS features mapped to peptide
+ * CDS/200-220 gtc aac TGa acGt att AAC tta
+ * mapped to PEP/6-7 WN by mapping [206, 207, 210, 210, 215, 217] to [6, 7]
+ * getMappedPositions(206, 206) should return [6, 6]
+ * getMappedPositions(200, 214) should return [6, 6]
+ * getMappedPositions(210, 215) should return [6, 7]
+ *
+ *
+ * @param begin
+ * @param end
+ * @return
+ */
+ public int[] getMappedPositions(int begin, int end)
+ {
+ MapList map = mapping.getMap();
+ return mappingIsFromLinkedSequence ? map.locateInTo(begin, end)
+ : map.locateInFrom(begin, end);
+ }
+
+ public boolean isFromCds()
+ {
+ if (mapping.getMap().getFromRatio() == 3)
+ {
+ return mappingIsFromLinkedSequence;
+ }
+ return !mappingIsFromLinkedSequence;
+ }
}
diff --git a/src/jalview/datamodel/SequenceFeature.java b/src/jalview/datamodel/SequenceFeature.java
index 2dd9cf0..df268f8 100755
--- a/src/jalview/datamodel/SequenceFeature.java
+++ b/src/jalview/datamodel/SequenceFeature.java
@@ -20,13 +20,6 @@
*/
package jalview.datamodel;
-import jalview.datamodel.features.FeatureAttributeType;
-import jalview.datamodel.features.FeatureAttributes;
-import jalview.datamodel.features.FeatureLocationI;
-import jalview.datamodel.features.FeatureSourceI;
-import jalview.datamodel.features.FeatureSources;
-import jalview.util.StringUtils;
-
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -35,6 +28,13 @@ import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;
+import jalview.datamodel.features.FeatureAttributeType;
+import jalview.datamodel.features.FeatureAttributes;
+import jalview.datamodel.features.FeatureLocationI;
+import jalview.datamodel.features.FeatureSourceI;
+import jalview.datamodel.features.FeatureSources;
+import jalview.util.StringUtils;
+
/**
* A class that models a single contiguous feature on a sequence. If flag
* 'contactFeature' is true, the start and end positions are interpreted instead
@@ -586,13 +586,17 @@ public class SequenceFeature implements FeatureLocationI
}
/**
- * Answers an html-formatted report of feature details
+ * Answers an html-formatted report of feature details. If parameter
+ * {@code mf} is not null, the feature is a virtual linked feature, and
+ * details included both the original location and the mapped location
+ * (CDS/peptide).
*
* @param seqName
+ * @param mf
*
* @return
*/
- public String getDetailsReport(String seqName)
+ public String getDetailsReport(String seqName, MappedFeatures mf)
{
FeatureSourceI metadata = FeatureSources.getInstance()
.getSource(source);
@@ -600,9 +604,25 @@ public class SequenceFeature implements FeatureLocationI
StringBuilder sb = new StringBuilder(128);
sb.append("
");
sb.append("");
- sb.append(String.format(ROW_DATA, "Location", seqName,
+ String name = mf == null ? seqName : mf.getLinkedSequenceName();
+ sb.append(String.format(ROW_DATA, "Location", name,
begin == end ? begin
: begin + (isContactFeature() ? ":" : "-") + end));
+ if (mf != null)
+ {
+ int[] beginRange = mf.getMappedPositions(begin, begin);
+ int[] endRange = mf.getMappedPositions(end, end);
+ int from = beginRange[0];
+ int to = endRange[endRange.length - 1];
+ String s = mf.isFromCds() ? "Peptide Location" : "Coding location";
+ sb.append(String.format(ROW_DATA, s, seqName, from == to ? from
+ : from + (isContactFeature() ? ":" : "-") + to));
+ if (mf.isFromCds())
+ {
+ sb.append(String.format(ROW_DATA, "Consequence",
+ mf.findProteinVariants(this), ""));
+ }
+ }
sb.append(String.format(ROW_DATA, "Type", type, ""));
String desc = StringUtils.stripHtmlTags(description);
sb.append(String.format(ROW_DATA, "Description", desc, ""));
diff --git a/src/jalview/gui/IdPanel.java b/src/jalview/gui/IdPanel.java
index 2b1507a..3ce9d4d 100755
--- a/src/jalview/gui/IdPanel.java
+++ b/src/jalview/gui/IdPanel.java
@@ -20,17 +20,6 @@
*/
package jalview.gui;
-import jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.Sequence;
-import jalview.datamodel.SequenceGroup;
-import jalview.datamodel.SequenceI;
-import jalview.gui.SeqPanel.MousePos;
-import jalview.io.SequenceAnnotationReport;
-import jalview.util.MessageManager;
-import jalview.util.Platform;
-import jalview.viewmodel.AlignmentViewport;
-import jalview.viewmodel.ViewportRanges;
-
import java.awt.BorderLayout;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
@@ -44,6 +33,17 @@ import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.gui.SeqPanel.MousePos;
+import jalview.io.SequenceAnnotationReport;
+import jalview.util.MessageManager;
+import jalview.util.Platform;
+import jalview.viewmodel.AlignmentViewport;
+import jalview.viewmodel.ViewportRanges;
+
/**
* This panel hosts alignment sequence ids and responds to mouse clicks on them,
* as well as highlighting ids matched by a search from the Find menu.
@@ -62,8 +62,6 @@ public class IdPanel extends JPanel
ScrollThread scrollThread = null;
- String linkImageURL;
-
int offy;
// int width;
@@ -84,8 +82,7 @@ public class IdPanel extends JPanel
this.av = av;
alignPanel = parent;
setIdCanvas(new IdCanvas(av));
- linkImageURL = getClass().getResource("/images/link.gif").toString();
- seqAnnotReport = new SequenceAnnotationReport(linkImageURL);
+ seqAnnotReport = new SequenceAnnotationReport(true);
setLayout(new BorderLayout());
add(getIdCanvas(), BorderLayout.CENTER);
addMouseListener(this);
diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java
index 187090b..568f7f1 100644
--- a/src/jalview/gui/PopupMenu.java
+++ b/src/jalview/gui/PopupMenu.java
@@ -20,6 +20,31 @@
*/
package jalview.gui;
+import java.awt.Color;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.Vector;
+
+import javax.swing.ButtonGroup;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JColorChooser;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+import javax.swing.JRadioButtonMenuItem;
+
import jalview.analysis.AAFrequency;
import jalview.analysis.AlignmentAnnotationUtils;
import jalview.analysis.AlignmentUtils;
@@ -56,31 +81,6 @@ import jalview.util.StringUtils;
import jalview.util.UrlLink;
import jalview.viewmodel.seqfeatures.FeatureRendererModel;
-import java.awt.Color;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.BitSet;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Hashtable;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.Vector;
-
-import javax.swing.ButtonGroup;
-import javax.swing.JCheckBoxMenuItem;
-import javax.swing.JColorChooser;
-import javax.swing.JMenu;
-import javax.swing.JMenuItem;
-import javax.swing.JPopupMenu;
-import javax.swing.JRadioButtonMenuItem;
-
/**
* The popup menu that is displayed on right-click on a sequence id, or in the
* sequence alignment.
@@ -755,14 +755,16 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
}
/**
- * Add a link to show feature details for each sequence feature
+ * Add a menu item to show feature details for each sequence feature. Any
+ * linked 'virtual' features (CDS/protein) are also optionally found and
+ * included.
*
* @param features
- * @param column
* @param seq
+ * @param column
*/
protected void addFeatureDetails(List features,
- SequenceI seq, int column)
+ final SequenceI seq, final int column)
{
/*
* add features in CDS/protein complement at the corresponding
@@ -797,39 +799,49 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
String name = seq.getName();
for (final SequenceFeature sf : features)
{
- addFeatureDetailsMenuItem(details, name, sf);
+ addFeatureDetailsMenuItem(details, name, sf, null);
}
if (mf != null)
{
- name = mf.fromSeq == seq ? mf.mapping.getTo().getName()
- : mf.fromSeq.getName();
for (final SequenceFeature sf : mf.features)
{
- addFeatureDetailsMenuItem(details, name, sf);
+ addFeatureDetailsMenuItem(details, name, sf, mf);
}
}
}
/**
- * A helper method to add one menu item whose action is to show details for one
- * feature. The menu text includes feature description, but this may be
+ * A helper method to add one menu item whose action is to show details for
+ * one feature. The menu text includes feature description, but this may be
* truncated.
*
* @param details
* @param seqName
* @param sf
+ * @param mf
*/
void addFeatureDetailsMenuItem(JMenu details, final String seqName,
- final SequenceFeature sf)
+ final SequenceFeature sf, MappedFeatures mf)
{
int start = sf.getBegin();
int end = sf.getEnd();
+ if (mf != null)
+ {
+ /*
+ * show local rather than linked feature coordinates
+ */
+ int[] beginRange = mf.getMappedPositions(start, start);
+ start = beginRange[0];
+ int[] endRange = mf.getMappedPositions(end, end);
+ end = endRange[endRange.length - 1];
+ }
StringBuilder desc = new StringBuilder();
desc.append(sf.getType()).append(" ").append(String.valueOf(start));
if (start != end)
{
- desc.append("-").append(String.valueOf(end));
+ desc.append(sf.isContactFeature() ? ":" : "-");
+ desc.append(String.valueOf(end));
}
String description = sf.getDescription();
if (description != null)
@@ -860,26 +872,27 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
@Override
public void actionPerformed(ActionEvent e)
{
- showFeatureDetails(seqName, sf);
+ showFeatureDetails(sf, seqName, mf);
}
});
details.add(item);
}
/**
- * Opens a panel showing a text report of feature dteails
- *
- * @param seqName
+ * Opens a panel showing a text report of feature details
*
* @param sf
+ * @param seqName
+ * @param mf
*/
- protected void showFeatureDetails(String seqName, SequenceFeature sf)
+ protected void showFeatureDetails(SequenceFeature sf, String seqName,
+ MappedFeatures mf)
{
CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
// it appears Java's CSS does not support border-collapse :-(
cap.addStylesheetRule("table { border-collapse: collapse;}");
cap.addStylesheetRule("table, td, th {border: 1px solid black;}");
- cap.setText(sf.getDetailsReport(seqName));
+ cap.setText(sf.getDetailsReport(seqName, mf));
Desktop.addInternalFrame(cap,
MessageManager.getString("label.feature_details"), 500, 500);
@@ -1763,7 +1776,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
"label.create_sequence_details_report_annotation_for",
new Object[]
{ seq.getDisplayId(true) }) + "");
- new SequenceAnnotationReport(null).createSequenceAnnotationReport(
+ new SequenceAnnotationReport(false).createSequenceAnnotationReport(
contents, seq, true, true, ap.getSeqPanel().seqCanvas.fr);
contents.append("
");
}
diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java
index 27ceb27..f28217d 100644
--- a/src/jalview/gui/SeqPanel.java
+++ b/src/jalview/gui/SeqPanel.java
@@ -207,8 +207,6 @@ public class SeqPanel extends JPanel
StringBuffer keyboardNo2;
- java.net.URL linkImageURL;
-
private final SequenceAnnotationReport seqARep;
StringBuilder tooltipText = new StringBuilder();
@@ -229,8 +227,7 @@ public class SeqPanel extends JPanel
*/
public SeqPanel(AlignViewport viewport, AlignmentPanel alignPanel)
{
- linkImageURL = getClass().getResource("/images/link.gif");
- seqARep = new SequenceAnnotationReport(linkImageURL.toString());
+ seqARep = new SequenceAnnotationReport(true);
ToolTipManager.sharedInstance().registerComponent(this);
ToolTipManager.sharedInstance().setInitialDelay(0);
ToolTipManager.sharedInstance().setDismissDelay(10000);
@@ -1047,9 +1044,9 @@ public class SeqPanel extends JPanel
{
List features = ap.getFeatureRenderer()
.findFeaturesAtColumn(sequence, column + 1);
- unshownFeatures = seqARep.appendFeaturesLengthLimit(tooltipText, pos,
- features,
- this.ap.getSeqPanel().seqCanvas.fr, MAX_TOOLTIP_LENGTH);
+ unshownFeatures = seqARep.appendFeatures(tooltipText, pos,
+ features, this.ap.getSeqPanel().seqCanvas.fr,
+ MAX_TOOLTIP_LENGTH);
/*
* add features in CDS/protein complement at the corresponding
@@ -1067,9 +1064,8 @@ public class SeqPanel extends JPanel
pos);
if (mf != null)
{
- unshownFeatures = seqARep.appendFeaturesLengthLimit(
- tooltipText, pos, mf, fr2,
- MAX_TOOLTIP_LENGTH);
+ unshownFeatures = seqARep.appendFeatures(tooltipText,
+ pos, mf, fr2, MAX_TOOLTIP_LENGTH);
}
}
}
diff --git a/src/jalview/io/FeaturesFile.java b/src/jalview/io/FeaturesFile.java
index a8a3746..92473ec 100755
--- a/src/jalview/io/FeaturesFile.java
+++ b/src/jalview/io/FeaturesFile.java
@@ -20,6 +20,18 @@
*/
package jalview.io;
+import java.awt.Color;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
import jalview.analysis.AlignmentUtils;
import jalview.analysis.SequenceIdMatcher;
import jalview.api.AlignViewportI;
@@ -44,18 +56,6 @@ import jalview.util.MapList;
import jalview.util.ParseHtmlBodyAndLinks;
import jalview.util.StringUtils;
-import java.awt.Color;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.TreeMap;
-
/**
* Parses and writes features files, which may be in Jalview, GFF2 or GFF3
* format. These are tab-delimited formats but with differences in the use of
@@ -736,7 +736,6 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI
if (mf != null)
{
- MapList mapping = mf.mapping.getMap();
for (SequenceFeature sf : mf.features)
{
/*
@@ -752,9 +751,7 @@ public class FeaturesFile extends AlignFile implements FeaturesSourceI
found.add(sf);
int begin = sf.getBegin();
int end = sf.getEnd();
- int[] range = mf.mapping.getTo() == seq.getDatasetSequence()
- ? mapping.locateInTo(begin, end)
- : mapping.locateInFrom(begin, end);
+ int[] range = mf.getMappedPositions(begin, end);
SequenceFeature sf2 = new SequenceFeature(sf, range[0],
range[1], group, sf.getScore());
complementary.add(sf2);
diff --git a/src/jalview/io/SequenceAnnotationReport.java b/src/jalview/io/SequenceAnnotationReport.java
index 0125277..8328e7a 100644
--- a/src/jalview/io/SequenceAnnotationReport.java
+++ b/src/jalview/io/SequenceAnnotationReport.java
@@ -20,6 +20,13 @@
*/
package jalview.io;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
import jalview.api.FeatureColourI;
import jalview.datamodel.DBRefEntry;
import jalview.datamodel.DBRefSource;
@@ -32,13 +39,6 @@ import jalview.util.StringUtils;
import jalview.util.UrlLink;
import jalview.viewmodel.seqfeatures.FeatureRendererModel;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
/**
* generate HTML reports for a sequence
*
@@ -56,12 +56,12 @@ public class SequenceAnnotationReport
private static final int MAX_SOURCES = 40;
+ private static String linkImageURL;
+
private static final String[][] PRIMARY_SOURCES = new String[][] {
DBRefSource.CODINGDBS, DBRefSource.DNACODINGDBS,
DBRefSource.PROTEINDBS };
- final String linkImageURL;
-
/*
* Comparator to order DBRefEntry by Source + accession id (case-insensitive),
* with 'Primary' sources placed before others, and 'chromosome' first of all
@@ -120,14 +120,30 @@ public class SequenceAnnotationReport
}
};
- public SequenceAnnotationReport(String linkURL)
+ private boolean forTooltip;
+
+ /**
+ * Constructor given a flag which affects behaviour
+ *
+ * - if true, generates feature details suitable to show in a tooltip
+ * - if false, generates feature details in a form suitable for the sequence
+ * details report
+ *
+ *
+ * @param isForTooltip
+ */
+ public SequenceAnnotationReport(boolean isForTooltip)
{
- this.linkImageURL = linkURL;
+ this.forTooltip = isForTooltip;
+ if (linkImageURL == null)
+ {
+ linkImageURL = getClass().getResource("/images/link.gif").toString();
+ }
}
/**
- * Append text for the list of features to the tooltip Returns number of
- * features left if maxlength limit is (or would have been) reached
+ * Append text for the list of features to the tooltip. Returns the number of
+ * features not added if maxlength limit is (or would have been) reached.
*
* @param sb
* @param residuePos
@@ -135,7 +151,7 @@ public class SequenceAnnotationReport
* @param minmax
* @param maxlength
*/
- public int appendFeaturesLengthLimit(final StringBuilder sb,
+ public int appendFeatures(final StringBuilder sb,
int residuePos, List features,
FeatureRendererModel fr, int maxlength)
{
@@ -150,16 +166,10 @@ public class SequenceAnnotationReport
return 0;
}
- public void appendFeatures(final StringBuilder sb, int residuePos,
- List features, FeatureRendererModel fr)
- {
- appendFeaturesLengthLimit(sb, residuePos, features, fr, 0);
- }
-
/**
- * Appends text for mapped features (e.g. CDS feature for peptide or vice versa)
- * Returns number of features left if maxlength limit is (or would have been)
- * reached
+ * Appends text for mapped features (e.g. CDS feature for peptide or vice
+ * versa) Returns number of features left if maxlength limit is (or would have
+ * been) reached.
*
* @param sb
* @param residuePos
@@ -167,7 +177,7 @@ public class SequenceAnnotationReport
* @param fr
* @param maxlength
*/
- public int appendFeaturesLengthLimit(StringBuilder sb, int residuePos,
+ public int appendFeatures(StringBuilder sb, int residuePos,
MappedFeatures mf, FeatureRendererModel fr, int maxlength)
{
for (int i = 0; i < mf.features.size(); i++)
@@ -181,12 +191,6 @@ public class SequenceAnnotationReport
return 0;
}
- public void appendFeatures(StringBuilder sb, int residuePos,
- MappedFeatures mf, FeatureRendererModel fr)
- {
- appendFeaturesLengthLimit(sb, residuePos, mf, fr, 0);
- }
-
/**
* Appends the feature at rpos to the given buffer
*
@@ -199,19 +203,44 @@ public class SequenceAnnotationReport
FeatureRendererModel fr, SequenceFeature feature,
MappedFeatures mf, int maxlength)
{
+ int begin = feature.getBegin();
+ int end = feature.getEnd();
+
+ /*
+ * if this is a virtual features, convert begin/end to the
+ * coordinates of the sequence it is mapped to
+ */
+ int[] beginRange = null;
+ int[] endRange = null;
+ if (mf != null)
+ {
+ beginRange = mf.getMappedPositions(begin, begin);
+ endRange = mf.getMappedPositions(end, end);
+ begin = beginRange[0];
+ end = endRange[endRange.length - 1];
+ }
+
StringBuilder sb = new StringBuilder();
if (feature.isContactFeature())
{
- if (feature.getBegin() == rpos || feature.getEnd() == rpos)
+ /*
+ * include if rpos is at start or end position of [mapped] feature
+ */
+ boolean showContact = (mf == null) && (rpos == begin || rpos == end);
+ boolean showMappedContact = (mf != null) && ((rpos >= beginRange[0]
+ && rpos <= beginRange[beginRange.length - 1])
+ || (rpos >= endRange[0]
+ && rpos <= endRange[endRange.length - 1]));
+ if (showContact || showMappedContact)
{
if (sb0.length() > 6)
{
sb.append("
");
}
- sb.append(feature.getType()).append(" ").append(feature.getBegin())
- .append(":").append(feature.getEnd());
+ sb.append(feature.getType()).append(" ").append(begin).append(":")
+ .append(end);
}
- return appendTextMaxLengthReached(sb0, sb, maxlength);
+ return appendText(sb0, sb, maxlength);
}
if (sb0.length() > 6)
@@ -226,11 +255,11 @@ public class SequenceAnnotationReport
if (rpos != 0)
{
// we are marking a positional feature
- sb.append(feature.begin);
- }
- if (feature.begin != feature.end)
- {
- sb.append(" ").append(feature.end);
+ sb.append(begin);
+ if (begin != end)
+ {
+ sb.append(" ").append(end);
+ }
}
String description = feature.getDescription();
@@ -291,27 +320,28 @@ public class SequenceAnnotationReport
}
}
}
- return appendTextMaxLengthReached(sb0, sb, maxlength);
+ return appendText(sb0, sb, maxlength);
}
- void appendFeature(final StringBuilder sb, int rpos,
- FeatureRendererModel fr, SequenceFeature feature,
- MappedFeatures mf)
- {
- appendFeature(sb, rpos, fr, feature, mf, 0);
- }
-
- private static boolean appendTextMaxLengthReached(StringBuilder sb0,
- StringBuilder sb, int maxlength)
+ /**
+ * Appends sb to sb0, and returns false, unless maxlength is not zero and
+ * appending would make the result longer than or equal to maxlength, in which
+ * case the append is not done and returns true
+ *
+ * @param sb0
+ * @param sb
+ * @param maxlength
+ * @return
+ */
+ private static boolean appendText(StringBuilder sb0, StringBuilder sb,
+ int maxlength)
{
- boolean ret = false;
if (maxlength == 0 || sb0.length() + sb.length() < maxlength)
{
sb0.append(sb);
return false;
- } else {
- return true;
}
+ return true;
}
/**
@@ -466,7 +496,7 @@ public class SequenceAnnotationReport
.getNonPositionalFeatures())
{
int sz = -sb.length();
- appendFeature(sb, 0, fr, sf, null);
+ appendFeature(sb, 0, fr, sf, null, 0);
sz += sb.length();
maxWidth = Math.max(maxWidth, sz);
}
diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
index 9a8a086..0a667aa 100644
--- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
+++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
@@ -20,6 +20,20 @@
*/
package jalview.viewmodel.seqfeatures;
+import java.awt.Color;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
import jalview.api.AlignViewportI;
import jalview.api.FeatureColourI;
import jalview.api.FeaturesDisplayedI;
@@ -38,20 +52,6 @@ import jalview.renderer.seqfeatures.FeatureRenderer;
import jalview.schemes.FeatureColour;
import jalview.util.ColorUtils;
-import java.awt.Color;
-import java.beans.PropertyChangeListener;
-import java.beans.PropertyChangeSupport;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
public abstract class FeatureRendererModel
implements jalview.api.FeatureRenderer
{
@@ -1162,8 +1162,8 @@ public abstract class FeatureRendererModel
}
@Override
- public MappedFeatures findComplementFeaturesAtResidue(SequenceI sequence,
- int pos)
+ public MappedFeatures findComplementFeaturesAtResidue(
+ final SequenceI sequence, final int pos)
{
SequenceI ds = sequence.getDatasetSequence();
if (ds == null)
@@ -1232,9 +1232,12 @@ public abstract class FeatureRendererModel
}
/*
- * sort by renderorder, inefficiently
+ * sort by renderorder (inefficiently but ok for small scale);
+ * NB this sorts 'on top' feature to end, for rendering
*/
List result = new ArrayList<>();
+ final int toAdd = found.size();
+ int added = 0;
for (String type : renderOrder)
{
for (SequenceFeature sf : found)
@@ -1242,11 +1245,15 @@ public abstract class FeatureRendererModel
if (type.equals(sf.getType()))
{
result.add(sf);
- if (result.size() == found.size())
- {
- return new MappedFeatures(mapping, mapFrom, pos, residue,
- result);
- }
+ added++;
+ }
+ if (added == toAdd)
+ {
+ break;
+ }
+ if (added == toAdd)
+ {
+ break;
}
}
}
diff --git a/test/jalview/datamodel/SequenceFeatureTest.java b/test/jalview/datamodel/SequenceFeatureTest.java
index cd8f9eb..673ea29 100644
--- a/test/jalview/datamodel/SequenceFeatureTest.java
+++ b/test/jalview/datamodel/SequenceFeatureTest.java
@@ -26,11 +26,11 @@ import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertSame;
import static org.testng.AssertJUnit.assertTrue;
-import jalview.gui.JvOptionPane;
-
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
+import jalview.gui.JvOptionPane;
+
public class SequenceFeatureTest
{
@@ -285,7 +285,7 @@ public class SequenceFeatureTest
String expected = "
Location | TestSeq | 22 |
"
+ "Type | variant | |
"
+ "Description | G,C | |
";
- assertEquals(expected, sf.getDetailsReport(seqName));
+ assertEquals(expected, sf.getDetailsReport(seqName, null));
// contact feature
sf = new SequenceFeature("Disulphide Bond", "a description", 28, 31,
@@ -293,7 +293,7 @@ public class SequenceFeatureTest
expected = "
Location | TestSeq | 28:31 |
"
+ "Type | Disulphide Bond | |
"
+ "Description | a description | |
";
- assertEquals(expected, sf.getDetailsReport(seqName));
+ assertEquals(expected, sf.getDetailsReport(seqName, null));
sf = new SequenceFeature("variant", "G,C", 22, 33,
12.5f, "group");
@@ -306,7 +306,7 @@ public class SequenceFeatureTest
+ "Group | group | |
"
+ "Child | | ENSP002 |
"
+ "Parent | | ENSG001 |
";
- assertEquals(expected, sf.getDetailsReport(seqName));
+ assertEquals(expected, sf.getDetailsReport(seqName, null));
/*
* feature with embedded html link in description
@@ -317,6 +317,6 @@ public class SequenceFeatureTest
+ "Type | Pfam | |
"
+ "Description | Fer2 Status: True Positive Pfam 8_8 | |
"
+ "Group | Uniprot | |
";
- assertEquals(expected, sf.getDetailsReport(seqName));
+ assertEquals(expected, sf.getDetailsReport(seqName, null));
}
}
diff --git a/test/jalview/io/SequenceAnnotationReportTest.java b/test/jalview/io/SequenceAnnotationReportTest.java
index 42183ca..7e00caa 100644
--- a/test/jalview/io/SequenceAnnotationReportTest.java
+++ b/test/jalview/io/SequenceAnnotationReportTest.java
@@ -23,6 +23,14 @@ package jalview.io;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertTrue;
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
import jalview.api.FeatureColourI;
import jalview.datamodel.DBRefEntry;
import jalview.datamodel.Sequence;
@@ -33,15 +41,6 @@ import jalview.io.gff.GffConstants;
import jalview.renderer.seqfeatures.FeatureRenderer;
import jalview.schemes.FeatureColour;
import jalview.viewmodel.seqfeatures.FeatureRendererModel;
-
-import java.awt.Color;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
-
import junit.extensions.PA;
public class SequenceAnnotationReportTest
@@ -57,24 +56,24 @@ public class SequenceAnnotationReportTest
@Test(groups = "Functional")
public void testAppendFeature_disulfideBond()
{
- SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+ SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
StringBuilder sb = new StringBuilder();
sb.append("123456");
SequenceFeature sf = new SequenceFeature("disulfide bond", "desc", 1,
3, 1.2f, "group");
// residuePos == 2 does not match start or end of feature, nothing done:
- sar.appendFeature(sb, 2, null, sf, null);
+ sar.appendFeature(sb, 2, null, sf, null, 0);
assertEquals("123456", sb.toString());
// residuePos == 1 matches start of feature, text appended (but no
)
// feature score is not included
- sar.appendFeature(sb, 1, null, sf, null);
+ sar.appendFeature(sb, 1, null, sf, null, 0);
assertEquals("123456disulfide bond 1:3", sb.toString());
// residuePos == 3 matches end of feature, text appended
//
is prefixed once sb.length() > 6
- sar.appendFeature(sb, 3, null, sf, null);
+ sar.appendFeature(sb, 3, null, sf, null, 0);
assertEquals("123456disulfide bond 1:3
disulfide bond 1:3",
sb.toString());
}
@@ -82,13 +81,13 @@ public class SequenceAnnotationReportTest
@Test(groups = "Functional")
public void testAppendFeatures_longText()
{
- SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+ SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
StringBuilder sb = new StringBuilder();
String longString = "Abcd".repeat(50);
SequenceFeature sf = new SequenceFeature("sequence", longString, 1, 3,
"group");
- sar.appendFeature(sb, 1, null, sf, null);
+ sar.appendFeature(sb, 1, null, sf, null, 0);
assertTrue(sb.length() < 100);
List sfl = new ArrayList<>();
@@ -103,7 +102,7 @@ public class SequenceAnnotationReportTest
sfl.add(sf);
sfl.add(sf);
sfl.add(sf);
- int n = sar.appendFeaturesLengthLimit(sb, 1, sfl,
+ int n = sar.appendFeatures(sb, 1, sfl,
new FeatureRenderer(null), 200); // text should terminate before 200 characters
String s = sb.toString();
assertTrue(s.length() < 200);
@@ -114,27 +113,27 @@ public class SequenceAnnotationReportTest
@Test(groups = "Functional")
public void testAppendFeature_status()
{
- SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+ SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
StringBuilder sb = new StringBuilder();
SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3,
Float.NaN, "group");
sf.setStatus("Confirmed");
- sar.appendFeature(sb, 1, null, sf, null);
+ sar.appendFeature(sb, 1, null, sf, null, 0);
assertEquals("METAL 1 3; Fe2-S; (Confirmed)", sb.toString());
}
@Test(groups = "Functional")
public void testAppendFeature_withScore()
{
- SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+ SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
StringBuilder sb = new StringBuilder();
SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3, 1.3f,
"group");
FeatureRendererModel fr = new FeatureRenderer(null);
Map minmax = fr.getMinMax();
- sar.appendFeature(sb, 1, fr, sf, null);
+ sar.appendFeature(sb, 1, fr, sf, null, 0);
/*
* map has no entry for this feature type - score is not shown:
*/
@@ -144,7 +143,7 @@ public class SequenceAnnotationReportTest
* map has entry for this feature type - score is shown:
*/
minmax.put("METAL", new float[][] { { 0f, 1f }, null });
- sar.appendFeature(sb, 1, fr, sf, null);
+ sar.appendFeature(sb, 1, fr, sf, null, 0);
//
is appended to a buffer > 6 in length
assertEquals("METAL 1 3; Fe2-S
METAL 1 3; Fe2-S Score=1.3",
sb.toString());
@@ -154,19 +153,19 @@ public class SequenceAnnotationReportTest
*/
minmax.put("METAL", new float[][] { { 2f, 2f }, null });
sb.setLength(0);
- sar.appendFeature(sb, 1, fr, sf, null);
+ sar.appendFeature(sb, 1, fr, sf, null, 0);
assertEquals("METAL 1 3; Fe2-S", sb.toString());
}
@Test(groups = "Functional")
public void testAppendFeature_noScore()
{
- SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+ SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
StringBuilder sb = new StringBuilder();
SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3,
Float.NaN, "group");
- sar.appendFeature(sb, 1, null, sf, null);
+ sar.appendFeature(sb, 1, null, sf, null, 0);
assertEquals("METAL 1 3; Fe2-S", sb.toString());
}
@@ -176,7 +175,7 @@ public class SequenceAnnotationReportTest
@Test(groups = "Functional")
public void testAppendFeature_colouredByAttribute()
{
- SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+ SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
StringBuilder sb = new StringBuilder();
SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3,
Float.NaN, "group");
@@ -186,7 +185,7 @@ public class SequenceAnnotationReportTest
* first with no colour by attribute
*/
FeatureRendererModel fr = new FeatureRenderer(null);
- sar.appendFeature(sb, 1, fr, sf, null);
+ sar.appendFeature(sb, 1, fr, sf, null, 0);
assertEquals("METAL 1 3; Fe2-S", sb.toString());
/*
@@ -197,7 +196,7 @@ public class SequenceAnnotationReportTest
fc.setAttributeName("Pfam");
fr.setColour("METAL", fc);
sb.setLength(0);
- sar.appendFeature(sb, 1, fr, sf, null);
+ sar.appendFeature(sb, 1, fr, sf, null, 0);
assertEquals("METAL 1 3; Fe2-S", sb.toString()); // no change
/*
@@ -205,7 +204,7 @@ public class SequenceAnnotationReportTest
*/
fc.setAttributeName("clinical_significance");
sb.setLength(0);
- sar.appendFeature(sb, 1, fr, sf, null);
+ sar.appendFeature(sb, 1, fr, sf, null, 0);
assertEquals("METAL 1 3; Fe2-S; clinical_significance=Benign",
sb.toString());
}
@@ -213,7 +212,7 @@ public class SequenceAnnotationReportTest
@Test(groups = "Functional")
public void testAppendFeature_withScoreStatusAttribute()
{
- SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+ SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
StringBuilder sb = new StringBuilder();
SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3, 1.3f,
"group");
@@ -227,7 +226,7 @@ public class SequenceAnnotationReportTest
fc.setAttributeName("clinical_significance");
fr.setColour("METAL", fc);
minmax.put("METAL", new float[][] { { 0f, 1f }, null });
- sar.appendFeature(sb, 1, fr, sf, null);
+ sar.appendFeature(sb, 1, fr, sf, null, 0);
assertEquals(
"METAL 1 3; Fe2-S Score=1.3; (Confirmed); clinical_significance=Benign",
@@ -237,38 +236,38 @@ public class SequenceAnnotationReportTest
@Test(groups = "Functional")
public void testAppendFeature_DescEqualsType()
{
- SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+ SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
StringBuilder sb = new StringBuilder();
SequenceFeature sf = new SequenceFeature("METAL", "METAL", 1, 3,
Float.NaN, "group");
// description is not included if it duplicates type:
- sar.appendFeature(sb, 1, null, sf, null);
+ sar.appendFeature(sb, 1, null, sf, null, 0);
assertEquals("METAL 1 3", sb.toString());
sb.setLength(0);
sf.setDescription("Metal");
// test is case-sensitive:
- sar.appendFeature(sb, 1, null, sf, null);
+ sar.appendFeature(sb, 1, null, sf, null, 0);
assertEquals("METAL 1 3; Metal", sb.toString());
}
@Test(groups = "Functional")
public void testAppendFeature_stripHtml()
{
- SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+ SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
StringBuilder sb = new StringBuilder();
SequenceFeature sf = new SequenceFeature("METAL",
"helloworld", 1, 3,
Float.NaN, "group");
- sar.appendFeature(sb, 1, null, sf, null);
+ sar.appendFeature(sb, 1, null, sf, null, 0);
// !! strips off ??
assertEquals("METAL 1 3; helloworld", sb.toString());
sb.setLength(0);
sf.setDescription("
&kHD>6");
- sar.appendFeature(sb, 1, null, sf, null);
+ sar.appendFeature(sb, 1, null, sf, null, 0);
// if no tag, html-encodes > and < (only):
assertEquals("METAL 1 3; <br>&kHD>6", sb.toString());
}
@@ -276,7 +275,7 @@ public class SequenceAnnotationReportTest
@Test(groups = "Functional")
public void testCreateSequenceAnnotationReport()
{
- SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+ SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
StringBuilder sb = new StringBuilder();
SequenceI seq = new Sequence("s1", "MAKLKRFQSSTLL");
@@ -398,7 +397,7 @@ public class SequenceAnnotationReportTest
@Test(groups = "Functional")
public void testCreateSequenceAnnotationReport_withEllipsis()
{
- SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+ SequenceAnnotationReport sar = new SequenceAnnotationReport(false);
StringBuilder sb = new StringBuilder();
SequenceI seq = new Sequence("s1", "ABC");