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;
private String rootSOTerm;
/*
- * feature types present in Feature Renderer which have the same Sequence
- * Ontology root parent as the one this editor is acting on
+ * 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 List<String> peerSoTerms;
+ private final Map<String, List<String>> relatedSoTerms;
/*
* if true, filter or colour settings are also applied to
- * any sub-types of rootSOTerm in the Sequence Ontology
+ * any sub-types of parentTerm in the Sequence Ontology
*/
private boolean applyFiltersToSubtypes;
private boolean applyColourToSubtypes;
+ private String parentSOTerm;
+
/**
* Constructor
*
this.featureType = theType;
ap = fr.ap;
- peerSoTerms = findSequenceOntologyPeers(this.featureType);
+ relatedSoTerms = findSequenceOntologyGroupings(this.featureType,
+ fr.getRenderOrder());
/*
- * save original colours and filters for this feature type
- * and any sub-types, to restore on Cancel
+ * 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 (String child : peerSoTerms)
+ for (List<String> related : relatedSoTerms.values())
{
- originalFilters.put(child, fr.getFeatureFilter(child));
- originalColours.put(child, fr.getFeatureColours().get(child));
+ for (String type : related)
+ {
+ originalFilters.put(type, fr.getFeatureFilter(type));
+ originalColours.put(type, fr.getFeatureColours().get(type));
+ }
}
adjusting = true;
}
/**
- * Answers a (possibly empty) list of feature types known to the Feature
- * Renderer which share a top level Sequence Ontology parent with the current
- * feature type. The current type is not included.
+ * 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 List<String> findSequenceOntologyPeers(String featureType)
+ protected static Map<String, List<String>> findSequenceOntologyGroupings(
+ String featureType, List<String> featureTypes)
{
- List<String> peers = new ArrayList<>();
+ List<String> sortedTypes = new ArrayList<>(featureTypes);
+ Collections.sort(sortedTypes);
+
+ Map<String, List<String>> parents = new HashMap<>();
/*
- * first find the SO term (if any) that is the root
- * parent of the current type
+ * 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();
- List<String> roots = so.getRootParents(featureType);
- if (roots == null || roots.size() > 1)
+ candidates.add(featureType);
+ while (!candidates.isEmpty())
{
- /*
- * feature type is not an SO term, or has ambiguous root
- */
- return peers;
- }
- rootSOTerm = roots.get(0);
-
- List<String> types = fr.getRenderOrder();
- for (String type : types)
- {
- if (!type.equals(featureType) && so.isA(type, rootSOTerm))
+ String term = candidates.remove(0);
+ List<String> includedFeatures = new ArrayList<>();
+ for (String type : sortedTypes)
{
- peers.add(type);
+ 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));
}
- Collections.sort(peers); // sort for ease of reading in tooltip
- return peers;
+
+ return parents;
}
/**
MessageManager.getString("action.colour"), true);
/*
- * option to apply colour to peer types as well (if there are any)
+ * option to apply colour to other selected types as well
*/
- if (!peerSoTerms.isEmpty())
+ if (!relatedSoTerms.isEmpty())
{
applyColourToSubtypes = false;
colourByPanel.add(initSubtypesPanel(false));
}
/**
- * Constructs and returns a panel with a checkbox for the option to apply any
- * changes also to sub-types of the feature type
+ * 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
*/
{
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));
- applyToSubtypesCB.setToolTipText(getSubtypesTooltip());
- applyToSubtypesCB.addActionListener(new ActionListener()
+ toSubtypes.add(applyToSubtypesCB);
+
+ /*
+ * 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();
colourChanged(true);
}
}
- });
- toSubtypes.add(applyToSubtypesCB);
+ };
+ applyToSubtypesCB.addActionListener(action);
+ parentType.addActionListener(action);
return toSubtypes;
}
fr.setColour(featureType, acg);
if (applyColourToSubtypes)
{
- for (String child : peerSoTerms)
+ for (String child : relatedSoTerms.get(parentSOTerm))
{
fr.setColour(child, acg);
}
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
}
}
+ /**
+ * Restores colours for all feature types to their values when the dialog was
+ * opened
+ */
protected void restoreOriginalColours()
{
for (Entry<String, FeatureColourI> entry : originalColours.entrySet())
outerPanel.setBackground(Color.white);
/*
- * option to apply colour to peer types as well (if there are any)
+ * option to apply colour to other selected types as well
*/
- if (!peerSoTerms.isEmpty())
+ if (!relatedSoTerms.isEmpty())
{
applyFiltersToSubtypes = false;
outerPanel.add(initSubtypesPanel(true));
}
/**
- * Builds a tooltip for the 'Apply to subtypes' checkbox with a list of
- * subtypes of this feature type
+ * 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 getSubtypesTooltip()
+ protected String getSOTermsTooltip(List<String> list)
{
- StringBuilder sb = new StringBuilder(20 * peerSoTerms.size());
+ StringBuilder sb = new StringBuilder(20 * relatedSoTerms.size());
sb.append(MessageManager.getString("label.apply_also_to"));
- for (String child : peerSoTerms)
+ for (String child : list)
{
sb.append("<br>").append(child);
}
fr.setFeatureFilter(featureType, combined.isEmpty() ? null : combined);
if (applyFiltersToSubtypes)
{
- for (String child : peerSoTerms)
+ for (String child : relatedSoTerms.get(parentSOTerm))
{
fr.setFeatureFilter(child, combined.isEmpty() ? null : combined);
}
--- /dev/null
+package jalview.gui;
+
+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 FeatureTypeSettingsTest
+{
+ @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()
+ {
+ /*
+ * 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");
+
+ /*
+ * for stop_gained:
+ * transcript_variant further adds 5_prime_UTR_variant,
+ * non_coding_transcript_exon_variant, synonymous_variant, splice_region_variant
+ * feature_variant further adds upstream_gene_variant
+ * sequence_variant further adds sequence_variant
+ */
+ Map<String, List<String>> map = FeatureTypeSettings
+ .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]");
+ }
+}