From: Jim Procter Date: Tue, 21 Apr 2020 13:15:07 +0000 (+0100) Subject: Merge branch 'develop' into releases/Release_2_11_1_Branch X-Git-Tag: Release_2_11_1_0~6 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=165cd5fefff08d6633dafb15576ab6c30cdb65d8;hp=6ff6c9b534b42eeb3faafa7bef32c39c34d297f0;p=jalview.git Merge branch 'develop' into releases/Release_2_11_1_Branch --- diff --git a/help/help/help.jhm b/help/help/help.jhm index aecd1a4..99d010d 100755 --- a/help/help/help.jhm +++ b/help/help/help.jhm @@ -47,6 +47,7 @@ + diff --git a/help/help/helpTOC.xml b/help/help/helpTOC.xml index 5802ddb..a0c7fe6 100755 --- a/help/help/helpTOC.xml +++ b/help/help/helpTOC.xml @@ -63,6 +63,7 @@ + diff --git a/help/help/html/features/seqfeaturereport.html b/help/help/html/features/seqfeaturereport.html new file mode 100644 index 0000000..340df5c --- /dev/null +++ b/help/help/html/features/seqfeaturereport.html @@ -0,0 +1,54 @@ + + + +Sequence Feature Reports + + +

+ Sequence Feature Reports
Sequence features + can carry a number of attributes. To view all the attributes for a + particular sequence feature, mouse over the feature and right-click + to open the Popup Menu and + select the feature's entry from the Feature Details + submenu. +

+ Full details for a particular Sequence Feature can be displayed as HTML in a report window +

+ Virtual Feature Reports
When a sequence feature + report is shown for features mapped between CDS and Protein + sequences, the report will include both the original and mapped + feature's location. +

+

+ Copying and pasting annotation to other programs
+ The File→Save option in the sequence + annotation report window allows the report to be saved as HTML, + which will preserve links and any other metadata. It is also + possible to copy and paste the text to other programs, but in some + cases, the HTML will not be preserved. In that case, you can toggle + the display of HTML source code with the Edit→Show + HTML Source drop down menu entry. +

+ Feature Reports were added in Jalview 2.11. + + diff --git a/help/help/html/features/seqfeaturesrep.png b/help/help/html/features/seqfeaturesrep.png new file mode 100644 index 0000000..f5995a1 Binary files /dev/null and b/help/help/html/features/seqfeaturesrep.png differ diff --git a/help/help/html/features/splitView.html b/help/help/html/features/splitView.html index 03b9ced..bb379ed 100644 --- a/help/help/html/features/splitView.html +++ b/help/help/html/features/splitView.html @@ -60,10 +60,14 @@ settings tabs and corresponding views showing 'Virtual Features' from each view overlaid on the other (created with Jalview 2.11.1.0).

-

When virtual features are enabled, they are also shown on any - linked 3D structure views when 'Colour by Sequence' is enabled, and +

+ When virtual features are enabled, they are also shown on any linked + 3D structure views when 'Colour by Sequence' is enabled, and exported as GFF and Jalview Features files (mapped to their - associated virtual coordinates).

+ associated virtual coordinates). Both the original and the mapped + locations are also included in Sequence + Feature Reports. +

Operations supported in Split Frame Mode

diff --git a/help/help/html/menus/popupMenu.html b/help/help/html/menus/popupMenu.html index 7625606..9c65e1a 100755 --- a/help/help/html/menus/popupMenu.html +++ b/help/help/html/menus/popupMenu.html @@ -27,14 +27,15 @@

