JAL-1807 Bob's JalviewJS prototype first commit
[jalviewjs.git] / src / jalview / analysis / AlignmentAnnotationUtils.java
1 package jalview.analysis;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.BitSet;
6 import java.util.Collections;
7 import java.util.HashMap;
8 import java.util.List;
9 import java.util.Map;
10
11 import javajs.J2SIgnoreImport;
12
13 import jalview.datamodel.AlignmentAnnotation;
14 import jalview.datamodel.SequenceI;
15 import jalview.renderer.AnnotationRenderer;
16
17 @J2SIgnoreImport({jalview.renderer.AnnotationRenderer.class})
18 public class AlignmentAnnotationUtils
19 {
20
21   /**
22    * Helper method to populate lists of annotation types for the Show/Hide
23    * Annotations menus. If sequenceGroup is not null, this is restricted to
24    * annotations which are associated with sequences in the selection group.
25    * <p/>
26    * If an annotation row is currently visible, its type (label) is added (once
27    * only per type), to the shownTypes list. If it is currently hidden, it is
28    * added to the hiddenTypesList.
29    * <p/>
30    * For rows that belong to a line graph group, so are always rendered
31    * together:
32    * <ul>
33    * <li>Treat all rows in the group as visible, if at least one of them is</li>
34    * <li>Build a list of all the annotation types that belong to the group</li>
35    * </ul>
36    * 
37    * @param shownTypes
38    *          a map, keyed by calcId (annotation source), whose entries are the
39    *          lists of annotation types found for the calcId; each annotation
40    *          type in turn may be a list (in the case of grouped annotations)
41    * @param hiddenTypes
42    *          a map, similar to shownTypes, but for hidden annotation types
43    * @param annotations
44    *          the annotations on the alignment to scan
45    * @param forSequences
46    *          the sequences to restrict search to
47    */
48   public static void getShownHiddenTypes(
49           Map<String, List<List<String>>> shownTypes,
50           Map<String, List<List<String>>> hiddenTypes,
51           List<AlignmentAnnotation> annotations,
52           List<SequenceI> forSequences)
53   {
54     BitSet visibleGraphGroups = AlignmentAnnotationUtils
55             .getVisibleLineGraphGroups(annotations);
56
57     /*
58      * Build a lookup, by calcId (annotation source), of all annotation types in
59      * each graph group.
60      */
61     Map<String, Map<Integer, List<String>>> groupLabels = new HashMap<String, Map<Integer, List<String>>>();
62
63     // trackers for which calcId!label combinations we have dealt with
64     List<String> addedToShown = new ArrayList<String>();
65     List<String> addedToHidden = new ArrayList<String>();
66
67     for (AlignmentAnnotation aa : annotations)
68     {
69       /*
70        * Ignore non-positional annotations, can't render these against an
71        * alignment
72        */
73       if (aa.annotations == null)
74       {
75         continue;
76       }
77       if (forSequences != null
78               && (aa.sequenceRef != null && forSequences
79                       .contains(aa.sequenceRef)))
80       {
81         String calcId = aa.getCalcId();
82
83         /*
84          * Build a 'composite label' for types in line graph groups.
85          */
86         final List<String> labelAsList = new ArrayList<String>();
87         final String displayLabel = aa.label;
88         labelAsList.add(displayLabel);
89         if (aa.graph == AlignmentAnnotation.LINE_GRAPH
90                 && aa.graphGroup > -1)
91         {
92           if (!groupLabels.containsKey(calcId))
93           {
94             groupLabels.put(calcId, new HashMap<Integer, List<String>>());
95           }
96           Map<Integer, List<String>> groupLabelsForCalcId = groupLabels
97                   .get(calcId);
98           if (groupLabelsForCalcId.containsKey(aa.graphGroup))
99           {
100             if (!groupLabelsForCalcId.get(aa.graphGroup).contains(
101                     displayLabel))
102             {
103               groupLabelsForCalcId.get(aa.graphGroup).add(displayLabel);
104             }
105           }
106           else
107           {
108             groupLabelsForCalcId.put(aa.graphGroup, labelAsList);
109           }
110         }
111         else
112         /*
113          * 'Simple case' - not a grouped annotation type - list of one label
114          * only
115          */
116         {
117           String rememberAs = calcId + "!" + displayLabel;
118           if (aa.visible && !addedToShown.contains(rememberAs))
119           {
120             if (!shownTypes.containsKey(calcId))
121             {
122               shownTypes.put(calcId, new ArrayList<List<String>>());
123             }
124             shownTypes.get(calcId).add(labelAsList);
125             addedToShown.add(rememberAs);
126           }
127           else
128           {
129             if (!aa.visible && !addedToHidden.contains(rememberAs))
130             {
131               if (!hiddenTypes.containsKey(calcId))
132               {
133                 hiddenTypes.put(calcId, new ArrayList<List<String>>());
134               }
135               hiddenTypes.get(calcId).add(labelAsList);
136               addedToHidden.add(rememberAs);
137             }
138           }
139         }
140       }
141     }
142     /*
143      * Finally add the 'composite group labels' to the appropriate lists,
144      * depending on whether the group is identified as visible or hidden. Don't
145      * add the same label more than once (there may be many graph groups that
146      * generate it).
147      */
148     for (String calcId : groupLabels.keySet())
149     {
150       for (int group : groupLabels.get(calcId).keySet())
151       {
152         final List<String> groupLabel = groupLabels.get(calcId).get(group);
153         // don't want to duplicate 'same types in different order'
154         Collections.sort(groupLabel);
155         if (visibleGraphGroups.get(group))
156         {
157           if (!shownTypes.containsKey(calcId))
158           {
159             shownTypes.put(calcId, new ArrayList<List<String>>());
160           }
161           if (!shownTypes.get(calcId).contains(groupLabel))
162           {
163             shownTypes.get(calcId).add(groupLabel);
164           }
165         }
166         else
167         {
168           if (!hiddenTypes.containsKey(calcId))
169           {
170             hiddenTypes.put(calcId, new ArrayList<List<String>>());
171           }
172           if (!hiddenTypes.get(calcId).contains(groupLabel))
173           {
174             hiddenTypes.get(calcId).add(groupLabel);
175           }
176         }
177       }
178     }
179   }
180
181   /**
182    * Returns a BitSet (possibly empty) of those graphGroups for line graph
183    * annotations, which have at least one member annotation row marked visible.
184    * <p/>
185    * Only one row in each visible group is marked visible, but when it is drawn,
186    * so are all the other rows in the same group.
187    * <p/>
188    * This lookup set allows us to check whether rows apparently marked not
189    * visible are in fact shown.
190    * 
191    * @see AnnotationRenderer#drawComponent
192    * @param annotations
193    * @return
194    */
195   public static BitSet getVisibleLineGraphGroups(
196           List<AlignmentAnnotation> annotations)
197   {
198     BitSet result = new BitSet();
199     for (AlignmentAnnotation ann : annotations)
200     {
201       if (ann.graph == AlignmentAnnotation.LINE_GRAPH && ann.visible)
202       {
203         int gg = ann.graphGroup;
204         if (gg > -1)
205         {
206           result.set(gg);
207         }
208       }
209     }
210     return result;
211   }
212
213   /**
214    * Converts an array of AlignmentAnnotation into a List of
215    * AlignmentAnnotation. A null array is converted to an empty list.
216    * 
217    * @param anns
218    * @return
219    */
220   public static List<AlignmentAnnotation> asList(AlignmentAnnotation[] anns)
221   {
222     // TODO use AlignmentAnnotationI instead when it exists
223     return (anns == null ? Collections.<AlignmentAnnotation> emptyList()
224             : Arrays.asList(anns));
225   }
226 }