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