Popup Menu
This menu is visible when right clicking either within a selected region on the - alignment or on a selected sequence name. It may not be accessible + alignment, on a sequence name, and also when right-clicking a sequence feature. It may not be accessible when in 'Cursor Mode' (toggled with the F2 key).
Mac Users: pressing CTRL whilst clicking the mouse/track pad is the same as a right-click. See your system's settings to configure your track-pad's corners to generate right-clicks.

    -
  • Selection +
  • Selection
    + This menu is only visible when right-clicking a selected sequence or region of the alignment. + diff --git a/help/help/html/releases.html b/help/help/html/releases.html index 4695e7f..d3f2420 100755 --- a/help/help/html/releases.html +++ b/help/help/html/releases.html @@ -58,13 +58,13 @@ li:before { 2.11.1.0
    - 16/04/2020
    + 22/04/2020
    • - Map 'virtual' + Map 'virtual' codon features shown on protein (or vice versa) for display - in alignments, on structure views and for export. + in alignments, on structure views, in feature reports and for export.
    • Feature attributes from VCF files can be 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..87609c6 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,26 @@ 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 featureSequence; - /** - * the sequence mapped from + /* + * the mapping between sequences; + * NB this could be in either sense (from or to featureSequence) */ - public final SequenceI fromSeq; + private final Mapping mapping; /* - * features on the sequence mapped to that overlap the mapped positions + * features on featureSequence that overlap the mapped positions */ public final List features; @@ -60,21 +65,23 @@ public class MappedFeatures * Constructor * * @param theMapping - * @param from - * the sequence mapped from (e.g. CDS) + * sequence mapping (which may be either to, or from, the sequence + * holding the linked features) + * @param featureSeq + * the sequence hosting the virtual features * @param pos - * the residue position in the sequence mapped to + * the residue position in the sequence mapped to * @param res - * the residue character at position pos + * the residue character at position pos * @param theFeatures - * list of mapped features found in the 'from' sequence at - * the mapped position(s) + * list of mapped features found in the 'featureSeq' sequence at the + * mapped position(s) */ - public MappedFeatures(Mapping theMapping, SequenceI from, int pos, + public MappedFeatures(Mapping theMapping, SequenceI featureSeq, int pos, char res, List theFeatures) { mapping = theMapping; - fromSeq = from; + featureSequence = featureSeq; toPosition = pos; toResidue = res; features = theFeatures; @@ -90,13 +97,13 @@ public class MappedFeatures { codonPos = codonPositions; baseCodon = new char[3]; - int cdsStart = fromSeq.getStart(); + int cdsStart = featureSequence.getStart(); baseCodon[0] = Character - .toUpperCase(fromSeq.getCharAt(codonPos[0] - cdsStart)); + .toUpperCase(featureSequence.getCharAt(codonPos[0] - cdsStart)); baseCodon[1] = Character - .toUpperCase(fromSeq.getCharAt(codonPos[1] - cdsStart)); + .toUpperCase(featureSequence.getCharAt(codonPos[1] - cdsStart)); baseCodon[2] = Character - .toUpperCase(fromSeq.getCharAt(codonPos[2] - cdsStart)); + .toUpperCase(featureSequence.getCharAt(codonPos[2] - cdsStart)); } else { @@ -108,11 +115,14 @@ public class MappedFeatures /** * Computes and returns comma-delimited HGVS notation peptide variants derived * from codon allele variants. If no variants are found, answers an empty - * string. + * string. The peptide variant is either simply read from the "CSQ:HGVSp" + * attribute if present, else computed based on the "alleles" attribute if + * present. If neither attribute is found, no variant (empty string) is + * returned. * * @param sf - * a sequence feature (which must be one of those held in this - * object) + * a sequence feature (which must be one of those held in this + * object) * @return */ public String findProteinVariants(SequenceFeature sf) @@ -233,4 +243,53 @@ public class MappedFeatures return vars.toString(); } + + /** + * Answers the name of the linked sequence holding any mapped features + * + * @return + */ + public String getLinkedSequenceName() + { + return featureSequence == null ? null : featureSequence.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 mapping.to == featureSequence ? map.locateInFrom(begin, end) + : map.locateInTo(begin, end); + } + + /** + * Answers true if the linked features are on coding sequence, false if on + * peptide + * + * @return + */ + public boolean isFromCds() + { + if (mapping.getMap().getFromRatio() == 3) + { + return mapping.to != featureSequence; + } + return mapping.to == featureSequence; + } } diff --git a/src/jalview/datamodel/SequenceFeature.java b/src/jalview/datamodel/SequenceFeature.java index 2dd9cf0..6eeba2f 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,26 @@ 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)); + + String consequence = ""; + 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()) + { + 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, "")); @@ -615,6 +636,12 @@ public class SequenceFeature implements FeatureLocationI sb.append(String.format(ROW_DATA, "Group", featureGroup, "")); } + if (!consequence.isEmpty()) + { + sb.append(String.format(ROW_DATA, "Consequence", + "Translated by Jalview", consequence)); + } + if (otherDetails != null) { TreeMap ordered = new TreeMap<>( 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 e3ea098..cd974a4 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); @@ -1048,9 +1045,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 @@ -1068,9 +1065,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..27c1652 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,49 @@ 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); + if (beginRange == null || endRange == null) + { + // something went wrong + return false; + } + 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 +260,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 +325,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 +501,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..426ec1f 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,11 @@ 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; } } } diff --git a/test/jalview/datamodel/SequenceFeatureTest.java b/test/jalview/datamodel/SequenceFeatureTest.java index cd8f9eb..f8479a3 100644 --- a/test/jalview/datamodel/SequenceFeatureTest.java +++ b/test/jalview/datamodel/SequenceFeatureTest.java @@ -26,11 +26,15 @@ import static org.testng.AssertJUnit.assertNull; import static org.testng.AssertJUnit.assertSame; import static org.testng.AssertJUnit.assertTrue; -import jalview.gui.JvOptionPane; +import java.util.ArrayList; +import java.util.List; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import jalview.gui.JvOptionPane; +import jalview.util.MapList; + public class SequenceFeatureTest { @@ -285,7 +289,7 @@ public class SequenceFeatureTest String expected = "
      " + "" + "
      LocationTestSeq22
      Typevariant
      DescriptionG,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 +297,7 @@ public class SequenceFeatureTest expected = "
      " + "" + "
      LocationTestSeq28:31
      TypeDisulphide Bond
      Descriptiona 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 +310,7 @@ public class SequenceFeatureTest + "Groupgroup" + "ChildENSP002" + "ParentENSG001"; - assertEquals(expected, sf.getDetailsReport(seqName)); + assertEquals(expected, sf.getDetailsReport(seqName, null)); /* * feature with embedded html link in description @@ -317,6 +321,39 @@ public class SequenceFeatureTest + "TypePfam" + "DescriptionFer2 Status: True Positive Pfam 8_8" + "GroupUniprot"; - assertEquals(expected, sf.getDetailsReport(seqName)); + assertEquals(expected, sf.getDetailsReport(seqName, null)); + } + + /** + * Feature details report for a virtual feature should include original and + * mapped locations, and also derived peptide consequence if it can be + * determined + */ + @Test(groups = { "Functional" }) + public void testGetDetailsReport_virtualFeature() + { + SequenceI cds = new Sequence("Cds/101-121", "CCTttgAGAtttCAAatgGAT"); + SequenceI seq = new Sequence("TestSeq/8-14", "PLRFQMD"); + MapList map = new MapList(new int[] { 101, 118 }, new int[] { 8, 13 }, + 3, 1); + Mapping mapping = new Mapping(seq, map); + List features = new ArrayList<>(); + // vary ttg (Leu) to ttc (Phe) + SequenceFeature sf = new SequenceFeature("variant", "G,C", 106, 106, + null); + sf.setValue("alleles", "G,C"); // needed to compute peptide consequence! + features.add(sf); + + MappedFeatures mf = new MappedFeatures(mapping, cds, 9, 'L', features); + + String expected = "
      " + + "" + + "" + + "" + + "" + + "" + + "
      LocationCds106
      Peptide LocationTestSeq9
      Typevariant
      DescriptionG,C
      ConsequenceTranslated by Jalviewp.Leu9Phe
      allelesG,C
      "; + + assertEquals(expected, sf.getDetailsReport(seq.getName(), mf)); } } diff --git a/test/jalview/io/SequenceAnnotationReportTest.java b/test/jalview/io/SequenceAnnotationReportTest.java index 42183ca..772ed2b 100644 --- a/test/jalview/io/SequenceAnnotationReportTest.java +++ b/test/jalview/io/SequenceAnnotationReportTest.java @@ -23,8 +23,18 @@ 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.MappedFeatures; +import jalview.datamodel.Mapping; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; @@ -32,16 +42,8 @@ import jalview.gui.JvOptionPane; import jalview.io.gff.GffConstants; import jalview.renderer.seqfeatures.FeatureRenderer; import jalview.schemes.FeatureColour; +import jalview.util.MapList; 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 +59,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 +84,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 +105,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 +116,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 +146,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 +156,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 +178,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 +188,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 +199,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 +207,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 +215,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 +229,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 +239,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 but not ?? 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 +278,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 +400,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"); @@ -424,4 +426,63 @@ public class SequenceAnnotationReportTest .endsWith( "
      PDB7 3iu1
      PDB8,...
      (Output Sequence Details to list all database references)")); } + + /** + * Test adding a linked feature to the tooltip + */ + @Test(groups = "Functional") + public void testAppendFeature_virtualFeature() + { + /* + * map CDS to peptide sequence + */ + SequenceI cds = new Sequence("Cds/101-121", "CCTttgAGAtttCAAatgGAT"); + SequenceI peptide = new Sequence("Peptide/8-14", "PLRFQMD"); + MapList map = new MapList(new int[] { 101, 118 }, new int[] { 8, 13 }, + 3, 1); + Mapping mapping = new Mapping(peptide, map); + + /* + * assume variant feature found at CDS position 106 G>C + */ + List features = new ArrayList<>(); + // vary ttg (Leu) to ttc (Phe) + SequenceFeature sf = new SequenceFeature("variant", "G,C", 106, 106, + Float.NaN, null); + features.add(sf); + MappedFeatures mf = new MappedFeatures(mapping, cds, 9, 'L', features); + + StringBuilder sb = new StringBuilder(); + SequenceAnnotationReport sar = new SequenceAnnotationReport(false); + sar.appendFeature(sb, 1, null, sf, mf, 0); + + /* + * linked feature shown in tooltip in protein coordinates + */ + assertEquals("variant 9; G,C", sb.toString()); + + /* + * adding "alleles" attribute to variant allows peptide consequence + * to be calculated and added to the tooltip + */ + sf.setValue("alleles", "G,C"); + sb = new StringBuilder(); + sar.appendFeature(sb, 1, null, sf, mf, 0); + assertEquals("variant 9; G,C p.Leu9Phe", sb.toString()); + + /* + * now a virtual peptide feature on CDS + * feature at 11-12 on peptide maps to 110-115 on CDS + * here we test for tooltip at 113 (t) + */ + SequenceFeature sf2 = new SequenceFeature("metal", "Fe", 11, 12, + 2.3f, "Uniprot"); + features.clear(); + features.add(sf2); + mapping = new Mapping(peptide, map); + mf = new MappedFeatures(mapping, peptide, 113, 't', features); + sb = new StringBuilder(); + sar.appendFeature(sb, 1, null, sf2, mf, 0); + assertEquals("metal 110 115; Fe Score=2.3", sb.toString()); + } }