X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fviewmodel%2Fseqfeatures%2FFeatureRendererModel.java;h=e812ed58c594ad5c3e890ab872344351800f9869;hb=57738a1f3c19b1c3a00bd3ac5108f8cd0af32f99;hp=479767593428bf88bc81141a1c942c92e3bb0bf4;hpb=e16e9699b56f3db4e42dc694aaafad76dae66c4b;p=jalview.git diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java index 4797675..e812ed5 100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java @@ -20,23 +20,12 @@ */ package jalview.viewmodel.seqfeatures; -import jalview.api.AlignViewportI; -import jalview.api.FeatureColourI; -import jalview.api.FeaturesDisplayedI; -import jalview.datamodel.AlignmentI; -import jalview.datamodel.SequenceFeature; -import jalview.datamodel.SequenceI; -import jalview.datamodel.features.SequenceFeatures; -import jalview.renderer.seqfeatures.FeatureRenderer; -import jalview.schemes.FeatureColour; -import jalview.util.ColorUtils; -import jalview.util.matcher.KeyedMatcherSetI; - import java.awt.Color; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; @@ -46,20 +35,50 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import jalview.api.AlignViewportI; +import jalview.api.FeatureColourI; +import jalview.api.FeaturesDisplayedI; +import jalview.datamodel.AlignedCodonFrame; +import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.MappedFeatures; +import jalview.datamodel.SearchResultMatchI; +import jalview.datamodel.SearchResults; +import jalview.datamodel.SearchResultsI; +import jalview.datamodel.SequenceFeature; +import jalview.datamodel.SequenceI; +import jalview.datamodel.features.FeatureMatcherSetI; +import jalview.datamodel.features.SequenceFeatures; +import jalview.renderer.seqfeatures.FeatureRenderer; +import jalview.schemes.FeatureColour; +import jalview.util.ColorUtils; +import jalview.util.Platform; + public abstract class FeatureRendererModel implements jalview.api.FeatureRenderer { /* - * column indices of fields in Feature Settings table - * todo: transfer valuers as data beans instead of Object[][] + * a data bean to hold one row of feature settings from the gui */ - public static final int TYPE_COLUMN = 0; + public static class FeatureSettingsBean + { + public final String featureType; - public static final int COLOUR_COLUMN = 1; + public final FeatureColourI featureColour; - public static final int FILTER_COLUMN = 2; + public final FeatureMatcherSetI filter; - public static final int SHOW_COLUMN = 3; + public final Boolean show; + + public FeatureSettingsBean(String type, FeatureColourI colour, + FeatureMatcherSetI theFilter, Boolean isShown) + { + featureType = type; + featureColour = colour; + filter = theFilter; + show = isShown; + } + } /* * global transparency for feature @@ -79,17 +98,17 @@ public abstract class FeatureRendererModel /* * filters for each feature type */ - protected Map featureFilters = new HashMap<>(); + protected Map featureFilters = new HashMap<>(); protected String[] renderOrder; Map featureOrder = null; - protected PropertyChangeSupport changeSupport = new PropertyChangeSupport( - this); - protected AlignViewportI av; + private PropertyChangeSupport changeSupport = new PropertyChangeSupport( + this); + @Override public AlignViewportI getViewport() { @@ -282,14 +301,21 @@ public abstract class FeatureRendererModel { firing = Boolean.TRUE; findAllFeatures(true); // add all new features as visible - changeSupport.firePropertyChange("changeSupport", null, null); + notifyFeaturesChanged(); firing = Boolean.FALSE; } } } @Override - public List findFeaturesAtColumn(SequenceI sequence, int column) + public void notifyFeaturesChanged() + { + changeSupport.firePropertyChange("changeSupport", null, null); + } + + @Override + public List findFeaturesAtColumn(SequenceI sequence, + int column) { /* * include features at the position provided their feature type is @@ -309,12 +335,12 @@ public abstract class FeatureRendererModel visibleTypes); /* - * include features unless their feature group is not displayed, or - * they are hidden (have no colour) based on a filter or colour threshold + * include features unless they are hidden (have no colour), based on + * feature group visibility, or a filter or colour threshold */ for (SequenceFeature sf : features) { - if (!featureGroupNotShown(sf) && getColour(sf) != null) + if (getColour(sf) != null) { result.add(sf); } @@ -383,8 +409,8 @@ public abstract class FeatureRendererModel } if (groupDisplayed) { - Set types = asq.getFeatures().getFeatureTypesForGroups( - true, group); + Set types = asq.getFeatures() + .getFeatureTypesForGroups(true, group); for (String type : types) { if (!allfeatures.contains(type)) // or use HashSet and no test? @@ -463,6 +489,7 @@ public abstract class FeatureRendererModel } } } + protected Boolean firing = Boolean.FALSE; /** @@ -601,10 +628,11 @@ public abstract class FeatureRendererModel * @param type * @return */ - protected boolean showFeatureOfType(String type) + public boolean showFeatureOfType(String type) { - return type == null ? false : (av.getFeaturesDisplayed() == null ? true - : av.getFeaturesDisplayed().isVisible(type)); + return type == null ? false + : (av.getFeaturesDisplayed() == null ? true + : av.getFeaturesDisplayed().isVisible(type)); } @Override @@ -641,7 +669,7 @@ public abstract class FeatureRendererModel { featureOrder = new Hashtable<>(); } - featureOrder.put(type, new Float(position)); + featureOrder.put(type, Float.valueOf(position)); return position; } @@ -673,10 +701,10 @@ public abstract class FeatureRendererModel * Replace current ordering with new ordering * * @param data - * { String(Type), Colour(Type), Boolean(Displayed) } + * an array of { Type, Colour, Filter, Boolean } * @return true if any visible features have been reordered, else false */ - public boolean setFeaturePriority(Object[][] data) + public boolean setFeaturePriority(FeatureSettingsBean[] data) { return setFeaturePriority(data, true); } @@ -686,13 +714,14 @@ public abstract class FeatureRendererModel * on top) at the start of the data array * * @param data - * { String(Type), Colour(Type), Boolean(Displayed) } + * an array of { Type, Colour, Filter, Boolean } * @param visibleNew * when true current featureDisplay list will be cleared * @return true if any visible features have been reordered or recoloured, * else false (i.e. no need to repaint) */ - public boolean setFeaturePriority(Object[][] data, boolean visibleNew) + public boolean setFeaturePriority(FeatureSettingsBean[] data, + boolean visibleNew) { /* * note visible feature ordering and colours before update @@ -731,9 +760,9 @@ public abstract class FeatureRendererModel { for (int i = 0; i < data.length; i++) { - String type = data[i][TYPE_COLUMN].toString(); - setColour(type, (FeatureColourI) data[i][COLOUR_COLUMN]); - if (((Boolean) data[i][SHOW_COLUMN]).booleanValue()) + String type = data[i].featureType; + setColour(type, data[i].featureColour); + if (data[i].show) { av_featuresdisplayed.setVisible(type); } @@ -840,7 +869,7 @@ public abstract class FeatureRendererModel } if (newGroupsVisible) { - featureGroups.put(group, new Boolean(true)); + featureGroups.put(group, Boolean.valueOf(true)); return true; } return false; @@ -876,7 +905,7 @@ public abstract class FeatureRendererModel @Override public void setGroupVisibility(String group, boolean visible) { - featureGroups.put(group, new Boolean(visible)); + featureGroups.put(group, Boolean.valueOf(visible)); } @Override @@ -888,7 +917,7 @@ public abstract class FeatureRendererModel for (String gst : toset) { Boolean st = featureGroups.get(gst); - featureGroups.put(gst, new Boolean(visible)); + featureGroups.put(gst, Boolean.valueOf(visible)); if (st != null) { rdrw = rdrw || (visible != st.booleanValue()); @@ -971,10 +1000,9 @@ public abstract class FeatureRendererModel * @param sequenceFeature * @return */ - protected boolean featureGroupNotShown(final SequenceFeature sequenceFeature) + public boolean featureGroupNotShown(final SequenceFeature sequenceFeature) { - return featureGroups != null - && sequenceFeature.featureGroup != null + return featureGroups != null && sequenceFeature.featureGroup != null && sequenceFeature.featureGroup.length() != 0 && featureGroups.containsKey(sequenceFeature.featureGroup) && !featureGroups.get(sequenceFeature.featureGroup) @@ -986,7 +1014,7 @@ public abstract class FeatureRendererModel */ @Override public List findFeaturesAtResidue(SequenceI sequence, - int resNo) + int fromResNo, int toResNo) { List result = new ArrayList<>(); if (!av.areFeaturesDisplayed() || getFeaturesDisplayed() == null) @@ -999,13 +1027,12 @@ public abstract class FeatureRendererModel * displayed, and feature group is null or the empty string * or marked for display */ - Set visibleFeatures = getFeaturesDisplayed() - .getVisibleFeatures(); + List visibleFeatures = getDisplayedFeatureTypes(); String[] visibleTypes = visibleFeatures .toArray(new String[visibleFeatures.size()]); - List features = sequence.getFeatures().findFeatures( - resNo, resNo, visibleTypes); - + List features = sequence.getFeatures() + .findFeatures(fromResNo, toResNo, visibleTypes); + for (SequenceFeature sf : features) { if (!featureGroupNotShown(sf) && getColour(sf) != null) @@ -1017,19 +1044,35 @@ public abstract class FeatureRendererModel } /** - * Removes from the list of features any that duplicate the location of a - * feature of the same type. Should be used only for features of the same, - * simple, feature colour (which normally implies the same feature type). Does - * not check visibility settings for feature type or feature group. + * Removes from the list of features any whose group is not shown, or that are + * visible and duplicate the location of a visible feature of the same type. + * Should be used only for features of the same, simple, feature colour (which + * normally implies the same feature type). No filtering is done if + * transparency, or any feature filters, are in force. * * @param features */ public void filterFeaturesForDisplay(List features) { - if (features.isEmpty()) + /* + * fudge: JalviewJS's IntervalStore lacks the sort method called :-( + */ + if (Platform.isJS()) + { + return; + } + + /* + * don't remove 'redundant' features if + * - transparency is applied (feature count affects depth of feature colour) + * - filters are applied (not all features may be displayable) + */ + if (features.isEmpty() || transparency != 1f + || !featureFilters.isEmpty()) { return; } + SequenceFeatures.sortFeatures(features, true); SequenceFeature lastFeature = null; @@ -1037,6 +1080,11 @@ public abstract class FeatureRendererModel while (it.hasNext()) { SequenceFeature sf = it.next(); + if (featureGroupNotShown(sf)) + { + it.remove(); + continue; + } /* * a feature is redundant for rendering purposes if it has the @@ -1056,25 +1104,26 @@ public abstract class FeatureRendererModel } @Override - public Map getFeatureFilters() + public Map getFeatureFilters() { - return new HashMap<>(featureFilters); + return featureFilters; } @Override - public void setFeatureFilters(Map filters) + public void setFeatureFilters(Map filters) { featureFilters = filters; } @Override - public KeyedMatcherSetI getFeatureFilter(String featureType) + public FeatureMatcherSetI getFeatureFilter(String featureType) { return featureFilters.get(featureType); } @Override - public void setFeatureFilter(String featureType, KeyedMatcherSetI filter) + public void setFeatureFilter(String featureType, + FeatureMatcherSetI filter) { if (filter == null || filter.isEmpty()) { @@ -1088,8 +1137,8 @@ public abstract class FeatureRendererModel /** * Answers the colour for the feature, or null if the feature is excluded by - * feature type or group visibility, by filters, or by colour threshold - * settings + * feature group visibility, by filters, or by colour threshold settings. This + * method does not take feature type visibility into account. * * @param sf * @param fc @@ -1098,14 +1147,6 @@ public abstract class FeatureRendererModel public Color getColor(SequenceFeature sf, FeatureColourI fc) { /* - * is the feature type displayed? - */ - if (!showFeatureOfType(sf.getType())) - { - return null; - } - - /* * is the feature group displayed? */ if (featureGroupNotShown(sf)) @@ -1120,7 +1161,7 @@ public abstract class FeatureRendererModel { return null; } - + return fc.getColor(sf); } @@ -1134,14 +1175,159 @@ public abstract class FeatureRendererModel */ protected boolean featureMatchesFilters(SequenceFeature sf) { - KeyedMatcherSetI filter = featureFilters.get(sf.getType()); - // TODO temporary fudge for Score and Label - return filter == null ? true - : filter.matches( - key -> "Label".equals(key[0]) ? sf.getDescription() - : ("Score".equals(key[0]) - ? String.valueOf(sf.getScore()) - : sf.getValueAsString(key))); + FeatureMatcherSetI filter = featureFilters.get(sf.getType()); + return filter == null ? true : filter.matches(sf); + } + + /** + * Answers true unless the specified group is set to hidden. Defaults to true + * if group visibility is not set. + * + * @param group + * @return + */ + public boolean isGroupVisible(String group) + { + if (!featureGroups.containsKey(group)) + { + return true; + } + return featureGroups.get(group); + } + + /** + * Orders features in render precedence (last in order is last to render, so + * displayed on top of other features) + * + * @param order + */ + public void orderFeatures(Comparator order) + { + Arrays.sort(renderOrder, order); } + @Override + public MappedFeatures findComplementFeaturesAtResidue( + final SequenceI sequence, final int pos) + { + SequenceI ds = sequence.getDatasetSequence(); + if (ds == null) + { + ds = sequence; + } + final char residue = ds.getCharAt(pos - ds.getStart()); + + List found = new ArrayList<>(); + List mappings = this.av.getAlignment() + .getCodonFrame(sequence); + + /* + * fudge: if no mapping found, check the complementary alignment + * todo: only store in one place? StructureSelectionManager? + */ + if (mappings.isEmpty()) + { + mappings = this.av.getCodingComplement().getAlignment() + .getCodonFrame(sequence); + } + + /* + * todo: direct lookup of CDS for peptide and vice-versa; for now, + * have to search through an unordered list of mappings for a candidate + */ + SequenceToSequenceMapping mapping = null; + SequenceI mapFrom = null; + + for (AlignedCodonFrame acf : mappings) + { + mapping = acf.getCoveringCodonMapping(ds); + if (mapping == null) + { + continue; + } + SearchResultsI sr = new SearchResults(); + mapping.markMappedRegion(ds, pos, sr); + for (SearchResultMatchI match : sr.getResults()) + { + int fromRes = match.getStart(); + int toRes = match.getEnd(); + mapFrom = match.getSequence(); + List fs = findFeaturesAtResidue(mapFrom, fromRes, + toRes); + for (SequenceFeature sf : fs) + { + if (!found.contains(sf)) + { + found.add(sf); + } + } + } + + /* + * just take the first mapped features we find + */ + if (!found.isEmpty()) + { + break; + } + } + if (found.isEmpty()) + { + return null; + } + + /* + * sort by renderorder (inefficiently but ok for small scale); + * NB this sorts 'on top' feature to end, for rendering + */ + List result = new ArrayList<>(); + final int toAdd = found.size(); + int added = 0; + for (String type : renderOrder) + { + for (SequenceFeature sf : found) + { + if (type.equals(sf.getType())) + { + result.add(sf); + added++; + } + if (added == toAdd) + { + break; + } + } + } + + return new MappedFeatures(mapping.getMapping(), mapFrom, pos, residue, + result); + } + + @Override + public boolean isVisible(SequenceFeature feature) + { + if (feature == null) + { + return false; + } + if (getFeaturesDisplayed() == null + || !getFeaturesDisplayed().isVisible(feature.getType())) + { + return false; + } + if (featureGroupNotShown(feature)) + { + return false; + } + FeatureColourI fc = featureColours.get(feature.getType()); + if (fc != null && fc.isOutwithThreshold(feature)) + { + return false; + } + if (!featureMatchesFilters(feature)) + { + return false; + } + return true; + } }