label.fetch_retrieve_from =Retrieve from {0}</html>
label.fetch_retrieve_from_all_sources = Retrieve from all {0} sources in {1}<br>First is :{2}<html>
label.feature_settings_click_drag = Drag up or down to change render order.<br/>Double click to select columns containing feature.
+label.feature_settings_select_columns = Double click to select columns containing feature
label.transparency_tip = Adjust transparency to 'see through' feature colours.
label.opt_and_params_further_details = see further details by right-clicking
label.opt_and_params_show_brief_desc_image_link = <html>Click to show brief description<br><img src="{0}"/> Right click for further information.</html>
label.most_polymer_residues = Most Polymer Residues
label.cached_structures = Cached Structures
label.free_text_search = Free Text Search
+label.summary_view = Summary View
+label.group_by_so = Group features by Sequence Ontology
+label.apply_to_subtypes = Apply to features and sub-types of
+label.apply_also_to = Apply also to:
label.backupfiles_confirm_delete = Confirm delete
label.backupfiles_confirm_delete_old_files = Delete the following older backup files? (see the Backups tab in Preferences for more options)
label.backupfiles_confirm_save_file = Confirm save file
label.rest_client_submit = {0} utilizando {1}
label.fetch_retrieve_from =Recuperar de {0}
label.fetch_retrieve_from_all_sources = Recuperar de todas las fuentes {0} en {1}<br>La primera es :{2}
-label.feature_settings_click_drag = Haga clic o arrastre los tipos de las características hacia arriba o hacia abajo para cambiar el orden de visualización.<br/>Haga doble clic para seleccionar las columnas que contienen las características del alineamiento/selección actual.<br/>
+label.feature_settings_click_drag = Haga clic o arrastre los tipos de las características hacia arriba o hacia abajo para cambiar el orden de visualización.<br/>Haga doble clic para seleccionar las columnas que contienen las características del alineamiento/selección actual.
+label.feature_settings_select_columns =Haga doble clic para seleccionar las columnas que contienen las características del alineamiento/selección actual
label.opt_and_params_further_details = ver los detalles adicionales haciendo clic en el botón derecho
label.opt_and_params_show_brief_desc_image_link = Haga clic para ver una descripción breve<br><img src="{0}"/>Haga clic en el botón derecho para obtener información adicional.
label.opt_and_params_show_brief_desc = Haga clic para ver una descripción breve<br>
label.most_polymer_residues = Más Residuos de Polímeros
label.cached_structures = Estructuras en Caché
label.free_text_search = Búsqueda de texto libre
+label.summary_view = Vista Resumida
+label.group_by_so = Agrupar por términos de la Sequence Ontology
+label.apply_to_subtypes = Aplicar también a características y subtipos de
+label.apply_also_to = Aplicar también a:
label.backupfiles_confirm_delete = Confirmar borrar
label.backupfiles_confirm_delete_old_files = ¿Borrar los siguientes archivos? (ver la pestaña 'Copias' de la ventana de Preferencias para más opciones)
label.backupfiles_confirm_save_file = Confirmar guardar archivo
* @param toggle
* - rather than explicitly set, toggle selection state
* @param featureType
- * - feature type string
+ * - one or more feature types to match
* @return true if operation affected state
*/
boolean markColumnsContainingFeatures(boolean invert,
- boolean extendCurrent, boolean toggle, String featureType);
+ boolean extendCurrent, boolean toggle, String... featureType);
/**
* sort the alignment or current selection by average score over the given set
* configure 'full' SO model if preferences say to,
* else use the default (SO Lite)
*/
- if (Cache.getDefault("USE_FULL_SO", false))
+ if (Cache.getDefault("USE_FULL_SO", true))
{
SequenceOntologyFactory.setInstance(new SequenceOntology());
}
@Override
public boolean markColumnsContainingFeatures(boolean invert,
- boolean extendCurrent, boolean toggle, String featureType)
+ boolean extendCurrent, boolean toggle, String... featureType)
{
// JBPNote this routine could also mark rows, not just columns.
// need a decent query structure to allow all types of feature searches
SequenceCollectionI sqcol = searchSelection ? viewport
.getSelectionGroup() : viewport.getAlignment();
- int nseq = findColumnsWithFeature(featureType, sqcol, bs);
+ int nseq = findColumnsWithFeature(sqcol, bs, featureType);
ColumnSelection cs = viewport.getColumnSelection();
if (cs == null)
cs = new ColumnSelection();
}
+ String featureTypeString = featureType[0];
+ if (featureType.length > 1)
+ {
+ /*
+ * ellipsis if multiple feature types selected
+ * (from Summary View in Feature Settings)
+ */
+ featureTypeString += "...";
+ }
+
if (bs.cardinality() > 0 || invert)
{
boolean changed = cs.markColumns(bs, sqcol.getStartRes(),
invert ? MessageManager
.getString("label.not_containing")
: MessageManager.getString("label.containing"),
- featureType, Integer.valueOf(nseq).toString() }));
+ featureTypeString, Integer.valueOf(nseq).toString() }));
return true;
}
}
{
String key = searchSelection ? "label.no_feature_found_selection"
: "label.no_feature_of_type_found";
- avcg.setStatus(MessageManager.formatMessage(key,
- new String[] { featureType }));
+ avcg.setStatus(
+ MessageManager.formatMessage(key, new String[]
+ { featureTypeString }));
if (!extendCurrent)
{
cs.clear();
/**
* Sets a bit in the BitSet for each column (base 0) in the sequence
- * collection which includes a visible feature of the specified feature type.
- * Returns the number of sequences which have the feature visible in the
- * selected range.
+ * collection which includes a visible feature of the specified feature
+ * type(s). Returns the number of sequences which have the feature(s) visible
+ * in the selected range.
*
- * @param featureType
* @param sqcol
* @param bs
+ * @param featureType
+ *
* @return
*/
- int findColumnsWithFeature(String featureType,
- SequenceCollectionI sqcol, BitSet bs)
+ int findColumnsWithFeature(SequenceCollectionI sqcol,
+ BitSet bs, String... featureType)
{
FeatureRenderer fr = alignPanel == null ? null : alignPanel
.getFeatureRenderer();
--- /dev/null
+package jalview.datamodel.ontology;
+
+import jalview.io.gff.SequenceOntologyFactory;
+import jalview.io.gff.SequenceOntologyI;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A base class for models of Sequence Ontology and others
+ *
+ * @author gmcarstairs
+ *
+ */
+public abstract class OntologyBase implements OntologyI
+{
+ protected Map<String, List<String>> rootParents = new HashMap<>();
+
+ @Override
+ public Set<String> getParentTerms(Set<String> terms)
+ {
+ Set<String> parents = new HashSet<>(terms);
+
+ boolean childRemoved = true;
+ while (childRemoved)
+ {
+ childRemoved = removeChild(parents);
+ }
+ return parents;
+ }
+
+ /**
+ * Removes the first term in the given set found which is a child of another
+ * term in the set. Answers true if a child was found and removed, else false.
+ *
+ * @param terms
+ * @return
+ */
+ boolean removeChild(Set<String> terms)
+ {
+ for (String t1 : terms)
+ {
+ for (String t2 : terms)
+ {
+ if (t1 != t2)
+ {
+ if (isA(t1, t2))
+ {
+ terms.remove(t1);
+ return true;
+ }
+ if (isA(t2, t1))
+ {
+ terms.remove(t2);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public List<String> getChildTerms(String parent, List<String> terms)
+ {
+ List<String> children = new ArrayList<>();
+ for (String term : terms)
+ {
+ if (!term.equals(parent) && isA(term, parent))
+ {
+ children.add(term);
+ }
+ }
+ return children;
+ }
+
+ /**
+ * Answers a (possibly empty) map of any Ontology terms (from the given term
+ * and its parents) which subsume one or more of the target terms. The map key
+ * is an ontology term, and the entry is the list of target terms that are
+ * sub-terms of the key.
+ * <p>
+ * For example if {@code stop_gained} and {@code stop_lost} are known feature
+ * types, then SO term {@ nonsynonymous_variant} is the first common parent of
+ * both terms
+ *
+ * @param givenTerm
+ * the term to search from
+ * @param targetTerms
+ * candidate terms to 'capture' in ontology groupings
+ * @return
+ */
+ @Override
+ public Map<String, List<String>> findSequenceOntologyGroupings(
+ String givenTerm, List<String> targetTerms)
+ {
+ List<String> sortedTypes = new ArrayList<>(targetTerms);
+ Collections.sort(sortedTypes);
+
+ Map<String, List<String>> parents = new HashMap<>();
+ if (!isValidTerm(givenTerm))
+ {
+ return parents;
+ }
+
+ /*
+ * method:
+ * walk up featureType and all of its parents
+ * find other feature types which are subsumed by each term
+ * add each distinct aggregation of included feature types to the map
+ */
+ List<String> candidates = new ArrayList<>();
+ SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+
+ candidates.add(givenTerm);
+ while (!candidates.isEmpty())
+ {
+ String term = candidates.remove(0);
+ List<String> includedFeatures = new ArrayList<>();
+ for (String type : sortedTypes)
+ {
+ if (!type.equals(givenTerm) && so.isA(type, term))
+ {
+ includedFeatures.add(type);
+ }
+ }
+ if (!includedFeatures.isEmpty()
+ && !parents.containsValue(includedFeatures))
+ {
+ parents.put(term, includedFeatures);
+ }
+ candidates.addAll(so.getParents(term));
+ }
+
+ return parents;
+ }
+}
--- /dev/null
+package jalview.datamodel.ontology;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public interface OntologyI
+{
+ /**
+ * Answers true if the term can be identified in the ontology (possibly by id,
+ * description or alias), else false
+ *
+ * @param term
+ * @return
+ */
+ boolean isValidTerm(String term);
+
+ /**
+ * Answers true if <code>childTerm</code> is the same as, or a sub-type
+ * (specialisation of) <code>parentTerm</code>, else false
+ *
+ * @param childTerm
+ * @param parentTerm
+ * @return
+ */
+ boolean isA(String childTerm, String parentTerm);
+
+ /**
+ * Answers those terms in the given set which are not child terms of some
+ * other term in the set. That is, returns a set of parent terms. The input
+ * set is not modified.
+ *
+ * @param terms
+ * @return
+ */
+ Set<String> getParentTerms(Set<String> terms);
+
+ /**
+ * Answers a (possibly empty) list of those terms in the supplied list which
+ * are a child (directly or indirectly) of <code>parent</code>. The parent
+ * term itself is not included (even if in the input list)
+ *
+ * @param parent
+ * @param terms
+ * @return
+ */
+ List<String> getChildTerms(String parent, List<String> terms);
+
+ /**
+ * Answers a (possibly empty) list of the immediate parent terms of the given
+ * term
+ *
+ * @param term
+ * @return
+ */
+ List<String> getParents(String term);
+
+ /**
+ * Returns a sorted list of all valid terms queried for (i.e. terms processed
+ * which were valid in the SO), using the friendly description.
+ *
+ * This can be used to check that any hard-coded stand-in for the full SO
+ * includes all the terms needed for correct processing.
+ *
+ * @return
+ */
+ List<String> termsFound();
+
+ /**
+ * Returns a sorted list of all invalid terms queried for (i.e. terms
+ * processed which were not found in the SO), using the friendly description.
+ *
+ * This can be used to report any 'non-compliance' in data, and/or to report
+ * valid terms missing from any hard-coded stand-in for the full SO.
+ *
+ * @return
+ */
+ List<String> termsNotFound();
+
+ /**
+ * Answers the top level parent terms (normally only one) for the given term,
+ * that is, those that have no parent themselves. Answers null if {@code term}
+ * is not a sequence ontology term. Answers a list just containing
+ * {@code term} if it is a valid term with no parent.
+ *
+ * @param term
+ * @return
+ */
+ List<String> getRootParents(String term);
+
+ /**
+ * Answers a (possibly empty) map of any Ontology terms (from the given term
+ * and its parents) which subsume one or more of the target terms. The map key
+ * is an ontology term, and the entry is the list of target terms that are
+ * sub-terms of the key.
+ * <p>
+ * For example if {@code stop_gained} and {@code stop_lost} are known feature
+ * types, then SO term {@ nonsynonymous_variant} is the first common parent of
+ * both terms
+ *
+ * @param givenTerm
+ * the term to search from
+ * @param targetTerms
+ * candidate terms to 'capture' in ontology groupings
+ * @return
+ */
+ Map<String, List<String>> findSequenceOntologyGroupings(String givenTerm,
+ List<String> targetTerms);
+}
\ No newline at end of file
*/
package jalview.ext.so;
+import jalview.bin.Cache;
+import jalview.datamodel.ontology.OntologyBase;
import jalview.io.gff.SequenceOntologyI;
import java.io.BufferedInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.biojava.nbio.ontology.Ontology;
+import org.biojava.nbio.ontology.Synonym;
import org.biojava.nbio.ontology.Term;
import org.biojava.nbio.ontology.Term.Impl;
import org.biojava.nbio.ontology.Triple;
* A wrapper class that parses the Sequence Ontology and exposes useful access
* methods. This version uses the BioJava parser.
*/
-public class SequenceOntology implements SequenceOntologyI
+public class SequenceOntology extends OntologyBase
+ implements SequenceOntologyI
{
/*
* the parsed Ontology data as modelled by BioJava
/*
* lookup of terms by user readable name (NB not guaranteed unique)
*/
- private Map<String, Term> termsByDescription;
+ private Map<String, Term> aliases;
/*
* Map where key is a Term and value is a (possibly empty) list of
*/
public SequenceOntology()
{
- termsFound = new ArrayList<String>();
- termsNotFound = new ArrayList<String>();
- termsByDescription = new HashMap<String, Term>();
- termIsA = new HashMap<Term, List<Term>>();
+ termsFound = new ArrayList<>();
+ termsNotFound = new ArrayList<>();
+ aliases = new HashMap<>();
+ termIsA = new HashMap<>();
- loadOntologyZipFile("so-xp-simple.obo");
+ loadOntologyZipFile("so-simple.obo");
}
/**
OboParser parser = new OboParser();
ontology = parser.parseOBO(oboFile, "SO", "the SO ontology");
isA = ontology.getTerm("is_a");
- storeTermNames();
+ storeTermAliases();
}
/**
- * Stores a lookup table of terms by description. Note that description is not
- * guaranteed unique. Where duplicate descriptions are found, try to discard
- * the term that is flagged as obsolete. However we do store obsolete terms
- * where there is no duplication of description.
+ * Stores a lookup table of terms by description or synonym. Note that
+ * description is not guaranteed unique. Where duplicate descriptions are
+ * found, try to discard the term that is flagged as obsolete. However we do
+ * store obsolete terms where there is no duplication of description.
*/
- protected void storeTermNames()
+ protected void storeTermAliases()
{
+ Set<String> ambiguous = new HashSet<>();
+
for (Term term : ontology.getTerms())
{
if (term instanceof Impl)
{
+ boolean newTermIsObsolete = isObsolete(term);
String description = term.getDescription();
if (description != null)
{
- Term replaced = termsByDescription.get(description);
+ description = canonicalise(description);
+ Term replaced = aliases.get(description);
if (replaced != null)
{
- boolean newTermIsObsolete = isObsolete(term);
boolean oldTermIsObsolete = isObsolete(replaced);
if (newTermIsObsolete && !oldTermIsObsolete)
{
- System.err.println("Ignoring " + term.getName()
+ Cache.log.debug("SequenceOntology ignoring " + term.getName()
+ " as obsolete and duplicated by "
+ replaced.getName());
term = replaced;
}
else if (!newTermIsObsolete && oldTermIsObsolete)
{
- System.err.println("Ignoring " + replaced.getName()
+ Cache.log.debug("SequenceOntology ignoring "
+ + replaced.getName()
+ " as obsolete and duplicated by " + term.getName());
}
else
{
- System.err.println("Warning: " + term.getName()
+ Cache.log.debug("SequenceOntology warning: " + term.getName()
+ " has replaced " + replaced.getName()
+ " for lookup of '" + description + "'");
}
}
- termsByDescription.put(description, term);
+ aliases.put(description, term);
+
+ /*
+ * also store synonyms if not ambiguous
+ */
+ if (!newTermIsObsolete)
+ {
+ storeSynonymsForTerm(term, ambiguous);
+ }
+ }
+ }
+ }
+
+ /*
+ * remove ambiguous synonyms for safety;
+ * problem: what if a synonym matches a description?
+ * only one case found:
+ * nmd_transcript is synonym for SO:0001621:NMD_transcript_variant
+ * and also the description for SO:0002114:NMD_transcript
+ */
+ for (String syn : ambiguous)
+ {
+ aliases.remove(syn);
+ }
+ }
+
+ /**
+ * Stores any synonyms as an alternative lookup for the term, canonicalised
+ * for case/hyphen/space insensitivity on lookup.
+ * <p>
+ * Some synonyms may be ambiguous (present for more than one term), and these
+ * are handled as follows:
+ * <ul>
+ * <li>if a synonym matches the <em>description</em> of another term, it is
+ * not saved, so that a term can always be found by description
+ * <ul>
+ * <li>Example: {@code nmd_transcript} is the description for
+ * {@code NMD_transcript} and also a synonym for
+ * {@code NMD_transcript_variant} - the synonym is ignored</li>
+ * </ul>
+ * </li>
+ * <li>if one term is a sub-term (directly or indirectly) of the other, the
+ * synonym is retained for the more general term
+ * <ul>
+ * <li>Example: {@code helix} is a synonym for
+ * {@code alpha_helix, right_handed_peptide_helix, peptide_helix} - it is kept
+ * for {@code peptide_helix} as this is a parent of the other terms</li>
+ * </ul>
+ * </li>
+ * <li>otherwise the synonym is added to the {@code ambiguous} list for
+ * removal
+ * <ul>
+ * <li>Example: {@code sequence variation} is a synonym for
+ * {@code sequence_alteration} and {@code alternate_sequence_site} but these
+ * have no {@code isA} relationship - the synonym is ignored as ambiguous</li>
+ * </ul>
+ * </ul>
+ *
+ * @param term
+ * @param ambiguous
+ */
+ void storeSynonymsForTerm(Term term, Set<String> ambiguous)
+ {
+ for (Object syn : term.getSynonyms())
+ {
+ String name = ((Synonym) syn).getName();
+ String synonym = canonicalise(name);
+ if (aliases.containsKey(synonym))
+ {
+ final Term found = aliases.get(synonym);
+ if (found != term)
+ {
+ /*
+ * this alias is ambiguous - matches description,
+ * or an alias, of another term
+ */
+ String msg = String.format(
+ "SequenceOntology ambiguous synonym %s for '%s:%s' and '%s:%s'",
+ synonym, term.getName(), term.getDescription(),
+ found.getName(), found.getDescription());
+ Cache.log.debug(msg);
+
+ /*
+ * preserve any entry whose canonical description happens to match
+ * a synonym (NMD_transcript is a valid description, and also
+ * a synonym for NMD_transcript_variant)
+ * also preserve a parent (more general) term
+ */
+ if (synonym.equals(canonicalise(found.getDescription()))
+ || termIsA(term, found))
+ {
+ // leave it alone
+ }
+ /*
+ * replace a specialised term with a more general one
+ * with the same alias
+ */
+ // else if
+ // (synonym.equals(canonicalise(term.getDescription())))
+ else if (termIsA(found, term))
+ {
+ aliases.put(synonym, term);
+ }
+ else
+ {
+ ambiguous.add(synonym);
+ }
}
}
+ else
+ {
+ aliases.put(synonym, term);
+ }
}
}
/**
+ * Converts a string to lower case and changes hyphens and spaces to
+ * underscores
+ *
+ * @param s
+ * @return
+ */
+ static String canonicalise(String s)
+ {
+ return s == null ? null
+ : s.toLowerCase().replace('-', '_').replace(' ', '_');
+ }
+
+ /**
* Answers true if the term has property "is_obsolete" with value true, else
* false
*
{
if (!termsNotFound.contains(term))
{
- System.err.println("SO term " + term + " invalid");
+ Cache.log.debug("SequenceOntology term " + term + " invalid");
termsNotFound.add(term);
}
}
*/
protected synchronized void findParents(Term childTerm)
{
- List<Term> result = new ArrayList<Term>();
+ List<Term> result = new ArrayList<>();
for (Triple triple : ontology.getTriples(childTerm, null, isA))
{
Term parent = triple.getObject();
/**
* Returns the Term for a given name (e.g. "SO:0000735") or description (e.g.
- * "sequence_location"), or null if not found.
+ * "sequence_location"), or alias, or null if not found
*
* @param child
* @return
*/
- protected Term getTerm(String nameOrDescription)
+ protected Term getTerm(final String nameOrDescription)
{
- Term t = termsByDescription.get(nameOrDescription);
+ if (nameOrDescription == null)
+ {
+ return null;
+ }
+ Term t = aliases.get(canonicalise(nameOrDescription));
if (t == null)
{
try
return termsNotFound;
}
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalStateException
+ * if a loop is detected in the ontology
+ */
+ @Override
+ public List<String> getRootParents(final String term)
+ {
+ /*
+ * check in cache first
+ */
+ if (rootParents.containsKey(term))
+ {
+ return rootParents.get(term);
+ }
+ Term t = getTerm(term);
+ if (t == null)
+ {
+ return null;
+ }
+
+ /*
+ * todo: check for loops using 'seen', allowing for alternate paths e.g.
+ * stop_gained isA feature_truncation isA feature_variant
+ * " isA nonsynonymous_variant ... isA geneVariant isA feature_variant
+ */
+ List<Term> seen = new ArrayList<>();
+ List<Term> top = new ArrayList<>();
+ List<Term> query = new ArrayList<>();
+ query.add(t);
+
+ while (!query.isEmpty())
+ {
+ List<Term> nextQuery = new ArrayList<>();
+ for (Term q : query)
+ {
+ Set<Triple> parents = ontology.getTriples(q, null, isA);
+ if (parents.isEmpty())
+ {
+ /*
+ * q has no parents so is a top level term
+ */
+ top.add(q);
+ }
+ else
+ {
+ /*
+ * search all parent terms
+ */
+ for (Triple triple : parents)
+ {
+ Term parent = triple.getObject();
+ nextQuery.add(parent);
+ }
+ }
+ }
+ query = nextQuery;
+ }
+
+ List<String> result = new ArrayList<>();
+ for (Term found : top)
+ {
+ String desc = found.getDescription();
+ if (!result.contains(desc))
+ {
+ result.add(desc);
+ }
+ }
+
+ /*
+ * save result in cache
+ */
+ rootParents.put(term, result);
+
+ return result;
+ }
+
+ @Override
+ public List<String> getParents(String term)
+ {
+ List<String> parents = new ArrayList<>();
+ Term t = getTerm(term);
+ if (t != null)
+ {
+ for (Triple triple : ontology.getTriples(t, null, isA))
+ {
+ Term parent = triple.getObject();
+ parents.add(parent.getDescription());
+ }
+ }
+ return parents;
+ }
+
+ @Override
+ public boolean isValidTerm(String term)
+ {
+ return getTerm(term) != null;
+ }
}
}
/**
- * Hides columns containing (or not containing) a specified feature, provided
- * that would not leave all columns hidden
+ * Hides columns containing (or not containing) the specified feature(s),
+ * provided that would not leave all columns hidden
*
- * @param featureType
* @param columnsContaining
+ * @param featureTypes
+ *
* @return
*/
- public boolean hideFeatureColumns(String featureType,
- boolean columnsContaining)
+ public boolean hideFeatureColumns(boolean columnsContaining,
+ String... featureTypes)
{
boolean notForHiding = avc.markColumnsContainingFeatures(
- columnsContaining, false, false, featureType);
+ columnsContaining, false, false, featureTypes);
if (notForHiding)
{
if (avc.markColumnsContainingFeatures(!columnsContaining, false,
- false, featureType))
+ false, featureTypes))
{
getViewport().hideSelectedColumns();
return true;
AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
if (Cache.getDefault("HIDE_INTRONS", true))
{
- newFrame.hideFeatureColumns(SequenceOntologyI.EXON, false);
+ newFrame.hideFeatureColumns(false, SequenceOntologyI.EXON);
}
String newtitle = String.format("%s %s %s",
dna ? MessageManager.getString("label.proteins")
import jalview.datamodel.features.FeatureMatcherI;
import jalview.datamodel.features.FeatureMatcherSet;
import jalview.datamodel.features.FeatureMatcherSetI;
+import jalview.datamodel.ontology.OntologyI;
import jalview.gui.Help.HelpId;
import jalview.io.JalviewFileChooser;
import jalview.io.JalviewFileView;
+import jalview.io.gff.SequenceOntologyFactory;
import jalview.schemes.FeatureColour;
import jalview.util.MessageManager;
import jalview.util.Platform;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import javax.swing.JSlider;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
+import javax.swing.RowFilter;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
import javax.swing.event.ChangeEvent;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
+import javax.swing.table.TableRowSorter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
public class FeatureSettings extends JPanel
implements FeatureSettingsControllerI
{
+ private static final Font VERDANA_12 = new Font("Verdana", Font.PLAIN, 12);
+
private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
.getString("label.sequence_feature_colours");
*/
Map<String, float[]> typeWidth = null;
+ /*
+ * if true, 'child' feature types are not displayed
+ */
+ JCheckBox summaryView;
+
+ /*
+ * those feature types that do not have a parent feature type present
+ * (as determined by an Ontology relationship)
+ */
+ List<String> topLevelTypes;
+
/**
* Constructor
*
originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
+ topLevelTypes = new ArrayList<>();
+
try
{
jbInit();
ex.printStackTrace();
}
+ initTable();
+
+ scrollPane.setViewportView(table);
+
+ if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
+ {
+ fr.findAllFeatures(true); // display everything!
+ }
+
+ discoverAllFeatureData();
+ final PropertyChangeListener change;
+ final FeatureSettings fs = this;
+ fr.addPropertyChangeListener(change = new PropertyChangeListener()
+ {
+ @Override
+ public void propertyChange(PropertyChangeEvent evt)
+ {
+ if (!fs.resettingTable && !fs.handlingUpdate)
+ {
+ fs.handlingUpdate = true;
+ fs.resetTable(null);
+ // new groups may be added with new sequence feature types only
+ fs.handlingUpdate = false;
+ }
+ }
+ });
+
+ frame = new JInternalFrame();
+ frame.setContentPane(this);
+ if (Platform.isAMac())
+ {
+ Desktop.addInternalFrame(frame,
+ MessageManager.getString("label.sequence_feature_settings"),
+ 600, 480);
+ }
+ else
+ {
+ Desktop.addInternalFrame(frame,
+ MessageManager.getString("label.sequence_feature_settings"),
+ 600, 450);
+ }
+ frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
+
+ frame.addInternalFrameListener(
+ new javax.swing.event.InternalFrameAdapter()
+ {
+ @Override
+ public void internalFrameClosed(
+ javax.swing.event.InternalFrameEvent evt)
+ {
+ fr.removePropertyChangeListener(change);
+ };
+ });
+ frame.setLayer(JLayeredPane.PALETTE_LAYER);
+ inConstruction = false;
+ }
+
+ /**
+ * Constructs and configures the JTable which displays columns of data for
+ * each feature type
+ */
+ protected void initTable()
+ {
table = new JTable()
{
@Override
String tip = null;
int column = table.columnAtPoint(e.getPoint());
int row = table.rowAtPoint(e.getPoint());
-
switch (column)
{
case TYPE_COLUMN:
- tip = JvSwingUtils.wrapTooltip(true, MessageManager
+ /*
+ * drag to reorder not enabled in Summary View
+ */
+ tip = summaryView.isSelected()
+ ? MessageManager.getString(
+ "label.feature_settings_select_columns")
+ : JvSwingUtils.wrapTooltip(true, MessageManager
.getString("label.feature_settings_click_drag"));
break;
case COLOUR_COLUMN:
default:
break;
}
-
return tip;
}
return loc;
}
};
+
JTableHeader tableHeader = table.getTableHeader();
- tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
+ tableHeader.setFont(VERDANA_12);
tableHeader.setReorderingAllowed(false);
- table.setFont(new Font("Verdana", Font.PLAIN, 12));
+ table.setFont(VERDANA_12);
table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
if (evt.isPopupTrigger())
{
- Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
- popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
- evt.getY());
+ popupMenu(selectedRow, type, evt.getX(), evt.getY());
}
else if (evt.getClickCount() == 2)
{
boolean invertSelection = evt.isAltDown();
boolean toggleSelection = Platform.isControlDown(evt);
boolean extendSelection = evt.isShiftDown();
+ String[] terms = getTermsInScope(type);
fr.ap.alignFrame.avc.markColumnsContainingFeatures(
- invertSelection, extendSelection, toggleSelection, type);
+ invertSelection, extendSelection, toggleSelection, terms);
}
}
if (evt.isPopupTrigger())
{
String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
- Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
- popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
- evt.getY());
+ popupMenu(selectedRow, type, evt.getX(), evt.getY());
}
}
});
public void mouseDragged(MouseEvent evt)
{
int newRow = table.rowAtPoint(evt.getPoint());
- if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
- {
- /*
- * reposition 'selectedRow' to 'newRow' (the dragged to location)
- * this could be more than one row away for a very fast drag action
- * so just swap it with adjacent rows until we get it there
- */
- Object[][] data = ((FeatureTableModel) table.getModel())
- .getData();
- int direction = newRow < selectedRow ? -1 : 1;
- for (int i = selectedRow; i != newRow; i += direction)
- {
- Object[] temp = data[i];
- data[i] = data[i + direction];
- data[i + direction] = temp;
- }
- updateFeatureRenderer(data);
- table.repaint();
- selectedRow = newRow;
- }
+ dragRow(newRow);
}
});
- // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
- // MessageManager.getString("label.feature_settings_click_drag")));
- scrollPane.setViewportView(table);
+ }
- if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
+ /**
+ * Answers an array consisting of the given type, and also (if 'Summary View'
+ * is selected), any feature types which are child terms of it in the Sequence
+ * Ontology
+ *
+ * @param type
+ * @return
+ */
+ protected String[] getTermsInScope(String type)
+ {
+ if (!summaryView.isSelected())
{
- fr.findAllFeatures(true); // display everything!
+ return new String[] { type };
}
- discoverAllFeatureData();
- final PropertyChangeListener change;
- final FeatureSettings fs = this;
- fr.addPropertyChangeListener(change = new PropertyChangeListener()
- {
- @Override
- public void propertyChange(PropertyChangeEvent evt)
- {
- if (!fs.resettingTable && !fs.handlingUpdate)
- {
- fs.handlingUpdate = true;
- fs.resetTable(null);
- // new groups may be added with new sequence feature types only
- fs.handlingUpdate = false;
- }
- }
+ List<String> terms = new ArrayList<>();
+ terms.add(type);
- });
+ OntologyI so = SequenceOntologyFactory.getInstance();
- frame = new JInternalFrame();
- frame.setContentPane(this);
- if (Platform.isAMac())
- {
- Desktop.addInternalFrame(frame,
- MessageManager.getString("label.sequence_feature_settings"),
- 600, 480);
- }
- else
+ Object[][] data = ((FeatureTableModel) table.getModel()).getData();
+ for (Object[] row : data)
{
- Desktop.addInternalFrame(frame,
- MessageManager.getString("label.sequence_feature_settings"),
- 600, 450);
+ String type2 = (String) row[TYPE_COLUMN];
+ if (!type2.equals(type) && so.isA(type2, type))
+ {
+ terms.add(type2);
+ }
}
- frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
-
- frame.addInternalFrameListener(
- new javax.swing.event.InternalFrameAdapter()
- {
- @Override
- public void internalFrameClosed(
- javax.swing.event.InternalFrameEvent evt)
- {
- fr.removePropertyChangeListener(change);
- };
- });
- frame.setLayer(JLayeredPane.PALETTE_LAYER);
- inConstruction = false;
+ return terms.toArray(new String[terms.size()]);
}
- protected void popupSort(final int rowSelected, final String type,
- final Object typeCol, final Map<String, float[][]> minmax, int x,
+ protected void popupMenu(final int rowSelected, final String type, int x,
int y)
{
- final FeatureColourI featureColour = (FeatureColourI) typeCol;
-
JPopupMenu men = new JPopupMenu(MessageManager
.formatMessage("label.settings_for_param", new String[]
{ type }));
final FeatureSettings me = this;
scr.addActionListener(new ActionListener()
{
-
@Override
public void actionPerformed(ActionEvent e)
{
- me.af.avc
- .sortAlignmentByFeatureScore(Arrays.asList(new String[]
- { type }));
+ String[] types = getTermsInScope(type);
+ me.af.avc.sortAlignmentByFeatureScore(Arrays.asList(types));
}
-
});
JMenuItem dens = new JMenuItem(
MessageManager.getString("label.sort_by_density"));
dens.addActionListener(new ActionListener()
{
-
@Override
public void actionPerformed(ActionEvent e)
{
- me.af.avc
- .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
- { type }));
+ String[] types = getTermsInScope(type);
+ me.af.avc.sortAlignmentByFeatureDensity(Arrays.asList(types));
}
-
});
men.add(dens);
@Override
public void actionPerformed(ActionEvent arg0)
{
+ String[] types = getTermsInScope(type);
fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
- false, type);
+ false, types);
}
});
JMenuItem clearCols = new JMenuItem(MessageManager
@Override
public void actionPerformed(ActionEvent arg0)
{
+ String[] types = getTermsInScope(type);
fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
- false, type);
+ false, types);
}
});
JMenuItem hideCols = new JMenuItem(
@Override
public void actionPerformed(ActionEvent arg0)
{
- fr.ap.alignFrame.hideFeatureColumns(type, true);
+ String[] types = getTermsInScope(type);
+ fr.ap.alignFrame.hideFeatureColumns(true, types);
}
});
JMenuItem hideOtherCols = new JMenuItem(
@Override
public void actionPerformed(ActionEvent arg0)
{
- fr.ap.alignFrame.hideFeatureColumns(type, false);
+ String[] types = getTermsInScope(type);
+ fr.ap.alignFrame.hideFeatureColumns(false, types);
}
});
men.add(selCols);
*/
Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
visibleGroups.toArray(new String[visibleGroups.size()]));
+
for (String type : types)
{
displayableTypes.add(type);
}
}
+ /*
+ * enable 'Summary View' if some types are sub-types of others
+ */
+ Set<String> parents = SequenceOntologyFactory.getInstance()
+ .getParentTerms(displayableTypes);
+ summaryView.setEnabled(parents.size() < displayableTypes.size());
+
Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
int dataIndex = 0;
{
continue;
}
-
data[dataIndex][TYPE_COLUMN] = type;
data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
updateOriginalData(data);
}
- table.setModel(new FeatureTableModel(data));
+ /*
+ * recreate the table model
+ */
+ FeatureTableModel dataModel = new FeatureTableModel(data);
+ table.setModel(dataModel);
+
+ /*
+ * we want to be able to filter out rows for sub-types, but not to sort
+ * rows, so have to add a RowFilter to a disabled TableRowSorter (!)
+ */
+ final TableRowSorter<FeatureTableModel> sorter = new TableRowSorter<>(
+ dataModel);
+ for (int i = 0; i < table.getColumnCount(); i++)
+ {
+ sorter.setSortable(i, false);
+ }
+
+ /*
+ * filter rows to only top-level Ontology types if requested
+ */
+ sorter.setRowFilter(new RowFilter<FeatureTableModel, Integer>()
+ {
+ @Override
+ public boolean include(
+ Entry<? extends FeatureTableModel, ? extends Integer> entry)
+ {
+ if (!summaryView.isSelected())
+ {
+ return true;
+ }
+ int row = entry.getIdentifier(); // this is model, not view, row number
+ String featureType = (String) entry.getModel().getData()[row][TYPE_COLUMN];
+ return parents.contains(featureType);
+ }
+ });
+ table.setRowSorter(sorter);
+
table.getColumnModel().getColumn(0).setPreferredWidth(200);
groupPanel.setLayout(
}
});
+ summaryView = new JCheckBox(
+ MessageManager.getString("label.summary_view"));
+ summaryView
+ .setToolTipText(
+ MessageManager.getString("label.group_by_so"));
+ summaryView.addActionListener(new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ resetTable(null);
+ }
+ });
+
transparency.setMaximum(70);
transparency.setToolTipText(
MessageManager.getString("label.transparency_tip"));
- JPanel transPanel = new JPanel(new GridLayout(1, 2));
- bigPanel.add(transPanel, BorderLayout.SOUTH);
+ JPanel lowerPanel = new JPanel(new GridLayout(1, 2));
+ bigPanel.add(lowerPanel, BorderLayout.SOUTH);
JPanel transbuttons = new JPanel(new GridLayout(5, 1));
transbuttons.add(optimizeOrder);
transbuttons.add(sortByScore);
transbuttons.add(sortByDens);
transbuttons.add(help);
+ JPanel transPanel = new JPanel(new GridLayout(3, 1));
+ transPanel.add(summaryView);
+ transPanel.add(new JLabel(" Colour transparency" + ":"));
transPanel.add(transparency);
- transPanel.add(transbuttons);
+ lowerPanel.add(transPanel);
+ lowerPanel.add(transbuttons);
JPanel buttonPanel = new JPanel();
buttonPanel.add(ok);
}
/**
+ * Reorders features by 'dragging' selectedRow to 'newRow'
+ *
+ * @param newRow
+ */
+ protected void dragRow(int newRow)
+ {
+ if (summaryView.isSelected())
+ {
+ // no drag while in summary view
+ return;
+ }
+
+ if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
+ {
+ /*
+ * reposition 'selectedRow' to 'newRow' (the dragged to location)
+ * this could be more than one row away for a very fast drag action
+ * so just swap it with adjacent rows until we get it there
+ */
+ Object[][] data = ((FeatureTableModel) table.getModel())
+ .getData();
+ int direction = newRow < selectedRow ? -1 : 1;
+ for (int i = selectedRow; i != newRow; i += direction)
+ {
+ Object[] temp = data[i];
+ data[i] = data[i + direction];
+ data[i + direction] = temp;
+ }
+ updateFeatureRenderer(data);
+ table.repaint();
+ selectedRow = newRow;
+ }
+ }
+
+ protected void refreshTable()
+ {
+ Object[][] data = ((FeatureTableModel) table.getModel()).getData();
+ for (Object[] row : data)
+ {
+ String type = (String) row[TYPE_COLUMN];
+ FeatureColourI colour = fr.getFeatureColours().get(type);
+ FeatureMatcherSetI filter = fr.getFeatureFilter(type);
+ if (filter == null)
+ {
+ filter = new FeatureMatcherSet();
+ }
+ row[COLOUR_COLUMN] = colour;
+ row[FILTER_COLUMN] = filter;
+ }
+ repaint();
+ }
+
+ /*
* Answers a suitable tooltip to show on the colour cell of the table
*
* @param fcol
}
/**
- * Answers the class of the object in column c of the first row of the table
+ * Answers the class of column c of the table
*/
@Override
public Class<?> getColumnClass(int c)
{
- Object v = getValueAt(0, c);
- return v == null ? null : v.getClass();
+ switch (c)
+ {
+ case TYPE_COLUMN:
+ return String.class;
+ case COLOUR_COLUMN:
+ return FeatureColour.class;
+ case FILTER_COLUMN:
+ return FeatureMatcherSet.class;
+ default:
+ return Boolean.class;
+ }
}
+ /**
+ * Answers true for all columns except Feature Type
+ */
@Override
public boolean isCellEditable(int row, int col)
{
- return col == 0 ? false : true;
+ return col != TYPE_COLUMN;
}
+ /**
+ * Sets the value in the model for a given row and column. If Visibility
+ * (Show/Hide) is being set, and the table is in Summary View, then it is
+ * set also on any sub-types of the row's feature type.
+ */
@Override
public void setValueAt(Object value, int row, int col)
{
data[row][col] = value;
fireTableCellUpdated(row, col);
+ if (summaryView.isSelected() && col == SHOW_COLUMN)
+ {
+ setSubtypesVisibility(row, (Boolean) value);
+ }
updateFeatureRenderer(data);
}
+ /**
+ * Sets the visibility of any feature types which are sub-types of the type
+ * in the given row of the table
+ *
+ * @param row
+ * @param value
+ */
+ protected void setSubtypesVisibility(int row, Boolean value)
+ {
+ String type = (String) data[row][TYPE_COLUMN];
+ OntologyI so = SequenceOntologyFactory.getInstance();
+
+ for (int r = 0; r < data.length; r++)
+ {
+ if (r != row)
+ {
+ String type2 = (String) data[r][TYPE_COLUMN];
+ if (so.isA(type2, type))
+ {
+ data[r][SHOW_COLUMN] = value;
+ fireTableCellUpdated(r, SHOW_COLUMN);
+ }
+ }
+ }
+ }
}
class ColorRenderer extends JLabel implements TableCellRenderer
String type;
- JButton button;
+ JButton colourButton;
JColorChooser colorChooser;
// which is a button.
// This button brings up the color chooser dialog,
// which is the editor from the user's point of view.
- button = new JButton();
- button.setActionCommand(EDIT);
- button.addActionListener(this);
- button.setBorderPainted(false);
+ colourButton = new JButton();
+ colourButton.setActionCommand(EDIT);
+ colourButton.addActionListener(this);
+ colourButton.setBorderPainted(false);
// Set up the dialog that the button brings up.
colorChooser = new JColorChooser();
- dialog = JColorChooser.createDialog(button,
+ dialog = JColorChooser.createDialog(colourButton,
MessageManager.getString("label.select_colour"), true, // modal
colorChooser, this, // OK button handler
null); // no CANCEL button handler
if (currentColor.isSimpleColour())
{
// bring up simple color chooser
- button.setBackground(currentColor.getColour());
+ colourButton.setBackground(currentColor.getColour());
colorChooser.setColor(currentColor.getColour());
dialog.setVisible(true);
}
{
// bring up graduated chooser.
chooser = new FeatureTypeSettings(me.fr, type);
- /**
- * @j2sNative
- */
- {
- chooser.setRequestFocusEnabled(true);
- chooser.requestFocus();
- }
+ chooser.setRequestFocusEnabled(true);
+ chooser.requestFocus();
chooser.addActionListener(this);
// Make the renderer reappear.
fireEditingStopped();
* (or filters!) are already set in FeatureRenderer, so just
* update table data without triggering updateFeatureRenderer
*/
- currentColor = fr.getFeatureColours().get(type);
- FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
- if (currentFilter == null)
- {
- currentFilter = new FeatureMatcherSet();
- }
- Object[] data = ((FeatureTableModel) table.getModel())
- .getData()[rowSelected];
- data[COLOUR_COLUMN] = currentColor;
- data[FILTER_COLUMN] = currentFilter;
+ refreshTable();
}
fireEditingStopped();
me.table.validate();
currentColor = (FeatureColourI) value;
this.rowSelected = row;
type = me.table.getValueAt(row, TYPE_COLUMN).toString();
- button.setOpaque(true);
- button.setBackground(me.getBackground());
+ colourButton.setOpaque(true);
+ colourButton.setBackground(me.getBackground());
if (!currentColor.isSimpleColour())
{
JLabel btn = new JLabel();
- btn.setSize(button.getSize());
+ btn.setSize(colourButton.getSize());
FeatureSettings.renderGraduatedColor(btn, currentColor);
- button.setBackground(btn.getBackground());
- button.setIcon(btn.getIcon());
- button.setText(btn.getText());
+ colourButton.setBackground(btn.getBackground());
+ colourButton.setIcon(btn.getIcon());
+ colourButton.setText(btn.getText());
}
else
{
- button.setText("");
- button.setIcon(null);
- button.setBackground(currentColor.getColour());
+ colourButton.setText("");
+ colourButton.setIcon(null);
+ colourButton.setBackground(currentColor.getColour());
}
- return button;
+ return colourButton;
}
}
String type;
- JButton button;
+ JButton filterButton;
protected static final String EDIT = "edit";
public FilterEditor(FeatureSettings me)
{
this.me = me;
- button = new JButton();
- button.setActionCommand(EDIT);
- button.addActionListener(this);
- button.setBorderPainted(false);
+ filterButton = new JButton();
+ filterButton.setActionCommand(EDIT);
+ filterButton.addActionListener(this);
+ filterButton.setBorderPainted(false);
}
/**
@Override
public void actionPerformed(ActionEvent e)
{
- if (button == e.getSource())
+ if (filterButton == e.getSource())
{
FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
chooser.addActionListener(this);
}
else if (e.getSource() instanceof Component)
{
-
/*
* after OK in variable colour dialog, any changes to filter
* (or colours!) are already set in FeatureRenderer, so just
* update table data without triggering updateFeatureRenderer
*/
- FeatureColourI currentColor = fr.getFeatureColours().get(type);
- currentFilter = me.fr.getFeatureFilter(type);
- if (currentFilter == null)
- {
- currentFilter = new FeatureMatcherSet();
- }
- Object[] data = ((FeatureTableModel) table.getModel())
- .getData()[rowSelected];
- data[COLOUR_COLUMN] = currentColor;
- data[FILTER_COLUMN] = currentFilter;
+ refreshTable();
fireEditingStopped();
me.table.validate();
}
currentFilter = (FeatureMatcherSetI) value;
this.rowSelected = row;
type = me.table.getValueAt(row, TYPE_COLUMN).toString();
- button.setOpaque(true);
- button.setBackground(me.getBackground());
- button.setText(currentFilter.toString());
- button.setIcon(null);
- return button;
+ filterButton.setOpaque(true);
+ filterButton.setBackground(me.getBackground());
+ filterButton.setText(currentFilter.toString());
+ filterButton.setToolTipText(currentFilter.toString());
+ filterButton.setIcon(null);
+ return filterButton;
}
}
}
class FeatureIcon implements Icon
{
+ private static final Font VERDANA_9 = new Font("Verdana", Font.PLAIN, 9);
+
FeatureColourI gcol;
Color backg;
// need an icon here.
g.setColor(gcol.getMaxColour());
- g.setFont(new Font("Verdana", Font.PLAIN, 9));
+ g.setFont(VERDANA_9);
// g.setFont(g.getFont().deriveFont(
// AffineTransform.getScaleInstance(
import jalview.datamodel.features.FeatureMatcherI;
import jalview.datamodel.features.FeatureMatcherSet;
import jalview.datamodel.features.FeatureMatcherSetI;
+import jalview.io.gff.SequenceOntologyFactory;
+import jalview.io.gff.SequenceOntologyI;
import jalview.schemes.FeatureColour;
import jalview.util.ColorUtils;
import jalview.util.MessageManager;
import java.awt.event.MouseEvent;
import java.text.DecimalFormat;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
/*
* the colour and filters to reset to on Cancel
+ * (including feature sub-types if modified)
*/
- private final FeatureColourI originalColour;
+ private Map<String, FeatureColourI> originalColours;
- private final FeatureMatcherSetI originalFilter;
+ private Map<String, FeatureMatcherSetI> originalFilters;
/*
* set flag to true when setting values programmatically,
private JPanel chooseFiltersPanel;
+ /*
+ * the root Sequence Ontology terms (if any) that is a parent of
+ * the current feature type
+ */
+ private String rootSOTerm;
+
+ /*
+ * a map whose keys are Sequence Ontology terms - selected from the
+ * current term and its parents in the SO - whose subterms include
+ * additional feature types; the map entry is the list of additional
+ * feature types that match the key or have it as a parent term; in
+ * other words, distinct 'aggregations' that include the current feature type
+ */
+ private final Map<String, List<String>> relatedSoTerms;
+
+ /*
+ * if true, filter or colour settings are also applied to
+ * any sub-types of parentTerm in the Sequence Ontology
+ */
+ private boolean applyFiltersToSubtypes;
+
+ private boolean applyColourToSubtypes;
+
+ private String parentSOTerm;
+
/**
* Constructor
*
this.fr = frender;
this.featureType = theType;
ap = fr.ap;
- originalFilter = fr.getFeatureFilter(theType);
- originalColour = fr.getFeatureColours().get(theType);
-
+
+ SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+ relatedSoTerms = so.findSequenceOntologyGroupings(
+ this.featureType, fr.getRenderOrder());
+
+ /*
+ * save original colours and filters for this feature type,
+ * and any related types, to restore on Cancel
+ */
+ originalFilters = new HashMap<>();
+ originalFilters.put(theType, fr.getFeatureFilter(theType));
+ originalColours = new HashMap<>();
+ originalColours.put(theType, fr.getFeatureColours().get(theType));
+ for (List<String> related : relatedSoTerms.values())
+ {
+ for (String type : related)
+ {
+ originalFilters.put(type, fr.getFeatureFilter(type));
+ originalColours.put(type, fr.getFeatureColours().get(type));
+ }
+ }
+
adjusting = true;
-
+
try
{
initialise();
ex.printStackTrace();
return;
}
-
+
updateColoursTab();
-
+
updateFiltersTab();
-
+
adjusting = false;
-
+
colourChanged(false);
-
+
String title = MessageManager
.formatMessage("label.display_settings_for", new String[]
{ theType });
}
/**
+ * Answers a (possibly empty) map of any Sequence Ontology terms (the current
+ * feature type and its parents) which incorporate additional known feature
+ * types (the map entry).
+ * <p>
+ * For example if {@code stop_gained} and {@code stop_lost} are known feature
+ * types, then SO term {@ nonsynonymous_variant} is the first common parent of
+ * both terms
+ *
+ * @param featureType
+ * the current feature type being configured
+ * @param featureTypes
+ * all known feature types on the alignment
+ * @return
+ */
+ protected static Map<String, List<String>> findSequenceOntologyGroupings(
+ String featureType, List<String> featureTypes)
+ {
+ List<String> sortedTypes = new ArrayList<>(featureTypes);
+ Collections.sort(sortedTypes);
+
+ Map<String, List<String>> parents = new HashMap<>();
+
+ /*
+ * method:
+ * walk up featureType and all of its parents
+ * find other feature types which are subsumed by each term
+ * add each distinct aggregation of included feature types to the map
+ */
+ List<String> candidates = new ArrayList<>();
+ SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+ candidates.add(featureType);
+ while (!candidates.isEmpty())
+ {
+ String term = candidates.remove(0);
+ List<String> includedFeatures = new ArrayList<>();
+ for (String type : sortedTypes)
+ {
+ if (!type.equals(featureType) && so.isA(type, term))
+ {
+ includedFeatures.add(type);
+ }
+ }
+ if (!includedFeatures.isEmpty()
+ && !parents.containsValue(includedFeatures))
+ {
+ parents.put(term, includedFeatures);
+ }
+ candidates.addAll(so.getParents(term));
+ }
+
+ return parents;
+ }
+
+ /**
* Configures the widgets on the Colours tab according to the current feature
* colour scheme
*/
* if not set, default max colour to last plain colour,
* and make min colour a pale version of max colour
*/
+ FeatureColourI originalColour = originalColours.get(featureType);
Color max = originalColour.getMaxColour();
if (max == null)
{
MessageManager.getString("action.colour"), true);
/*
+ * option to apply colour to other selected types as well
+ */
+ if (!relatedSoTerms.isEmpty())
+ {
+ applyColourToSubtypes = false;
+ colourByPanel.add(initSubtypesPanel(false));
+ }
+
+ /*
* simple colour radio button and colour picker
*/
JPanel simpleColourPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
singleColour.setFont(JvSwingUtils.getLabelFont());
singleColour.setBorder(BorderFactory.createLineBorder(Color.black));
singleColour.setPreferredSize(new Dimension(40, 20));
- // if (originalColour.isGraduatedColour())
- // {
- // singleColour.setBackground(originalColour.getMaxColour());
- // singleColour.setForeground(originalColour.getMaxColour());
- // }
- // else
- // {
- singleColour.setBackground(originalColour.getColour());
- singleColour.setForeground(originalColour.getColour());
- // }
+ FeatureColourI originalColour = originalColours.get(featureType);
+ singleColour.setBackground(originalColour.getColour());
+ singleColour.setForeground(originalColour.getColour());
+
singleColour.addMouseListener(new MouseAdapter()
{
@Override
return colourByPanel;
}
+ /**
+ * Constructs and returns a panel with the option to apply any changes also to
+ * sub-types of SO terms at or above the feature type
+ *
+ * @return
+ */
+ protected JPanel initSubtypesPanel(final boolean forFilters)
+ {
+ JPanel toSubtypes = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ toSubtypes.setBackground(Color.WHITE);
+
+ /*
+ * checkbox 'apply to sub-types of...'
+ */
+ JCheckBox applyToSubtypesCB = new JCheckBox(MessageManager
+ .formatMessage("label.apply_to_subtypes", rootSOTerm));
+ toSubtypes.add(applyToSubtypesCB);
+ toSubtypes
+ .setToolTipText(MessageManager.getString("label.group_by_so"));
+
+ /*
+ * combobox to choose 'parent' of sub-types
+ */
+ List<String> soTerms = new ArrayList<>();
+ for (String term : relatedSoTerms.keySet())
+ {
+ soTerms.add(term);
+ }
+ // sort from most restrictive to most inclusive
+ Collections.sort(soTerms, new Comparator<String>()
+ {
+ @Override
+ public int compare(String o1, String o2)
+ {
+ return Integer.compare(relatedSoTerms.get(o1).size(),
+ relatedSoTerms.get(o2).size());
+ }
+ });
+ List<String> tooltips = new ArrayList<>();
+ for (String term : soTerms)
+ {
+ tooltips.add(getSOTermsTooltip(relatedSoTerms.get(term)));
+ }
+ JComboBox<String> parentType = JvSwingUtils
+ .buildComboWithTooltips(soTerms, tooltips);
+ toSubtypes.add(parentType);
+
+ /*
+ * on toggle of checkbox, or change of parent SO term,
+ * reset and then reapply filters to the selected scope
+ */
+ final ActionListener action = new ActionListener()
+ {
+ /*
+ * reset and reapply settings on toggle of checkbox
+ */
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ parentSOTerm = (String) parentType.getSelectedItem();
+ if (forFilters)
+ {
+ applyFiltersToSubtypes = applyToSubtypesCB.isSelected();
+ restoreOriginalFilters();
+ filtersChanged();
+ }
+ else
+ {
+ applyColourToSubtypes = applyToSubtypesCB.isSelected();
+ restoreOriginalColours();
+ colourChanged(true);
+ }
+ }
+ };
+ applyToSubtypesCB.addActionListener(action);
+ parentType.addActionListener(action);
+
+ return toSubtypes;
+ }
+
private void showColourChooser(JPanel colourPanel, String key)
{
Color col = JColorChooser.showDialog(this,
FeatureColourI acg = makeColourFromInputs();
/*
- * save the colour, and repaint stuff
+ * save the colour, and set on subtypes if selected
*/
fr.setColour(featureType, acg);
+ if (applyColourToSubtypes)
+ {
+ for (String child : relatedSoTerms.get(parentSOTerm))
+ {
+ fr.setColour(child, acg);
+ }
+ }
+ refreshFeatureSettings();
ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
updateColoursTab();
@Override
protected void raiseClosed()
{
+ refreshFeatureSettings();
+ }
+
+ protected void refreshFeatureSettings()
+ {
if (this.featureSettings != null)
{
- featureSettings.actionPerformed(new ActionEvent(this, 0, "CLOSED"));
+ featureSettings.actionPerformed(new ActionEvent(this, 0, "REFRESH"));
}
}
/**
* Action on Cancel is to restore colour scheme and filters as they were when
- * the dialog was opened
+ * the dialog was opened (including any feature sub-types that may have been
+ * changed)
*/
@Override
public void cancelPressed()
{
- fr.setColour(featureType, originalColour);
- fr.setFeatureFilter(featureType, originalFilter);
+ restoreOriginalColours();
+ restoreOriginalFilters();
ap.paintAlignment(true, true);
}
/**
+ * Restores filters for all feature types to their values when the dialog was
+ * opened
+ */
+ protected void restoreOriginalFilters()
+ {
+ for (Entry<String, FeatureMatcherSetI> entry : originalFilters
+ .entrySet())
+ {
+ fr.setFeatureFilter(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /**
+ * Restores colours for all feature types to their values when the dialog was
+ * opened
+ */
+ protected void restoreOriginalColours()
+ {
+ for (Entry<String, FeatureColourI> entry : originalColours.entrySet())
+ {
+ fr.setColour(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /**
* Action on text entry of a threshold value
*/
protected void thresholdValue_actionPerformed()
*/
adjusting = true;
float f = Float.parseFloat(thresholdValue.getText());
- f = Float.max(f, this.min);
+ f = Float.max(f, this.min);
f = Float.min(f, this.max);
thresholdValue.setText(String.valueOf(f));
slider.setValue((int) (f * scaleFactor));
{
filters = new ArrayList<>();
+ JPanel outerPanel = new JPanel();
+ outerPanel.setLayout(new BoxLayout(outerPanel, BoxLayout.Y_AXIS));
+ outerPanel.setBackground(Color.white);
+
+ /*
+ * option to apply colour to other selected types as well
+ */
+ if (!relatedSoTerms.isEmpty())
+ {
+ applyFiltersToSubtypes = false;
+ outerPanel.add(initSubtypesPanel(true));
+ }
+
JPanel filtersPanel = new JPanel();
filtersPanel.setLayout(new BoxLayout(filtersPanel, BoxLayout.Y_AXIS));
filtersPanel.setBackground(Color.white);
JvSwingUtils.createTitledBorder(filtersPanel,
MessageManager.getString("label.filters"), true);
+ outerPanel.add(filtersPanel);
JPanel andOrPanel = initialiseAndOrPanel();
filtersPanel.add(andOrPanel);
chooseFiltersPanel.setBackground(Color.white);
filtersPanel.add(chooseFiltersPanel);
- return filtersPanel;
+ return outerPanel;
}
/**
*/
private JPanel initialiseAndOrPanel()
{
- JPanel andOrPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ JPanel andOrPanel = new JPanel(new BorderLayout());
andOrPanel.setBackground(Color.white);
+
andFilters = new JRadioButton(MessageManager.getString("label.and"));
orFilters = new JRadioButton(MessageManager.getString("label.or"));
ActionListener actionListener = new ActionListener()
new JLabel(MessageManager.getString("label.join_conditions")));
andOrPanel.add(andFilters);
andOrPanel.add(orFilters);
+
return andOrPanel;
}
/**
+ * Builds a tooltip for the 'Apply also to...' combobox with a list of known
+ * feature types (excluding the current type) which are sub-types of the
+ * selected Sequence Ontology term
+ *
+ * @param
+ * @return
+ */
+ protected String getSOTermsTooltip(List<String> list)
+ {
+ StringBuilder sb = new StringBuilder(20 * relatedSoTerms.size());
+ sb.append(MessageManager.getString("label.apply_also_to"));
+ for (String child : list)
+ {
+ sb.append("<br>").append(child);
+ }
+ String tooltip = JvSwingUtils.wrapTooltip(true, sb.toString());
+ return tooltip;
+ }
+
+ /**
* Refreshes the display to show any filters currently configured for the
* selected feature type (editable, with 'remove' option), plus one extra row
* for adding a condition. This should be called after a filter has been
* (note this might now be an empty filter with no conditions)
*/
fr.setFeatureFilter(featureType, combined.isEmpty() ? null : combined);
+ if (applyFiltersToSubtypes)
+ {
+ for (String child : relatedSoTerms.get(parentSOTerm))
+ {
+ fr.setFeatureFilter(child, combined.isEmpty() ? null : combined);
+ }
+ }
+
+ refreshFeatureSettings();
ap.paintAlignment(true, true);
updateFiltersTab();
}
if (Cache.getDefault("HIDE_INTRONS", true))
{
- af.hideFeatureColumns(SequenceOntologyI.EXON, false);
+ af.hideFeatureColumns(false, SequenceOntologyI.EXON);
}
if (newAlframes != null)
{
*/
package jalview.io.gff;
-import java.util.List;
+import jalview.datamodel.ontology.OntologyI;
-public interface SequenceOntologyI
+public interface SequenceOntologyI extends OntologyI
{
/*
* selected commonly used values for quick reference
// SO:0000704
public static final String GENE = "gene";
-
- public boolean isA(String childTerm, String parentTerm);
-
- /**
- * Returns a sorted list of all valid terms queried for (i.e. terms processed
- * which were valid in the SO), using the friendly description.
- *
- * This can be used to check that any hard-coded stand-in for the full SO
- * includes all the terms needed for correct processing.
- *
- * @return
- */
- public List<String> termsFound();
-
- /**
- * Returns a sorted list of all invalid terms queried for (i.e. terms
- * processed which were not found in the SO), using the friendly description.
- *
- * This can be used to report any 'non-compliance' in data, and/or to report
- * valid terms missing from any hard-coded stand-in for the full SO.
- *
- * @return
- */
- public List<String> termsNotFound();
}
*/
package jalview.io.gff;
+import jalview.datamodel.ontology.OntologyBase;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
* @author gmcarstairs
*
*/
-public class SequenceOntologyLite implements SequenceOntologyI
+public class SequenceOntologyLite extends OntologyBase
+ implements SequenceOntologyI
{
/*
* initial selection of types of interest when processing Ensembl features
{ "sequence_variant", "sequence_variant" },
{ "structural_variant", "sequence_variant" },
{ "feature_variant", "sequence_variant" },
+ { "upstream_gene_variant", "sequence_variant" },
{ "gene_variant", "sequence_variant" },
{ "transcript_variant", "sequence_variant" },
+ { "non_coding_transcript_variant", "sequence_variant" },
+ { "non_coding_transcript_exon_variant", "sequence_variant" },
// NB Ensembl uses NMD_transcript_variant as if a 'transcript'
// but we model it here correctly as per the SO
{ "NMD_transcript_variant", "sequence_variant" },
return termsNotFound;
}
}
+
+ @Override
+ public List<String> getRootParents(final String term)
+ {
+ /*
+ * check in cache first
+ */
+ if (rootParents.containsKey(term))
+ {
+ return rootParents.get(term);
+ }
+
+ List<String> top = new ArrayList<>();
+ List<String> query = new ArrayList<>();
+ query.add(term);
+
+ while (!query.isEmpty())
+ {
+ List<String> nextQuery = new ArrayList<>();
+ for (String q : query)
+ {
+ List<String> theParents = parents.get(q);
+ if (theParents != null)
+ {
+ if (theParents.size() == 1 && theParents.get(0).equals(q))
+ {
+ /*
+ * top-level term
+ */
+ if (!top.contains(q))
+ {
+ top.add(q);
+ }
+ }
+ else
+ {
+ for (String p : theParents)
+ {
+ if (!p.equals(q))
+ {
+ nextQuery.add(p);
+ }
+ }
+ }
+ }
+ }
+ query = nextQuery;
+ }
+
+ rootParents.put(term, top);
+
+ return top.isEmpty() ? null : top;
+ }
+
+ @Override
+ public List<String> getParents(String term)
+ {
+ List<String> result = parents.get(term);
+ return result == null ? new ArrayList<>() : result;
+ }
+
+ @Override
+ public boolean isValidTerm(String term)
+ {
+ return parents.containsKey(term);
+ }
}
--- /dev/null
+/*
+ * BioJava development code
+ *
+ * This code may be freely distributed and modified under the
+ * terms of the GNU Lesser General Public Licence. This should
+ * be distributed with the code. If you do not have a copy,
+ * see:
+ *
+ * http://www.gnu.org/copyleft/lesser.html
+ *
+ * Copyright for this code is held jointly by the individual
+ * authors. These should be listed in @author doc comments.
+ *
+ * For more information on the BioJava project and its aims,
+ * or to join the biojava-l mailing list, visit the home page
+ * at:
+ *
+ * http://www.biojava.org/
+ *
+ * Created on Jan 24, 2008
+ *
+ */
+
+package org.biojava.nbio.ontology;
+
+import java.util.Comparator;
+
+
+public class Synonym implements Comparable<Synonym>{
+
+
+ public final static int UNKNOWN_SCOPE = -1;
+ public final static int RELATED_SYNONYM = 0;
+ public final static int EXACT_SYNONYM = 1;
+ public final static int NARROW_SYNONYM = 2;
+ public final static int BROAD_SYNONYM = 3;
+
+ int scope;
+ String category;
+ String name;
+
+ @Override
+ public String toString(){
+ String txt = "Synonym: name:"+name+ " category:" + category + " scope: " +scope;
+ return txt;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return toString().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ return obj instanceof Synonym
+ && ((Synonym) obj).toString().equals(this.toString());
+ }
+
+ public final static Comparator<Synonym> COMPARATOR = new Comparator<Synonym>()
+ {
+ @Override
+ public int compare(Synonym a, Synonym b) {
+ if (a == null && b == null)
+ {
+ return 0;
+ }
+ if (a == null)
+ {
+ return -1;
+ }
+ if (b == null)
+ {
+ return 1;
+ }
+ if (a.equals(b))
+ {
+ return 0;
+ }
+ String catA = a.getCategory();
+ String catB = b.getCategory();
+ if (catA == null && catB != null)
+ {
+ return 1;
+ }
+ if (catA != null && catB == null)
+ {
+ return -1;
+ }
+ if (catA != null && catB != null)
+ {
+ int comp = catA.compareToIgnoreCase(catB);
+ if (comp != 0)
+ {
+ return comp;
+ }
+ }
+ // todo check for null name
+ return a.getName().compareToIgnoreCase(b.getName());
+ }
+ };
+
+ public Synonym() {
+ }
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public String getCategory() {
+ return category;
+ }
+ public void setCategory(String category) {
+ this.category = category;
+ }
+ public int getScope() {
+ return scope;
+ }
+ public void setScope(int scope) {
+ this.scope = scope;
+ }
+ @Override
+ public int compareTo(Synonym o) {
+ return COMPARATOR.compare(this, o);
+ }
+}
public void testFindColumnsWithFeature()
{
SequenceI seq1 = new Sequence("seq1", "-a-MMMaaaaaaaaaaaaaaaa");
- SequenceI seq2 = new Sequence("seq2", "aa--aMM-MMMMMaaaaaaaaaa");
+ SequenceI seq2 = new Sequence("seq2/11-30", "aa--aMM-MMMMMaaaaaaaaaa");
SequenceI seq3 = new Sequence("seq3", "abcab-caD-aaMMMMMaaaaa");
SequenceI seq4 = new Sequence("seq4", "abc--abcaaaaaaaaaaaaaa");
/*
- * features start/end are base 1
+ * features
*/
- seq1.addSequenceFeature(new SequenceFeature("Metal", "desc", 2, 4, 0f,
- null));
- seq1.addSequenceFeature(new SequenceFeature("Helix", "desc", 1, 15, 0f,
- null));
- seq2.addSequenceFeature(new SequenceFeature("Metal", "desc", 4, 10,
- 10f,
- null));
- seq3.addSequenceFeature(new SequenceFeature("Metal", "desc", 11, 15,
- 10f, null));
+ seq1.addSequenceFeature(
+ new SequenceFeature("Metal", "desc", 2, 4, 0f, null));
+ seq1.addSequenceFeature(
+ new SequenceFeature("Helix", "desc", 1, 15, 0f, null));
+ seq2.addSequenceFeature(
+ new SequenceFeature("Metal", "desc", 14, 20, 10f, null));
+ seq3.addSequenceFeature(
+ new SequenceFeature("Metal", "desc", 11, 15, 10f, null));
// disulfide bond is a 'contact feature' - only select its 'start' and 'end'
- seq3.addSequenceFeature(new SequenceFeature("disulfide bond", "desc",
- 8, 12, 0f, null));
+ seq3.addSequenceFeature(
+ new SequenceFeature("disulfide bond", "desc", 8, 12, 0f, null));
/*
* select the first five columns --> Metal in seq1 cols 4-5
/*
* set features visible on a viewport as only visible features are selected
*/
- AlignFrame af = new AlignFrame(new Alignment(new SequenceI[] { seq1,
- seq2, seq3, seq4 }), 100, 100);
+ Alignment al = new Alignment(
+ new SequenceI[]
+ { seq1, seq2, seq3, seq4 });
+ AlignFrame af = new AlignFrame(al, 100, 100);
af.getFeatureRenderer().findAllFeatures(true);
AlignViewController avc = new AlignViewController(af, af.getViewport(),
af.alignPanel);
BitSet bs = new BitSet();
- int seqCount = avc.findColumnsWithFeature("Metal", sg, bs);
+ int seqCount = avc.findColumnsWithFeature(sg, bs, "Metal");
assertEquals(1, seqCount);
assertEquals(2, bs.cardinality());
assertTrue(bs.get(3)); // base 0
*/
sg.setEndRes(6);
bs.clear();
- seqCount = avc.findColumnsWithFeature("Metal", sg, bs);
+ seqCount = avc.findColumnsWithFeature(sg, bs, "Metal");
assertEquals(2, seqCount);
assertEquals(4, bs.cardinality());
assertTrue(bs.get(3));
sg.setStartRes(13);
sg.setEndRes(13);
bs.clear();
- seqCount = avc.findColumnsWithFeature("Metal", sg, bs);
+ seqCount = avc.findColumnsWithFeature(sg, bs, "Metal");
assertEquals(1, seqCount);
assertEquals(1, bs.cardinality());
assertTrue(bs.get(13));
sg.setStartRes(17);
sg.setEndRes(19);
bs.clear();
- seqCount = avc.findColumnsWithFeature("Metal", sg, bs);
+ seqCount = avc.findColumnsWithFeature(sg, bs, "Metal");
assertEquals(0, seqCount);
assertEquals(0, bs.cardinality());
sg.setStartRes(0);
sg.setEndRes(6);
bs.clear();
- seqCount = avc.findColumnsWithFeature("Metal", sg, bs);
+ seqCount = avc.findColumnsWithFeature(sg, bs, "Metal");
assertEquals(1, seqCount);
assertEquals(2, bs.cardinality());
assertTrue(bs.get(5));
sg.setStartRes(10);
sg.setEndRes(12);
bs.clear();
- seqCount = avc.findColumnsWithFeature("disulfide bond", sg, bs);
+ seqCount = avc.findColumnsWithFeature(sg, bs, "disulfide bond");
assertEquals(0, seqCount);
assertEquals(0, bs.cardinality());
sg.setStartRes(5);
sg.setEndRes(17);
bs.clear();
- seqCount = avc.findColumnsWithFeature("disulfide bond", sg, bs);
+ seqCount = avc.findColumnsWithFeature(sg, bs, "disulfide bond");
assertEquals(1, seqCount);
assertEquals(2, bs.cardinality());
assertTrue(bs.get(8));
assertTrue(bs.get(13));
/*
+ * look for multiple features; should match
+ * transcript_variant in seq3 positions 3-6, columns 3-7
+ * sequence_variant in seq2 positions 15-18, columns 7-11
+ * transcript_variant in seq3 positions 8 and 12, columns 9 and 14
+ */
+ seq3.addSequenceFeature(new SequenceFeature("transcript_variant",
+ "desc", 3, 6, 0f, null));
+ seq2.addSequenceFeature(new SequenceFeature("sequence_variant", "desc",
+ 15, 18, 0f, null));
+ sg.setStartRes(0);
+ sg.setEndRes(20);
+ bs.clear();
+ seqCount = avc.findColumnsWithFeature(sg, bs, "transcript_variant",
+ "sequence_variant", "disulfide bond", "junk");
+ assertEquals(2, seqCount);
+ assertEquals(10, bs.cardinality()); // 2-10 and 13, base 0
+ for (int i = 2; i <= 10; i++)
+ {
+ assertTrue(bs.get(i));
+ }
+ assertTrue(bs.get(13));
+
+ /*
* look for a feature that isn't there
*/
sg.setStartRes(0);
sg.setEndRes(19);
bs.clear();
- seqCount = avc.findColumnsWithFeature("Pfam", sg, bs);
+ seqCount = avc.findColumnsWithFeature(sg, bs, "Pfam");
assertEquals(0, seqCount);
assertEquals(0, bs.cardinality());
}
--- /dev/null
+package jalview.datamodel.ontology;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import jalview.ext.so.SequenceOntology;
+import jalview.io.gff.SequenceOntologyFactory;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class OntologyBaseTest
+{
+ @BeforeClass(alwaysRun = true)
+ public void setUp()
+ {
+ SequenceOntologyFactory.setInstance(new SequenceOntology());
+ }
+
+ @AfterClass(alwaysRun = true)
+ public void tearDown()
+ {
+ SequenceOntologyFactory.setInstance(null);
+ }
+
+ @Test(groups = "Functional")
+ public void testfindSequenceOntologyGroupings_nucleotide()
+ {
+ /*
+ * typical gnomAD feature types, plus the top level 'sequence_variant' as in dbSNP
+ */
+ List<String> featureTypes = Arrays.asList("sequence_variant",
+ "inframe_insertion", "stop_lost", "stop_gained",
+ "5_prime_UTR_variant", "non_coding_transcript_exon_variant",
+ "synonymous_variant", "inframe_deletion", "frameshift_variant",
+ "upstream_gene_variant", "splice_region_variant",
+ "missense_variant");
+
+ /*
+ * hierarchy from stop_gained
+ */
+ Map<String, List<String>> map = SequenceOntologyFactory.getInstance()
+ .findSequenceOntologyGroupings("stop_gained", featureTypes);
+ assertEquals(map.size(), 10);
+
+ /*
+ * feature_truncation adds inframe_deletion
+ */
+ List<String> terms = map.get("feature_truncation");
+ assertEquals(terms.size(), 1);
+ assertTrue(terms.contains("inframe_deletion"));
+
+ /*
+ * nonsynonymous_variant adds stop_lost, missense_variant
+ */
+ terms = map.get("nonsynonymous_variant");
+ assertEquals(terms.size(), 2);
+ assertEquals(terms.toString(), "[missense_variant, stop_lost]");
+
+ /*
+ * inframe_variant further adds inframe_deletion, inframe_insertion
+ */
+ terms = map.get("inframe_variant");
+ assertEquals(terms.size(), 4);
+ assertEquals(terms.toString(),
+ "[inframe_deletion, inframe_insertion, missense_variant, stop_lost]");
+
+ /*
+ * protein_altering_variant further adds frameshift_variant
+ */
+ terms = map.get("protein_altering_variant");
+ assertEquals(terms.size(), 5);
+ assertEquals(terms.toString(),
+ "[frameshift_variant, inframe_deletion, inframe_insertion, "
+ + "missense_variant, stop_lost]");
+
+ /*
+ * coding_sequence_variant further adds synonymous_variant
+ */
+ terms = map.get("coding_sequence_variant");
+ assertEquals(terms.size(), 6);
+ assertEquals(terms.toString(),
+ "[frameshift_variant, inframe_deletion, inframe_insertion, "
+ + "missense_variant, stop_lost, synonymous_variant]");
+
+ /*
+ * coding_transcript_variant further adds 5_prime_UTR_variant
+ */
+ terms = map.get("coding_transcript_variant");
+ assertEquals(terms.size(), 7);
+ assertEquals(terms.toString(),
+ "[5_prime_UTR_variant, frameshift_variant, inframe_deletion, "
+ + "inframe_insertion, missense_variant, stop_lost, synonymous_variant]");
+
+ /*
+ * exon_variant further adds non_coding_transcript_exon_variant
+ */
+ terms = map.get("exon_variant");
+ assertEquals(terms.size(), 8);
+ assertEquals(terms.toString(),
+ "[5_prime_UTR_variant, frameshift_variant, inframe_deletion, "
+ + "inframe_insertion, missense_variant, "
+ + "non_coding_transcript_exon_variant, stop_lost, synonymous_variant]");
+
+ /*
+ * transcript_variant further adds splice_region_variant
+ */
+ terms = map.get("transcript_variant");
+ assertEquals(terms.size(), 9);
+ assertEquals(terms.toString(),
+ "[5_prime_UTR_variant, frameshift_variant, inframe_deletion, "
+ + "inframe_insertion, missense_variant, "
+ + "non_coding_transcript_exon_variant, splice_region_variant, "
+ + "stop_lost, synonymous_variant]");
+
+ /*
+ * feature_variant further adds upstream_gene_variant
+ */
+ terms = map.get("feature_variant");
+ assertEquals(terms.size(), 10);
+ assertEquals(terms.toString(),
+ "[5_prime_UTR_variant, frameshift_variant, inframe_deletion, "
+ + "inframe_insertion, missense_variant, "
+ + "non_coding_transcript_exon_variant, splice_region_variant, "
+ + "stop_lost, synonymous_variant, upstream_gene_variant]");
+
+ /*
+ * sequence_variant adds itself
+ */
+ terms = map.get("sequence_variant");
+ assertEquals(terms.size(), 11);
+ assertEquals(terms.toString(),
+ "[5_prime_UTR_variant, frameshift_variant, inframe_deletion, "
+ + "inframe_insertion, missense_variant, "
+ + "non_coding_transcript_exon_variant, sequence_variant, splice_region_variant, "
+ + "stop_lost, synonymous_variant, upstream_gene_variant]");
+ }
+
+ @Test(groups = "Functional")
+ public void testfindSequenceOntologyGroupings_peptide()
+ {
+ /*
+ * typical Uniprot feature types
+ */
+ List<String> featureTypes = Arrays.asList("BETA-TURN-IR", "NEST-RL",
+ "BETA-BULGE", "ALPHA-BETA-MOTIF", "ASX-TURN-IR",
+ "GAMMA-TURN-CLASSIC", "GAMMA-TURN-INVERSE", "BETA-TURN-IL",
+ "BETA-TURN-IIL");
+
+ /*
+ * hierarchy from GAMMA-TURN-INVERSE
+ * this is a synonym for
+ */
+ Map<String, List<String>> map = SequenceOntologyFactory.getInstance()
+ .findSequenceOntologyGroupings("TURN", featureTypes);
+ assertEquals(map.size(), 10);
+ }
+
+}
*/
package jalview.ext.so;
-import static org.testng.AssertJUnit.assertFalse;
-import static org.testng.AssertJUnit.assertTrue;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertTrue;
+import jalview.bin.Cache;
import jalview.gui.JvOptionPane;
-import jalview.io.gff.SequenceOntologyI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.biojava.nbio.ontology.Synonym;
+import org.biojava.nbio.ontology.Term;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
+import junit.extensions.PA;
+
public class SequenceOntologyTest
{
JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
}
- private SequenceOntologyI so;
+ private SequenceOntology so;
@BeforeClass(alwaysRun = true)
public void setUp()
{
+ Cache.initLogger();
long now = System.currentTimeMillis();
try
{
assertTrue(so.isA("inframe_deletion", "sequence_variant"));
assertTrue(so.isA("inframe_insertion", "sequence_variant"));
}
+
+ @Test(groups = "Functional")
+ public void testGetChildTerms()
+ {
+ List<String> terms = Collections.<String> emptyList();
+ List<String> children = so.getChildTerms("exon", terms);
+ assertTrue(children.isEmpty());
+
+ terms = Arrays.asList("gene", "transcript", "snRNA", "junk", "mRNA");
+ children = so.getChildTerms("exon", terms);
+ assertTrue(children.isEmpty());
+ children = so.getChildTerms("transcript", terms);
+ assertEquals(children.size(), 2);
+ assertTrue(children.contains("snRNA"));
+ assertTrue(children.contains("mRNA"));
+
+ terms = Arrays.asList("gene", "transcript", "synonymous_variant",
+ "stop_lost", "chain");
+ children = so.getChildTerms("sequence_variant", terms);
+ assertEquals(children.size(), 2);
+ assertTrue(children.contains("synonymous_variant"));
+ assertTrue(children.contains("stop_lost"));
+ }
+
+ @Test(groups = "Functional")
+ public void testGetParentTerms()
+ {
+ Set<String> terms = new HashSet<>();
+ terms.add("sequence_variant");
+ terms.add("NMD_transcript_variant");
+ terms.add("stop_lost");
+ terms.add("chain"); // not an SO term
+
+ Set<String> parents = so.getParentTerms(terms);
+ assertEquals(parents.size(), 2);
+ assertTrue(parents.contains("sequence_variant"));
+ assertTrue(parents.contains("chain"));
+ }
+
+ @Test(groups = "Functional")
+ public void testGetParents()
+ {
+ // invalid term
+ List<String> roots = so.getParents("xyz");
+ assertTrue(roots.isEmpty());
+
+ roots = so.getParents("stop_gained");
+ assertEquals(roots.size(), 2);
+ assertTrue(roots.contains("nonsynonymous_variant"));
+ assertTrue(roots.contains("feature_truncation"));
+
+ // top level term
+ roots = so.getParents("sequence_variant");
+ assertTrue(roots.isEmpty());
+
+ roots = so.getParents(null);
+ assertTrue(roots.isEmpty());
+ }
+
+ @Test(groups = "Functional")
+ public void testGetRootParents()
+ {
+ List<String> roots = so.getRootParents("xyz");
+ assertNull(roots);
+ roots = so.getRootParents(null);
+ assertNull(roots);
+
+ roots = so.getRootParents("stop_gained");
+ assertEquals(roots.size(), 1);
+ assertEquals(roots.get(0), "sequence_variant");
+
+ roots = so.getRootParents("sequence_variant");
+ assertEquals(roots.size(), 1);
+ assertEquals(roots.get(0), "sequence_variant");
+
+ roots = so.getRootParents("alanine");
+ assertEquals(roots.size(), 1);
+ assertEquals(roots.get(0), "sequence_feature");
+
+ /*
+ * verify these are now cached
+ */
+ Map<String, List<String>> cached = (Map<String, List<String>>) PA
+ .getValue(so, "rootParents");
+ List<String> parents = cached.get("stop_gained");
+ assertEquals(parents.size(), 1);
+ parents = cached.get("sequence_variant");
+ assertEquals(parents.size(), 1);
+ assertTrue(parents.contains("sequence_variant"));
+ parents = cached.get("alanine");
+ assertEquals(parents.size(), 1);
+ assertTrue(parents.contains("sequence_feature"));
+ }
+
+ @Test(groups = "Functional")
+ public void testGetTerm()
+ {
+ assertNull(so.getTerm(null));
+ assertNull(so.getTerm("!*£&!"));
+
+ Term t = so.getTerm("SO:0000084");
+ assertNotNull(t);
+ assertEquals(t.getDescription(), "micronuclear_sequence");
+ // name lookup is case sensitive
+ assertNull(so.getTerm("so:0000084"));
+
+ t = so.getTerm("alpha_helix");
+ assertNotNull(t);
+ Object[] synonyms = t.getSynonyms();
+ assertEquals(synonyms.length, 2);
+ assertEquals(((Synonym) synonyms[0]).getName(), "a-helix");
+ assertEquals(((Synonym) synonyms[1]).getName(), "helix");
+ // case-insensitive description lookup
+ Term t2 = so.getTerm("ALPHA_HELIX");
+ assertSame(t, t2);
+ // can also retrieve by synonym
+ t2 = so.getTerm("a-helix");
+ assertSame(t, t2);
+
+ t = so.getTerm("serine_threonine_motif");
+ t2 = so.getTerm("ST-MOTIF"); // synonym is "st_motif"
+ assertNotNull(t);
+ assertSame(t, t2);
+
+ /*
+ * if a synonym is ambiguous within a hierarchy,
+ * we keep it for the most general term (always valid)
+ * helix is a synonym for
+ * alpha_helix (isA) right_handed_peptide_helix (isA) peptide_helix
+ * motif is a synonym for polypeptide_conserved_motif (isA) polypeptide_motif
+ *
+ */
+ t = so.getTerm("helix");
+ assertNotNull(t);
+ assertEquals(t.getDescription(), "peptide_helix");
+ t = so.getTerm("motif");
+ assertNotNull(t);
+ assertEquals(t.getDescription(), "polypeptide_motif");
+
+ /*
+ * ambiguous synonyms with no mutual hierarchy are not cached
+ * 'sequence variation' is a synonym for
+ * sequence_alteration SO:0001059
+ * alternate_sequence_site SO:0001149
+ * and these have no 'isA' relationship
+ */
+ assertNull(so.getTerm("sequence_variation"));
+
+ /*
+ * nmd_transcript is synonym for SO:0001621:NMD_transcript_variant
+ * and also the description for SO:0002114:NMD_transcript
+ * since v3.1 of so-simple.obo
+ */
+ t = so.getTerm("SO:0002114");
+ assertNotNull(t);
+ t2 = so.getTerm("SO:0001621");
+ assertNotNull(t2);
+ assertSame(t, so.getTerm("nmd_transcript"));
+ assertSame(t2, so.getTerm("nmd_transcript_variant"));
+ }
+
+ @Test(groups = "Functional")
+ public void testCanonicalise()
+ {
+ assertNull(SequenceOntology.canonicalise(null));
+ assertEquals(SequenceOntology.canonicalise("A-b_c"), "a_b_c");
+ assertEquals(SequenceOntology.canonicalise("A-b-C"), "a_b_c");
+ assertEquals(SequenceOntology.canonicalise("metal binding site"), "metal_binding_site");
+ String s = "thisought_nottobe_modified!";
+ String s2 = SequenceOntology.canonicalise(s);
+ assertSame(s, s2);
+ }
}
@Test(groups = "Functional")
public void testHideFeatureColumns()
{
- SequenceI seq1 = new Sequence("Seq1", "ABCDEFGHIJ");
- SequenceI seq2 = new Sequence("Seq2", "ABCDEFGHIJ");
- seq1.addSequenceFeature(new SequenceFeature("Metal", "", 1, 5, 0f, null));
- seq2.addSequenceFeature(new SequenceFeature("Metal", "", 6, 10, 10f,
- null));
- seq1.addSequenceFeature(new SequenceFeature("Turn", "", 2, 4,
- Float.NaN, null));
- seq2.addSequenceFeature(new SequenceFeature("Turn", "", 7, 9,
- Float.NaN, null));
+ SequenceI seq1 = new Sequence("Seq1/01-10", "A---BCDEFG-HIJ");
+ SequenceI seq2 = new Sequence("Seq2/11-20", "-AB-CDEF--GHIJ");
+ String METAL = "Metal";
+ String TURN = "Turn";
+ seq1.addSequenceFeature(
+ new SequenceFeature(METAL, "", 1, 5, 0f, null));
+ seq2.addSequenceFeature(
+ new SequenceFeature(METAL, "", 16, 20, 10f, null));
+ seq1.addSequenceFeature(
+ new SequenceFeature(TURN, "", 2, 4, Float.NaN, null));
+ seq2.addSequenceFeature(
+ new SequenceFeature(TURN, "", 17, 19, Float.NaN, null));
AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
AlignFrame alignFrame = new AlignFrame(al, al.getWidth(),
al.getHeight());
/*
* hiding a feature not present does nothing
*/
- assertFalse(alignFrame.hideFeatureColumns("exon", true));
+ assertFalse(alignFrame.hideFeatureColumns(true, "exon"));
assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
-
assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
.getNumberOfRegions(), 0);
- assertFalse(alignFrame.hideFeatureColumns("exon", false));
+ assertFalse(alignFrame.hideFeatureColumns(false, "exon"));
assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
-
assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
.getNumberOfRegions(), 0);
/*
* hiding a feature in all columns does nothing
*/
- assertFalse(alignFrame.hideFeatureColumns("Metal", true));
+ assertFalse(alignFrame.hideFeatureColumns(true, METAL));
assertTrue(alignFrame.getViewport().getColumnSelection().isEmpty());
-
assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
.getNumberOfRegions(), 0);
/*
* threshold Metal to hide features where score < 5
- * seq1 feature in columns 1-5 is hidden
- * seq2 feature in columns 6-10 is shown
+ * seq1 feature in columns 1-8 is hidden
+ * seq2 feature in columns 8-14 is shown
+ * result: columns 8-14 are hidden
+ * note this includes gapped columns spanned by the feature
*/
FeatureColourI fc = new FeatureColour(null, Color.red, Color.blue, null,
0f, 10f);
fc.setAboveThreshold(true);
fc.setThreshold(5f);
- alignFrame.getFeatureRenderer().setColour("Metal", fc);
- assertTrue(alignFrame.hideFeatureColumns("Metal", true));
+ alignFrame.getFeatureRenderer().setColour(METAL, fc);
+ assertTrue(alignFrame.hideFeatureColumns(true, METAL));
HiddenColumns hidden = alignFrame.getViewport().getAlignment().getHiddenColumns();
assertEquals(hidden.getNumberOfRegions(), 1);
Iterator<int[]> regions = hidden.iterator();
- int[] next = regions.next();
- assertEquals(next[0], 5);
- assertEquals(next[1], 9);
+ assertEquals(regions.next(), new int[] { 7, 13 }); // base 0
+ assertFalse(regions.hasNext());
/*
* hide a feature present in some columns
- * sequence positions [2-4], [7-9] are column positions
- * [1-3], [6-8] base zero
+ * seq1 positions [2-4] are column positions [4-6] base zero
+ * seq2 positions [17-19] are column positions [10-12] base zero
*/
alignFrame.getViewport().showAllHiddenColumns();
- assertTrue(alignFrame.hideFeatureColumns("Turn", true));
+ assertTrue(alignFrame.hideFeatureColumns(true, TURN));
regions = alignFrame.getViewport().getAlignment()
.getHiddenColumns().iterator();
assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
.getNumberOfRegions(), 2);
- next = regions.next();
- assertEquals(next[0], 1);
- assertEquals(next[1], 3);
- next = regions.next();
- assertEquals(next[0], 6);
- assertEquals(next[1], 8);
+ assertEquals(regions.next(), new int[] { 4, 6 });
+ assertEquals(regions.next(), new int[] { 10, 12 });
+ assertFalse(regions.hasNext());
+
+ /*
+ * hiding a contact feature should only hide start and end positions,
+ * not the intermediate columns
+ */
+ String DISULFIDE = "Disulfide Bond";
+ seq1.addSequenceFeature(
+ new SequenceFeature(DISULFIDE, "", 1, 5, 0f, null));
+ alignFrame.getViewport().showAllHiddenColumns();
+ assertTrue(alignFrame.hideFeatureColumns(true, DISULFIDE));
+ regions = alignFrame.getViewport().getAlignment().getHiddenColumns()
+ .iterator();
+ assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
+ .getNumberOfRegions(), 2);
+ assertEquals(regions.next(), new int[] { 0, 0 });
+ assertEquals(regions.next(), new int[] { 7, 7 });
+ assertFalse(regions.hasNext());
+
+ /*
+ * hide multiple feature types:
+ * TURN columns hides 4-6, 10-12
+ * DISULFIDE columns hides 0, 7
+ * combined is { 0-0, 4-7, 10-12 }
+ */
+ alignFrame.getViewport().showAllHiddenColumns();
+ assertTrue(alignFrame.hideFeatureColumns(true, TURN, DISULFIDE));
+ regions = alignFrame.getViewport().getAlignment().getHiddenColumns()
+ .iterator();
+ assertEquals(alignFrame.getViewport().getAlignment().getHiddenColumns()
+ .getNumberOfRegions(), 3);
+ assertEquals(regions.next(), new int[] { 0, 0 });
+ assertEquals(regions.next(), new int[] { 4, 7 });
+ assertEquals(regions.next(), new int[] { 10, 12 });
+ assertFalse(regions.hasNext());
}
@BeforeClass(alwaysRun = true)
/*
* use read-only test properties file
*/
- Cache.loadProperties("test/jalview/io/testProps.jvprops");
- Jalview.main(new String[] { "-nonews" });
+ Jalview.main(
+ new String[]
+ { "-nonews", "-props", "test/jalview/io/testProps.jvprops" });
}
@AfterMethod(alwaysRun = true)
package jalview.io.gff;
-import static org.testng.AssertJUnit.assertFalse;
-import static org.testng.AssertJUnit.assertTrue;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+import jalview.datamodel.ontology.OntologyI;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
public class SequenceOntologyLiteTest
{
+ private OntologyI so;
+
+ @BeforeClass(alwaysRun = true)
+ public void setUp()
+ {
+ so = new SequenceOntologyLite();
+ }
+
@Test(groups = "Functional")
public void testIsA_sequenceVariant()
{
- SequenceOntologyI so = new SequenceOntologyLite();
-
assertFalse(so.isA("CDS", "sequence_variant"));
assertTrue(so.isA("sequence_variant", "sequence_variant"));
assertTrue(so.isA("inframe_insertion", "sequence_variant"));
assertTrue(so.isA("splice_region_variant", "sequence_variant"));
}
+
+ @Test(groups = "Functional")
+ public void testGetParentTerms()
+ {
+ Set<String> terms = new HashSet<>();
+ terms.add("sequence_variant");
+ terms.add("NMD_transcript_variant");
+ terms.add("stop_lost");
+ terms.add("chain"); // not an SO term
+
+ Set<String> parents = so.getParentTerms(terms);
+ assertEquals(parents.size(), 2);
+ assertTrue(parents.contains("sequence_variant"));
+ assertTrue(parents.contains("chain"));
+ }
+
+ @Test(groups = "Functional")
+ public void testGetChildTerms()
+ {
+ List<String> terms = Collections.<String> emptyList();
+ List<String> children = so.getChildTerms("exon", terms);
+ assertTrue(children.isEmpty());
+
+ terms = Arrays.asList("gene", "transcript", "snRNA", "junk", "mRNA");
+ children = so.getChildTerms("exon", terms);
+ assertTrue(children.isEmpty());
+ children = so.getChildTerms("transcript", terms);
+ assertEquals(children.size(), 2);
+ assertTrue(children.contains("snRNA"));
+ assertTrue(children.contains("mRNA"));
+
+ terms = Arrays.asList("gene", "transcript", "synonymous_variant",
+ "stop_lost", "chain");
+ children = so.getChildTerms("sequence_variant", terms);
+ assertEquals(children.size(), 2);
+ assertTrue(children.contains("synonymous_variant"));
+ assertTrue(children.contains("stop_lost"));
+ }
+
+ @Test(groups = "Functional")
+ public void testGetRootParents()
+ {
+ List<String> roots = so.getRootParents("xyz");
+ assertNull(roots);
+ roots = so.getRootParents(null);
+ assertNull(roots);
+
+ roots = so.getRootParents("stop_gained");
+ assertEquals(roots.size(), 1);
+ assertEquals(roots.get(0), "sequence_variant");
+ }
}