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