Merge branch 'develop' into patch/JAL-4281_idwidthandannotHeight_in_project
[jalview.git] / src / jalview / renderer / ContactGeometry.java
1 package jalview.renderer;
2
3 import java.util.Arrays;
4 import java.util.Iterator;
5 import java.util.List;
6
7 import jalview.datamodel.ColumnSelection;
8 import jalview.datamodel.ContactListI;
9 import jalview.datamodel.HiddenColumns;
10 import jalview.renderer.ContactGeometry.contactInterval;
11
12 /**
13  * encapsulate logic for mapping between positions in a ContactList and their
14  * rendered representation in a given number of pixels.
15  * 
16  * @author jprocter
17  *
18  */
19 public class ContactGeometry
20 {
21
22   final ContactListI contacts;
23
24   /**
25    * how many pixels per contact (1..many)
26    */
27   final int pixels_step;
28
29   /**
30    * how many contacts per pixel (many > 0)
31    */
32   final double contacts_per_pixel;
33
34   /**
35    * number of contacts being mapped
36    */
37   final int contact_height;
38
39   /**
40    * number of pixels to map contact_height to
41    */
42   final int graphHeight;
43
44   /**
45    * number of contacts for each pixel_step - to last whole contact
46    */
47   final double contacts_step;
48   
49   final int lastStep;
50
51   /**
52    * Bean used to map from a range of contacts to a range of pixels
53    * @param contacts
54    * @param graphHeight Number of pixels to map given range of contacts
55    */
56   public ContactGeometry(final ContactListI contacts, int graphHeight)
57   {
58     this.contacts = contacts;
59     this.graphHeight = graphHeight;
60     contact_height = contacts.getContactHeight();
61     // fractional number of contacts covering each pixel
62     contacts_per_pixel = (graphHeight <= 1) ? contact_height
63             : ((double) contact_height) / ((double) graphHeight);
64
65     if (contacts_per_pixel >= 1)
66     {
67       // many contacts rendered per pixel
68       pixels_step = 1;
69     }
70     else
71     {
72       // pixel height for each contact
73       pixels_step = (int) Math
74               .ceil(((double) graphHeight) / (double) contact_height);
75     }
76     contacts_step = pixels_step*contacts_per_pixel;
77     lastStep = (int) Math.min((double)graphHeight, ((double)graphHeight)/((double)pixels_step));
78   }
79
80   public class contactInterval
81   {
82     public contactInterval(int cStart, int cEnd, int pStart, int pEnd)
83     {
84       this.cStart = cStart;
85       this.cEnd = cEnd;
86       this.pStart = pStart;
87       this.pEnd = pEnd;
88     }
89
90     // range on contact list
91     public final int cStart;
92
93     public final int cEnd;
94
95     // range in pixels
96     public final int pStart;
97
98     public final int pEnd;
99
100     @Override
101     public boolean equals(Object obj)
102     {
103       if (obj == null || !(obj instanceof contactInterval))
104       {
105         return false;
106       }
107       contactInterval them = (contactInterval) obj;
108       return cStart == them.cStart && cEnd == them.cEnd && pEnd == them.pEnd
109               && pStart == them.pStart;
110     }
111     @Override
112     public String toString()
113     {
114       return "Contacts ["+cStart+","+cEnd+"] : Pixels ["+pStart+","+pEnd+"]";
115     }
116   }
117
118   /**
119    * 
120    * @param columnSelection
121    * @param ci
122    * @param visibleOnly
123    *          - when true, only test intersection of visible columns given
124    *          matrix range
125    * @return true if the range on the matrix specified by ci intersects with
126    *         selected columns in the ContactListI's reference frame.
127    */
128
129   boolean intersects(contactInterval ci, ColumnSelection columnSelection,
130           HiddenColumns hiddenColumns, boolean visibleOnly)
131   {
132     boolean rowsel = false;
133     final int[] mappedRange = contacts.getMappedPositionsFor(ci.cStart,
134             ci.cEnd);
135     if (mappedRange == null)
136     {
137       return false;
138     }
139     for (int p = 0; p < mappedRange.length && !rowsel; p += 2)
140     {
141       boolean containsHidden = false;
142       if (visibleOnly && hiddenColumns != null
143               && hiddenColumns.hasHiddenColumns())
144       {
145         // TODO: turn into function on hiddenColumns and create test !!
146         Iterator<int[]> viscont = hiddenColumns.getVisContigsIterator(
147                 -1+mappedRange[p], -1+mappedRange[p + 1], false);
148         containsHidden = !viscont.hasNext();
149         if (!containsHidden)
150         {
151           for (int[] interval = viscont.next(); viscont
152                   .hasNext(); rowsel |= columnSelection
153                           .intersects(interval[p], interval[p + 1]))
154             ;
155         }
156       }
157       else
158       {
159         rowsel = columnSelection.intersects(-1+mappedRange[p],
160                 -1+mappedRange[p + 1]);
161       }
162     }
163     return rowsel;
164
165   }
166
167   /**
168    * Return mapped cell intersecting pStart \
169    * 
170    * FIXME: REDUNDANT METHOD - COULD DELETE FIXME: OR RE-IMPLEMENT AS EFFICIENT
171    * RANGE QUERY
172    * 
173    * @param pStart
174    *          [0..)
175    * @param pEnd
176    * @return nearest full cell containing pStart - does not set
177    *         contactInterval.pEnd or cEnd to equivalent position on pEnd !
178    */
179   public contactInterval mapFor(int pStart, int pEnd)
180   {
181     if (pStart < 0)
182     {
183       pStart = 0;
184     }
185     if (pEnd < pStart)
186     {
187       pEnd = pStart;
188     }
189     if (pEnd >= graphHeight)
190     {
191       pEnd = graphHeight - 1;
192     }
193     if (pStart >= graphHeight)
194     {
195       pStart = graphHeight - pixels_step;
196     }
197     int step = Math.floorDiv(pStart, pixels_step);
198     return findStep(step);
199   }
200
201   /**
202    * 
203    * @param step
204    *          [0..) n steps covering height and contactHeight
205    * @return contactInterval for step, or null if out of bounds
206    */
207   contactInterval findStep(int step)
208   {
209     if (step < 0 || step > lastStep)
210     {
211       return null;
212     }
213     return new contactInterval((int) Math.floor(contacts_step * step),
214             -1 + (int) Math.min(contact_height,
215                     Math.floor(contacts_step * (step + 1))),
216             pixels_step * step,
217             Math.min(graphHeight, (step + 1) * pixels_step) - 1);
218   }
219
220   /**
221    * return the cell containing given pixel
222    * 
223    * @param pCentre
224    * @return range for pCEntre
225    */
226   public contactInterval mapFor(int pCentre)
227   {
228     if (pCentre >= graphHeight + pixels_step)
229     {
230       return null;
231     }
232     int step = Math.floorDiv(pCentre, pixels_step);
233     return findStep(step);
234   }
235
236   public List<contactInterval> allSteps()
237   {
238     contactInterval[] array = new contactInterval[lastStep + 1];
239     int csum = 0, psum = 0;
240     for (int i = 0; i <= lastStep; i++)
241     {
242       array[i] = findStep(i);
243       csum += 1 + array[i].cEnd - array[i].cStart;
244       psum += 1 + array[i].pEnd - array[i].pStart;
245     }
246     if (csum != contact_height || psum != graphHeight)
247     {
248       System.err.println("csum = " + csum + " not " + contact_height + "\n"
249               + "psum = " + psum + " not " + graphHeight);
250       return null;
251     }
252     return Arrays.asList(array);
253   }
254
255   public Iterator<contactInterval> iterateOverContactIntervals(
256           int graphHeight)
257   {
258     // NOT YET IMPLEMENTED
259     return null;
260     // int cstart = 0, cend;
261     //
262     // for (int ht = y2,
263     // eht = y2 - graphHeight; ht >= eht; ht -= pixels_step)
264     // {
265     // cstart = (int) Math.floor(((double) y2 - ht) * contacts_per_pixel);
266     // cend = (int) Math.min(contact_height,
267     // Math.ceil(cstart + contacts_per_pixel * pixels_step));
268     //
269     // return new Iterator<contactIntervals>() {
270     //
271     // @Override
272     // public boolean hasNext()
273     // {
274     // // TODO Auto-generated method stub
275     // return false;
276     // }
277     //
278     // @Override
279     // public contactIntervals next()
280     // {
281     // // TODO Auto-generated method stub
282     // return null;
283     // }
284     //
285     // }
286   }
287 }