static int computePeptideVariants(SequenceI peptide, int peptidePos,
List<DnaVariant>[] codonVariants)
{
- String residue = String.valueOf(peptide.getCharAt(peptidePos - 1));
+ String residue = String
+ .valueOf(peptide.getCharAt(peptidePos - peptide.getStart()));
int count = 0;
String base1 = codonVariants[0].get(0).base;
String base2 = codonVariants[1].get(0).base;
}
@Override
- public void highlightSequence(SearchResultsI results)
+ public String highlightSequence(SearchResultsI results)
{
if (av.isFollowHighlight())
{
}
setStatusMessage(results);
seqCanvas.highlightSearchResults(results);
-
+ return null;
}
@Override
--- /dev/null
+package jalview.datamodel;
+
+import jalview.io.gff.Gff3Helper;
+import jalview.schemes.ResidueProperties;
+import jalview.util.MappingUtils;
+import jalview.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A data bean to hold a list of mapped sequence features (e.g. CDS features
+ * mapped from protein), and the mapping between the sequences
+ *
+ * @author gmcarstairs
+ */
+public class MappedFeatures
+{
+ /*
+ * the mapping from CDS to peptide
+ */
+ public final Mapping mapping;
+
+ /**
+ * the CDS sequence mapped to
+ */
+ public final SequenceI fromSeq;
+
+ /*
+ * the residue position in the peptide sequence
+ */
+ public final int fromPosition;
+
+ /*
+ * the peptide residue at the position
+ */
+ public final char fromResidue;
+
+ /*
+ * features on CDS that overlap the codon positions
+ */
+ public final List<SequenceFeature> features;
+
+ /**
+ * Constructor
+ *
+ * @param theMapping
+ * @param pos
+ * @param res
+ * @param theFeatures
+ */
+ public MappedFeatures(Mapping theMapping, SequenceI from, int pos,
+ char res,
+ List<SequenceFeature> theFeatures)
+ {
+ mapping = theMapping;
+ fromSeq = from;
+ fromPosition = pos;
+ fromResidue = res;
+ features = theFeatures;
+ }
+
+ /**
+ * Computes and returns a (possibly empty) list of HGVS notation peptide
+ * variants derived from codon allele variants
+ *
+ * @return
+ */
+ public List<String> findProteinVariants()
+ {
+ List<String> vars = new ArrayList<>();
+
+ /*
+ * determine canonical codon
+ */
+ int[] codonPos = MappingUtils.flattenRanges(
+ mapping.getMap().locateInFrom(fromPosition, fromPosition));
+ if (codonPos.length != 3)
+ {
+ // error
+ return vars;
+ }
+ final char[] baseCodon = new char[3];
+ int cdsStart = fromSeq.getStart();
+ baseCodon[0] = fromSeq.getCharAt(codonPos[0] - cdsStart);
+ baseCodon[1] = fromSeq.getCharAt(codonPos[1] - cdsStart);
+ baseCodon[2] = fromSeq.getCharAt(codonPos[2] - cdsStart);
+
+ // todo avoid duplication of code in AlignmentUtils.buildDnaVariantsMap
+
+ for (SequenceFeature sf : features)
+ {
+ int cdsPos = sf.getBegin();
+ if (cdsPos != sf.getEnd())
+ {
+ // not handling multi-locus variant features
+ continue;
+ }
+ if (cdsPos != codonPos[0] && cdsPos != codonPos[1]
+ && cdsPos != codonPos[2])
+ {
+ // e.g. feature on intron within spliced codon!
+ continue;
+ }
+
+ String alls = (String) sf.getValue(Gff3Helper.ALLELES);
+ if (alls == null)
+ {
+ continue;
+ }
+ String from3 = StringUtils.toSentenceCase(
+ ResidueProperties.aa2Triplet
+ .get(String.valueOf(fromResidue)));
+
+ /*
+ * make a peptide variant for each SNP allele
+ * e.g. C,G,T gives variants G and T for base C
+ */
+ String[] alleles = alls.toUpperCase().split(",");
+ for (String allele : alleles)
+ {
+ allele = allele.trim().toUpperCase();
+ if (allele.length() > 1)
+ {
+ continue; // multi-locus variant
+ }
+ char[] variantCodon = new char[3];
+ variantCodon[0] = baseCodon[0];
+ variantCodon[1] = baseCodon[1];
+ variantCodon[2] = baseCodon[2];
+
+ /*
+ * poke variant base into canonical codon
+ */
+ int i = cdsPos == codonPos[0] ? 0 : (cdsPos == codonPos[1] ? 1 : 2);
+ variantCodon[i] = allele.toUpperCase().charAt(0);
+ String codon = new String(variantCodon);
+ String peptide = ResidueProperties.codonTranslate(codon);
+ if (fromResidue != peptide.charAt(0))
+ {
+ String to3 = StringUtils.toSentenceCase(
+ ResidueProperties.aa2Triplet.get(peptide));
+ String var = "p." + from3 + fromPosition + to3;
+ if (!vars.contains(var))
+ {
+ vars.add(var);
+ }
+ }
+ }
+ }
+
+ return vars;
+ }
+}
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
+import java.util.StringTokenizer;
import java.util.Vector;
import org.jmol.adapter.smarter.SmarterJmolAdapter;
implements JmolStatusListener, JmolSelectionListener,
ComponentListener
{
+ private String lastMessage;
+
boolean allChainsSelected = false;
/*
String lastCommand;
- String lastMessage;
-
boolean loadedInline;
StringBuffer resetLastRes = new StringBuffer();
viewer.openStringInline(string);
}
- public void mouseOverStructure(int atomIndex, String strInfo)
+ protected void mouseOverStructure(int atomIndex, final String strInfo)
{
int pdbResNum;
int alocsep = strInfo.indexOf("^");
try
{
// recover PDB filename for the model hovered over.
- int mnumber = new Integer(mdlId).intValue() - 1;
+ int mnumber = Integer.valueOf(mdlId).intValue() - 1;
if (_modelFileNameMap != null)
{
int _mp = _modelFileNameMap.length - 1;
} catch (Exception e)
{
}
- ;
}
- if (lastMessage == null || !lastMessage.equals(strInfo))
+
+ /*
+ * highlight position on alignment(s); if some text is returned,
+ * show this as a second line on the structure hover tooltip
+ */
+ String label = getSsm().mouseOverStructure(pdbResNum, chainId,
+ pdbfilename);
+ if (label != null)
{
- getSsm().mouseOverStructure(pdbResNum, chainId, pdbfilename);
+ StringTokenizer toks = new StringTokenizer(strInfo, " ");
+ StringBuilder sb = new StringBuilder();
+ sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
+ .append(chainId).append("/1");
+ sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
+ .append(toks.nextToken());
+ sb.append("|").append(label).append("\"");
+ evalStateCommand(sb.toString());
}
-
- lastMessage = strInfo;
}
public void notifyAtomHovered(int atomIndex, String strInfo, String data)
{
+ if (strInfo.equals(lastMessage))
+ {
+ return;
+ }
+ lastMessage = strInfo;
if (data != null)
{
System.err.println("Ignoring additional hover info: " + data
import jalview.datamodel.AlignmentI;
import jalview.datamodel.ColumnSelection;
import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.MappedFeatures;
import jalview.datamodel.SearchResultMatchI;
import jalview.datamodel.SearchResults;
import jalview.datamodel.SearchResultsI;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
* the start of the highlighted region.
*/
@Override
- public void highlightSequence(SearchResultsI results)
+ public String highlightSequence(SearchResultsI results)
{
if (results == null || results.equals(lastSearchResults))
{
- return;
+ return null;
}
lastSearchResults = results;
{
setStatusMessage(results);
}
+ return results.isEmpty() ? null : getHighlightInfo(results);
+ }
+
+ /**
+ * temporary hack: answers a message suitable to show on structure hover
+ * label. This is normally null. It is a peptide variation description if
+ * <ul>
+ * <li>results are a single residue in a protein alignment</li>
+ * <li>there is a mapping to a coding sequence (codon)</li>
+ * <li>there are one or more SNP variant features on the codon</li>
+ * </ul>
+ * in which case the answer is of the format (e.g.) "p.Glu388Asp"
+ *
+ * @param results
+ * @return
+ */
+ private String getHighlightInfo(SearchResultsI results)
+ {
+ /*
+ * ideally, just find mapped CDS (as we don't care about render style here);
+ * for now, go via split frame complement's FeatureRenderer
+ */
+ AlignViewportI complement = ap.getAlignViewport().getCodingComplement();
+ if (complement == null)
+ {
+ return null;
+ }
+ AlignFrame af = Desktop.getAlignFrameFor(complement);
+ FeatureRendererModel fr2 = af.getFeatureRenderer();
+
+ int j = results.getSize();
+ List<String> infos = new ArrayList<>();
+ for (int i = 0; i < j; i++)
+ {
+ SearchResultMatchI match = results.getResults().get(i);
+ int pos = match.getStart();
+ if (pos == match.getEnd())
+ {
+ SequenceI seq = match.getSequence();
+ SequenceI ds = seq.getDatasetSequence() == null ? seq
+ : seq.getDatasetSequence();
+ MappedFeatures mf = fr2
+ .findComplementFeaturesAtResidue(ds, pos);
+ List<String> pv = mf.findProteinVariants();
+ for (String s : pv)
+ {
+ if (!infos.contains(s))
+ {
+ infos.addAll(pv);
+ }
+ }
+ }
+ }
+
+ if (infos.isEmpty())
+ {
+ return null;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (String info : infos)
+ {
+ if (sb.length() > 0)
+ {
+ sb.append("|");
+ }
+ sb.append(info);
+ }
+ return sb.toString();
}
@Override
.getCodingComplement();
AlignFrame af = Desktop.getAlignFrameFor(complement);
FeatureRendererModel fr2 = af.getFeatureRenderer();
- features = fr2.findComplementFeaturesAtResidue(sequence, pos);
- seqARep.appendFeatures(tooltipText, pos, features, fr2);
+ MappedFeatures mf = fr2.findComplementFeaturesAtResidue(sequence,
+ pos);
+ seqARep.appendFeatures(tooltipText, pos, mf.features, fr2);
}
}
}
import jalview.api.AlignViewportI;
import jalview.api.FeatureColourI;
+import jalview.datamodel.MappedFeatures;
import jalview.datamodel.Range;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
for (int pos = visiblePositions.start; pos <= visiblePositions.end; pos++)
{
int column = seq.findIndex(pos);
- List<SequenceFeature> features = fr2
+ MappedFeatures mf = fr2
.findComplementFeaturesAtResidue(seq, pos);
- for (SequenceFeature sf : features)
+ for (SequenceFeature sf : mf.features)
{
FeatureColourI fc = fr2.getFeatureStyle(sf.getType());
Color featureColour = fr2.getColor(sf, fc);
AlignViewportI complement = av.getCodingComplement();
AlignFrame af = Desktop.getAlignFrameFor(complement);
FeatureRendererModel fr2 = af.getFeatureRenderer();
- List<SequenceFeature> features = fr2.findComplementFeaturesAtResidue(
+ MappedFeatures mf = fr2.findComplementFeaturesAtResidue(
seq, seq.findPosition(column - 1));
ReverseListIterator<SequenceFeature> it = new ReverseListIterator<>(
- features);
+ mf.features);
while (it.hasNext())
{
SequenceFeature sf = it.next();
// TODO remove this? never called on SequenceListener type
public void mouseOverSequence(SequenceI sequence, int index, int pos);
- public void highlightSequence(SearchResultsI results);
+ /**
+ * Highlights any position(s) represented by the search results and
+ * (optionally) returns an informative message about the position(s)
+ * higlighted
+ *
+ * @param results
+ * @return
+ */
+ public String highlightSequence(SearchResultsI results);
// TODO remove this? never called
public void updateColours(SequenceI sequence, int index);
* @param pdbResNum
* @param chain
* @param pdbfile
+ * @return
*/
- public void mouseOverStructure(int pdbResNum, String chain,
+ public String mouseOverStructure(int pdbResNum, String chain,
String pdbfile)
{
AtomSpec atomSpec = new AtomSpec(pdbfile, chain, pdbResNum, 0);
List<AtomSpec> atoms = Collections.singletonList(atomSpec);
- mouseOverStructure(atoms);
+ return mouseOverStructure(atoms);
}
/**
*
* @param atoms
*/
- public void mouseOverStructure(List<AtomSpec> atoms)
+ public String mouseOverStructure(List<AtomSpec> atoms)
{
if (listeners == null)
{
// old or prematurely sent event
- return;
+ return null;
}
boolean hasSequenceListener = false;
for (int i = 0; i < listeners.size(); i++)
}
if (!hasSequenceListener)
{
- return;
+ return null;
}
SearchResultsI results = findAlignmentPositionsForStructurePositions(
atoms);
+ String result = null;
for (Object li : listeners)
{
if (li instanceof SequenceListener)
{
- ((SequenceListener) li).highlightSequence(results);
+ String s = ((SequenceListener) li).highlightSequence(results);
+ if (s != null)
+ {
+ result = s;
+ }
}
}
+ return result;
}
/**
import jalview.api.FeaturesDisplayedI;
import jalview.datamodel.AlignedCodonFrame;
import jalview.datamodel.AlignmentI;
+import jalview.datamodel.MappedFeatures;
import jalview.datamodel.Mapping;
import jalview.datamodel.SearchResultMatchI;
import jalview.datamodel.SearchResults;
* @param pos
* @return
*/
- public List<SequenceFeature> findComplementFeaturesAtResidue(SequenceI sequence, int pos)
+ public MappedFeatures findComplementFeaturesAtResidue(SequenceI sequence,
+ int pos)
{
SequenceI ds = sequence.getDatasetSequence();
+ if (ds == null)
+ {
+ ds = sequence;
+ }
+ final char residue = ds.getCharAt(pos - ds.getStart());
+
List<SequenceFeature> found = new ArrayList<>();
List<AlignedCodonFrame> mappings = this.av.getAlignment()
.getCodonFrame(sequence);
* todo: direct lookup of CDS for peptide and vice-versa; for now,
* have to search through an unordered list of mappings for a candidate
*/
+ Mapping mapping = null;
+ SequenceI mapFrom = null;
+
for (AlignedCodonFrame acf : mappings)
{
- Mapping mapping = acf.getMappingForSequence(sequence, true);
+ mapping = acf.getMappingForSequence(sequence, true);
if (mapping == null || mapping.getMap().getFromRatio() == mapping
.getMap().getToRatio())
{
{
int fromRes = match.getStart();
int toRes = match.getEnd();
+ mapFrom = match.getSequence();
List<SequenceFeature> fs = findFeaturesAtResidue(
match.getSequence(), fromRes, toRes);
for (SequenceFeature sf : fs)
}
}
}
+
+ /*
+ * just take the first mapped features we find
+ */
+ if (!found.isEmpty())
+ {
+ break;
+ }
}
+
/*
* sort by renderorder, inefficiently
*/
result.add(sf);
if (result.size() == found.size())
{
- return result;
+ return new MappedFeatures(mapping, mapFrom, pos, residue,
+ result);
}
}
}
}
- return result;
+ return new MappedFeatures(mapping, mapFrom, pos, residue, result);
}
}